Exemple #1
0
# Licensed under the MIT License.

import aiounittest
import unittest
from copy import copy, deepcopy
from unittest.mock import Mock

from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext
from botbuilder.schema import Activity, ActivityTypes, ConversationAccount, ConversationReference, ChannelAccount
from botframework.connector.aio import ConnectorClient
from botframework.connector.auth import ClaimsIdentity

reference = ConversationReference(
    activity_id='1234',
    channel_id='test',
    service_url='https://example.org/channel',
    user=ChannelAccount(id='user', name='User Name'),
    bot=ChannelAccount(id='bot', name='Bot Name'),
    conversation=ConversationAccount(id='convo1'))

test_activity = Activity(text='test', type=ActivityTypes.message)

incoming_message = TurnContext.apply_conversation_reference(
    copy(test_activity), reference, True)
outgoing_message = TurnContext.apply_conversation_reference(
    copy(test_activity), reference)
incoming_invoke = TurnContext.apply_conversation_reference(
    Activity(type=ActivityTypes.invoke), reference, True)


class AdapterUnderTest(BotFrameworkAdapter):
Exemple #2
0
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import aiounittest
from botbuilder.schema import Activity, ConversationReference
from botbuilder.core import TurnContext
from botbuilder.core.adapters import TestAdapter

RECEIVED_MESSAGE = Activity(type="message", text="received")
UPDATED_ACTIVITY = Activity(type="message", text="update")
DELETED_ACTIVITY_REFERENCE = ConversationReference(activity_id="1234")


class TestTestAdapter(aiounittest.AsyncTestCase):
    async def test_should_call_bog_logic_when_receive_activity_is_called(self):
        async def logic(context: TurnContext):
            assert context
            assert context.activity
            assert context.activity.type == "message"
            assert context.activity.text == "test"
            assert context.activity.id
            assert context.activity.from_property
            assert context.activity.recipient
            assert context.activity.conversation
            assert context.activity.channel_id
            assert context.activity.service_url

        adapter = TestAdapter(logic)
        await adapter.receive_activity("test")

    async def test_should_support_receive_activity_with_activity(self):
Exemple #3
0
 async def get_conversation_reference(
         self, skill_conversation_id: str) -> ConversationReference:
     conversation_reference = ConversationReference().deserialize(
         json.loads(self._conversation_refs[skill_conversation_id]))
     return conversation_reference
Exemple #4
0
    async def post_activity(
        self,
        from_bot_id: str,
        to_bot_id: str,
        to_url: str,
        service_url: str,
        conversation_id: str,
        activity: Activity,
    ) -> InvokeResponse:
        app_credentials = await self._get_app_credentials(from_bot_id, to_bot_id)

        if not app_credentials:
            raise KeyError("Unable to get appCredentials to connect to the skill")

        # Get token for the skill call
        token = (
            app_credentials.get_access_token()
            if app_credentials.microsoft_app_id
            else None
        )

        # Capture current activity settings before changing them.
        # TODO: DO we need to set the activity ID? (events that are created manually don't have it).
        original_conversation_id = activity.conversation.id
        original_service_url = activity.service_url
        original_caller_id = activity.caller_id
        original_relates_to = activity.relates_to

        try:
            # TODO: The relato has to be ported to the adapter in the new integration library when
            #  resolving conflicts in merge
            activity.relates_to = ConversationReference(
                service_url=activity.service_url,
                activity_id=activity.id,
                channel_id=activity.channel_id,
                conversation=ConversationAccount(
                    id=activity.conversation.id,
                    name=activity.conversation.name,
                    conversation_type=activity.conversation.conversation_type,
                    aad_object_id=activity.conversation.aad_object_id,
                    is_group=activity.conversation.is_group,
                    role=activity.conversation.role,
                    tenant_id=activity.conversation.tenant_id,
                    properties=activity.conversation.properties,
                ),
                bot=None,
            )
            activity.conversation.id = conversation_id
            activity.service_url = service_url
            activity.caller_id = f"urn:botframework:aadappid:{from_bot_id}"

            headers_dict = {
                "Content-type": "application/json; charset=utf-8",
            }
            if token:
                headers_dict.update(
                    {"Authorization": f"Bearer {token}",}
                )

            json_content = json.dumps(activity.serialize())
            resp = await self._session.post(
                to_url, data=json_content.encode("utf-8"), headers=headers_dict,
            )
            resp.raise_for_status()
            data = (await resp.read()).decode()
            content = json.loads(data) if data else None

            if content:
                return InvokeResponse(status=resp.status, body=content)

        finally:
            # Restore activity properties.
            activity.conversation.id = original_conversation_id
            activity.service_url = original_service_url
            activity.caller_id = original_caller_id
            activity.relates_to = original_relates_to
    async def _process_activity(
        self,
        claims_identity: ClaimsIdentity,
        conversation_id: str,
        reply_to_activity_id: str,
        activity: Activity,
    ) -> ResourceResponse:
        conversation_reference_result = await self._conversation_id_factory.get_conversation_reference(
            conversation_id)

        oauth_scope = None
        conversation_reference = None
        if isinstance(conversation_reference_result,
                      SkillConversationReference):
            oauth_scope = conversation_reference_result.oauth_scope
            conversation_reference = (
                conversation_reference_result.conversation_reference)
        else:
            conversation_reference = conversation_reference_result
            oauth_scope = (
                GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
                if self._channel_provider
                and self._channel_provider.is_government() else
                AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE)

        if not conversation_reference:
            raise KeyError("ConversationReference not found")

        activity_conversation_reference = ConversationReference(
            activity_id=activity.id,
            user=activity.from_property,
            bot=activity.recipient,
            conversation=activity.conversation,
            channel_id=activity.channel_id,
            service_url=activity.service_url,
        )

        async def callback(context: TurnContext):
            context.turn_state[
                SkillHandler.
                SKILL_CONVERSATION_REFERENCE_KEY] = activity_conversation_reference
            TurnContext.apply_conversation_reference(activity,
                                                     conversation_reference)
            context.activity.id = reply_to_activity_id

            if activity.type == ActivityTypes.end_of_conversation:
                await self._conversation_id_factory.delete_conversation_reference(
                    conversation_id)
                self._apply_eoc_to_turn_context_activity(context, activity)
                await self._bot.on_turn(context)
            elif activity.type == ActivityTypes.event:
                self._apply_event_to_turn_context_activity(context, activity)
                await self._bot.on_turn(context)
            else:
                await context.send_activity(activity)

        await self._adapter.continue_conversation(
            conversation_reference,
            callback,
            claims_identity=claims_identity,
            audience=oauth_scope,
        )
        return ResourceResponse(id=str(uuid4()))
Exemple #6
0
)
from botbuilder.schema import (
    Activity,
    ActivityTypes,
    ConversationAccount,
    ConversationReference,
    ConversationResourceResponse,
    ChannelAccount,
)
from botframework.connector.aio import ConnectorClient
from botframework.connector.auth import ClaimsIdentity

REFERENCE = ConversationReference(
    activity_id="1234",
    channel_id="test",
    service_url="https://example.org/channel",
    user=ChannelAccount(id="user", name="User Name"),
    bot=ChannelAccount(id="bot", name="Bot Name"),
    conversation=ConversationAccount(id="convo1"),
)

TEST_ACTIVITY = Activity(text="test", type=ActivityTypes.message)

INCOMING_MESSAGE = TurnContext.apply_conversation_reference(
    copy(TEST_ACTIVITY), REFERENCE, True
)
OUTGOING_MESSAGE = TurnContext.apply_conversation_reference(
    copy(TEST_ACTIVITY), REFERENCE
)
INCOMING_INVOKE = TurnContext.apply_conversation_reference(
    Activity(type=ActivityTypes.invoke), REFERENCE, True
)
    async def test_continue_conversation_with_audience(self):
        mock_credential_provider = unittest.mock.create_autospec(CredentialProvider)

        settings = BotFrameworkAdapterSettings(
            app_id="bot_id", credential_provider=mock_credential_provider
        )
        adapter = BotFrameworkAdapter(settings)

        skill_1_app_id = "00000000-0000-0000-0000-000000skill1"
        skill_2_app_id = "00000000-0000-0000-0000-000000skill2"

        skills_identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: skill_1_app_id,
                AuthenticationConstants.APP_ID_CLAIM: skill_2_app_id,
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        skill_2_service_url = "https://skill2.com/api/skills/"

        async def callback(context: TurnContext):
            TestBotFrameworkAdapter.get_creds_and_assert_values(
                context, skill_1_app_id, skill_2_app_id, 1,
            )
            TestBotFrameworkAdapter.get_client_and_assert_values(
                context, skill_1_app_id, skill_2_app_id, skill_2_service_url, 1,
            )

            # pylint: disable=protected-access
            client_cache = context.adapter._connector_client_cache
            client = client_cache.get(
                BotFrameworkAdapter.key_for_connector_client(
                    skill_2_service_url, skill_1_app_id, skill_2_app_id,
                )
            )
            assert client

            turn_state_client = context.turn_state.get(
                BotFrameworkAdapter.BOT_CONNECTOR_CLIENT_KEY
            )
            assert turn_state_client
            client_creds = turn_state_client.config.credentials

            assert skill_1_app_id == client_creds.microsoft_app_id
            assert skill_2_app_id == client_creds.oauth_scope
            assert client.config.base_url == turn_state_client.config.base_url

            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
        )
from botframework.connector.aio import ConnectorClient
from botframework.connector.auth import (
    ClaimsIdentity,
    AuthenticationConstants,
    AppCredentials,
    CredentialProvider,
    SimpleChannelProvider,
    GovernmentConstants,
    SimpleCredentialProvider,
)

REFERENCE = ConversationReference(
    activity_id="1234",
    channel_id="test",
    locale="en-uS",  # Intentionally oddly-cased to check that it isn't defaulted somewhere, but tests stay in English
    service_url="https://example.org/channel",
    user=ChannelAccount(id="user", name="User Name"),
    bot=ChannelAccount(id="bot", name="Bot Name"),
    conversation=ConversationAccount(id="convo1"),
)

TEST_ACTIVITY = Activity(text="test", type=ActivityTypes.message)

INCOMING_MESSAGE = TurnContext.apply_conversation_reference(
    copy(TEST_ACTIVITY), REFERENCE, True
)
OUTGOING_MESSAGE = TurnContext.apply_conversation_reference(
    copy(TEST_ACTIVITY), REFERENCE
)
INCOMING_INVOKE = TurnContext.apply_conversation_reference(
    Activity(type=ActivityTypes.invoke), REFERENCE, True
    async def post_activity(
        self,
        from_bot_id: str,
        to_bot_id: str,
        to_url: str,
        service_url: str,
        conversation_id: str,
        activity: Activity,
    ) -> InvokeResponse:
        if not from_bot_id:
            raise TypeError("from_bot_id")
        if not to_bot_id:
            raise TypeError("to_bot_id")
        if not to_url:
            raise TypeError("to_url")
        if not service_url:
            raise TypeError("service_url")
        if not conversation_id:
            raise TypeError("conversation_id")
        if not activity:
            raise TypeError("activity")

        if self._logger:
            self._logger.log(20, f"post to skill '{to_bot_id}' at '{to_url}'")

        credentials = await self._credentials_factory.create_credentials(
            from_bot_id, to_bot_id, self._login_endpoint, True
        )

        # Get token for the skill call
        token = credentials.get_access_token() if credentials.microsoft_app_id else None

        # Clone the activity so we can modify it before sending without impacting the original object.
        activity_copy = deepcopy(activity)

        # Apply the appropriate addressing to the newly created Activity.
        activity_copy.relates_to = ConversationReference(
            service_url=activity_copy.service_url,
            activity_id=activity_copy.id,
            channel_id=activity_copy.channel_id,
            conversation=ConversationAccount(
                id=activity_copy.conversation.id,
                name=activity_copy.conversation.name,
                conversation_type=activity_copy.conversation.conversation_type,
                aad_object_id=activity_copy.conversation.aad_object_id,
                is_group=activity_copy.conversation.is_group,
                role=activity_copy.conversation.role,
                tenant_id=activity_copy.conversation.tenant_id,
                properties=activity_copy.conversation.properties,
            ),
            bot=None,
        )
        activity_copy.conversation.id = conversation_id
        activity_copy.service_url = service_url
        if not activity_copy.recipient:
            activity_copy.recipient = ChannelAccount(role=RoleTypes.skill)
        else:
            activity_copy.recipient.role = RoleTypes.skill

        headers_dict = {
            "Content-type": "application/json; charset=utf-8",
        }
        if token:
            headers_dict.update(
                {"Authorization": f"Bearer {token}",}
            )
        json_content = dumps(activity_copy.serialize()).encode("utf-8")

        request = HttpRequest(
            request_uri=to_url, content=json_content, headers=headers_dict
        )
        response = await self._http_client.post(request=request)

        data = await response.read_content_str()

        if not await response.is_succesful() and self._logger:
            # Otherwise we can assume we don't have to deserialize - so just log the content so it's not lost.
            self._logger.log(
                40,
                f"Bot Framework call failed to '{to_url}' returning '{int(response.status_code)}' and '{data}'",
            )

        return InvokeResponse(
            status=response.status_code, body=loads(data) if data else None
        )
Exemple #10
0
    async def test_continue_conversation_without_audience(self):
        mock_credential_provider = unittest.mock.create_autospec(
            CredentialProvider)

        settings = BotFrameworkAdapterSettings(
            app_id="bot_id", credential_provider=mock_credential_provider)
        adapter = BotFrameworkAdapter(settings)

        skill_1_app_id = "00000000-0000-0000-0000-000000skill1"
        skill_2_app_id = "00000000-0000-0000-0000-000000skill2"

        skills_identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: skill_1_app_id,
                AuthenticationConstants.APP_ID_CLAIM: skill_2_app_id,
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        channel_service_url = "https://smba.trafficmanager.net/amer/"

        async def callback(context: TurnContext):
            TestBotFrameworkAdapter.get_creds_and_assert_values(
                context,
                skill_1_app_id,
                AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
                1,
            )
            TestBotFrameworkAdapter.get_client_and_assert_values(
                context,
                skill_1_app_id,
                AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
                channel_service_url,
                1,
            )

            # pylint: disable=protected-access
            client_cache = context.adapter._connector_client_cache
            client = client_cache.get(
                BotFrameworkAdapter.key_for_connector_client(
                    channel_service_url,
                    skill_1_app_id,
                    AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
                ))
            assert client

            turn_state_client = context.turn_state.get(
                BotFrameworkAdapter.BOT_CONNECTOR_CLIENT_KEY)
            assert turn_state_client
            client_creds = turn_state_client.config.credentials

            assert skill_1_app_id == client_creds.microsoft_app_id
            assert (AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE ==
                    client_creds.oauth_scope)
            assert client.config.base_url == turn_state_client.config.base_url

            scope = context.turn_state[BotFrameworkAdapter.BOT_OAUTH_SCOPE_KEY]
            assert AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE == scope

        refs = ConversationReference(service_url=channel_service_url)

        await adapter.continue_conversation(refs,
                                            callback,
                                            claims_identity=skills_identity)
 def _build_conversation_reference(self) -> ConversationReference:
     return ConversationReference(
         conversation=ConversationAccount(id=str(uuid())),
         service_url=self.SERVICE_URL,
     )