async def logic(context: TurnContext):
            if test_case != SkillFlowTestCase.root_bot_only:
                # Create a skill ClaimsIdentity and put it in turn_state so isSkillClaim() returns True.
                claims_identity = ClaimsIdentity({}, False)
                claims_identity.claims[
                    "ver"
                ] = "2.0"  # AuthenticationConstants.VersionClaim
                claims_identity.claims[
                    "aud"
                ] = (
                    SimpleComponentDialog.skill_bot_id
                )  # AuthenticationConstants.AudienceClaim
                claims_identity.claims[
                    "azp"
                ] = (
                    SimpleComponentDialog.parent_bot_id
                )  # AuthenticationConstants.AuthorizedParty
                context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = claims_identity

                if test_case == SkillFlowTestCase.root_bot_consuming_skill:
                    # Simulate the SkillConversationReference with a channel OAuthScope stored in turn_state.
                    # This emulates a response coming to a root bot through SkillHandler.
                    context.turn_state[
                        SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY
                    ] = SkillConversationReference(
                        None, AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
                    )

                if test_case == SkillFlowTestCase.middle_skill:
                    # Simulate the SkillConversationReference with a parent Bot ID stored in turn_state.
                    # This emulates a response coming to a skill from another skill through SkillHandler.
                    context.turn_state[
                        SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY
                    ] = SkillConversationReference(
                        None, SimpleComponentDialog.parent_bot_id
                    )

            async def aux(
                turn_context: TurnContext,  # pylint: disable=unused-argument
                activities: List[Activity],
                next: Callable,
            ):
                for activity in activities:
                    if activity.type == ActivityTypes.end_of_conversation:
                        SimpleComponentDialog.eoc_sent = activity
                        break

                return await next()

            # Interceptor to capture the EoC activity if it was sent so we can assert it in the tests.
            context.on_send_activities(aux)

            SimpleComponentDialog.dm_turn_result = await dialog_manager.on_turn(context)
    async def __process_activity_creates_correct_creds_and_client(
        self,
        bot_app_id: str,
        expected_caller_id: str,
        channel_service: str,
        expected_scope: str,
        expected_app_credentials_count: int,
        expected_client_credentials_count: int,
    ):
        identity = ClaimsIdentity({}, True)
        if bot_app_id:
            identity.claims = {
                AuthenticationConstants.AUDIENCE_CLAIM: bot_app_id,
                AuthenticationConstants.APP_ID_CLAIM: bot_app_id,
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            }

        credential_provider = SimpleCredentialProvider(bot_app_id, None)
        service_url = "https://smba.trafficmanager.net/amer/"

        async def callback(context: TurnContext):
            TestBotFrameworkAdapter.get_creds_and_assert_values(
                context,
                bot_app_id,
                expected_scope,
                expected_app_credentials_count,
            )
            TestBotFrameworkAdapter.get_client_and_assert_values(
                context,
                bot_app_id,
                expected_scope,
                service_url,
                expected_client_credentials_count,
            )

            assert context.activity.caller_id == expected_caller_id

        settings = BotFrameworkAdapterSettings(
            bot_app_id,
            credential_provider=credential_provider,
            channel_provider=SimpleChannelProvider(channel_service),
        )
        sut = BotFrameworkAdapter(settings)
        await sut.process_activity_with_identity(
            Activity(
                channel_id="emulator",
                service_url=service_url,
                text="test",
            ),
            identity,
            callback,
        )
    async def test_process_activity_for_forwarded_activity(self):
        bot_app_id = "00000000-0000-0000-0000-000000000001"
        skill_1_app_id = "00000000-0000-0000-0000-000000skill1"
        identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: skill_1_app_id,
                AuthenticationConstants.APP_ID_CLAIM: bot_app_id,
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        service_url = "https://root-bot.test.azurewebsites.net/"

        async def callback(context: TurnContext):
            TestBotFrameworkAdapter.get_creds_and_assert_values(
                context, skill_1_app_id, bot_app_id, 1,
            )
            TestBotFrameworkAdapter.get_client_and_assert_values(
                context, skill_1_app_id, bot_app_id, service_url, 1,
            )

            scope = context.turn_state[BotFrameworkAdapter.BOT_OAUTH_SCOPE_KEY]
            assert bot_app_id == scope

        settings = BotFrameworkAdapterSettings(bot_app_id)
        sut = BotFrameworkAdapter(settings)
        await sut.process_activity_with_identity(
            Activity(channel_id="emulator", service_url=service_url, text="test",),
            identity,
            callback,
        )
Example #4
0
    def setUpClass(cls):
        cls.bot_id = str(uuid4())
        cls.skill_id = str(uuid4())

        cls._test_id_factory = ConversationIdFactoryForTest()

        cls._claims_identity = ClaimsIdentity({}, False)

        cls._claims_identity.claims[AuthenticationConstants.AUDIENCE_CLAIM] = cls.bot_id
        cls._claims_identity.claims[AuthenticationConstants.APP_ID_CLAIM] = cls.skill_id
        cls._claims_identity.claims[
            AuthenticationConstants.SERVICE_URL_CLAIM
        ] = "http://testbot.com/api/messages"
        cls._conversation_reference = ConversationReference(
            conversation=ConversationAccount(id=str(uuid4())),
            service_url="http://testbot.com/api/messages",
        )
        activity = Activity.create_message_activity()
        activity.apply_conversation_reference(cls._conversation_reference)
        skill = BotFrameworkSkill(
            app_id=cls.skill_id,
            id="skill",
            skill_endpoint="http://testbot.com/api/messages",
        )
        cls._options = SkillConversationIdFactoryOptions(
            from_bot_oauth_scope=cls.bot_id,
            from_bot_id=cls.bot_id,
            activity=activity,
            bot_framework_skill=skill,
        )
 async def test_enterprise_channel_validation_wrong_issuer_fails(self):
     credentials = SimpleCredentialProvider(
         "2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F")
     with pytest.raises(Exception) as excinfo:
         await EnterpriseChannelValidation.validate_identity(
             ClaimsIdentity({"iss": "peanut"}, True), credentials)
     assert "Unauthorized" in str(excinfo.value)
    async def _ensure_channel_connector_client_is_created(
            self, service_url: str, claims_identity: ClaimsIdentity):
        # Ensure we have a default ConnectorClient and MSAppCredentials instance for the audience.
        audience = claims_identity.claims.get(
            AuthenticationConstants.AUDIENCE_CLAIM)

        if (not audience
                or AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER !=
                audience):
            # We create a default connector for audiences that are not coming from
            # the default https://api.botframework.com audience.
            # We create a default claim that contains only the desired audience.
            default_connector_claims = {
                AuthenticationConstants.AUDIENCE_CLAIM: audience
            }
            connector_claims_identity = ClaimsIdentity(
                claims=default_connector_claims, is_authenticated=True)

            await self.create_connector_client(service_url,
                                               connector_claims_identity)

        if SkillValidation.is_skill_claim(claims_identity.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
            MicrosoftAppCredentials.trust_service_url(service_url)
Example #7
0
 async def test_enterprise_channel_validation_no_audience_fails(self):
     credentials = SimpleCredentialProvider("", "")
     with pytest.raises(Exception) as excinfo:
         await GovernmentChannelValidation.validate_identity(
             ClaimsIdentity({"iss": "https://api.botframework.com"}, True),
             credentials,
         )
     assert "Unauthorized" in str(excinfo.value)
 def create_claims_identity(self, bot_app_id: str = "") -> ClaimsIdentity:
     return ClaimsIdentity(
         {
             AuthenticationConstants.AUDIENCE_CLAIM: bot_app_id,
             AuthenticationConstants.APP_ID_CLAIM: bot_app_id,
         },
         True,
     )
Example #9
0
    async def test_process_activity_with_identity_token_exchange_invoke_response(
            self):
        mock_credential_provider = unittest.mock.create_autospec(
            CredentialProvider)

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

        identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: "bot_id",
                AuthenticationConstants.APP_ID_CLAIM: "bot_id",
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        inbound_activity = Activity(
            type=ActivityTypes.invoke,
            name=SignInConstants.token_exchange_operation_name,
            service_url="http://tempuri.org/whatever",
            delivery_mode=DeliveryModes.normal,
            conversation=ConversationAccount(id="conversationId"),
            value=TokenExchangeInvokeRequest(
                id="token_exchange_id",
                token="token",
                connection_name="connection_name",
            ),
        )

        async def callback(context: TurnContext):
            activity = Activity(
                type=ActivityTypes.invoke_response,
                value=InvokeResponse(
                    status=200,
                    body=TokenExchangeInvokeResponse(
                        id=context.activity.value.id,
                        connection_name=context.activity.value.connection_name,
                    ),
                ),
            )

            await context.send_activity(activity)

        invoke_response = await adapter.process_activity_with_identity(
            inbound_activity,
            identity,
            callback,
        )

        assert invoke_response
        assert invoke_response.status == 200
        assert invoke_response.body["id"] == inbound_activity.value.id
        assert (invoke_response.body["connectionName"] ==
                inbound_activity.value.connection_name)
Example #10
0
    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

        refs = ConversationReference(service_url=skill_2_service_url)

        await adapter.continue_conversation(
            refs, callback, claims_identity=skills_identity, audience=skill_2_app_id
        )
 async def test_government_channel_validation_no_audience_fails(self):
     credentials = SimpleCredentialProvider(
         "2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F")
     with pytest.raises(Exception) as excinfo:
         await GovernmentChannelValidation.validate_identity(
             ClaimsIdentity({"iss": "https://api.botframework.us"}, True),
             credentials,
         )
     assert "Unauthorized" in str(excinfo.value)
Example #12
0
    async def test_enterprise_channel_validation_succeeds(self):
        credentials = SimpleCredentialProvider("", "")

        await EnterpriseChannelValidation.validate_identity(
            ClaimsIdentity(
                {
                    "iss": "https://api.botframework.com",
                    "aud": credentials.app_id
                }, True),
            credentials,
        )
Example #13
0
    async def test_enterprise_channel_validation_succeeds(self):
        credentials = SimpleCredentialProvider(
            "2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F"
        )

        await EnterpriseChannelValidation.validate_identity(
            ClaimsIdentity(
                {"iss": "https://api.botframework.com", "aud": credentials.app_id}, True
            ),
            credentials,
        )
    async def continue_conversation(
        self,
        reference: ConversationReference,
        callback: Callable,
        bot_id: str = None,
        claims_identity: ClaimsIdentity = None,  # pylint: disable=unused-argument
    ):
        """
        Continues a conversation with a user.

        :param reference: A reference to the conversation to continue
        :type reference: :class:`botbuilder.schema.ConversationReference
        :param callback: The method to call for the resulting bot turn
        :type callback: :class:`typing.Callable`
        :param bot_id: The application Id of the bot. This is the appId returned by the Azure portal registration,
        and is generally found in the `MicrosoftAppId` parameter in `config.py`.
        :type bot_id: :class:`typing.str`
        :param claims_identity: The bot claims identity
        :type claims_identity: :class:`botframework.connector.auth.ClaimsIdentity`

        :raises: It raises an argument null exception.

        :return: A task that represents the work queued to execute.

        .. remarks::
            This is often referred to as the bots *proactive messaging* flow as it lets the bot proactively
            send messages to a conversation or user that are already in a communication.
            Scenarios such as sending notifications or coupons to a user are enabled by this function.
        """
        # TODO: proactive messages
        if not claims_identity:
            if not bot_id:
                raise TypeError("Expected bot_id: str but got None instead")

            claims_identity = ClaimsIdentity(
                claims={
                    AuthenticationConstants.AUDIENCE_CLAIM: bot_id,
                    AuthenticationConstants.APP_ID_CLAIM: bot_id,
                },
                is_authenticated=True,
            )

        context = TurnContext(self, get_continuation_activity(reference))
        context.turn_state[BOT_IDENTITY_KEY] = claims_identity
        context.turn_state["BotCallbackHandler"] = callback
        await self._ensure_channel_connector_client_is_created(
            reference.service_url, claims_identity
        )
        return await self.run_pipeline(context, callback)
Example #15
0
    def create_turn_context(self, activity: Activity) -> TurnContext:
        turn_context = super().create_turn_context(activity)

        claims_identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.VERSION_CLAIM: "2.0",
                AuthenticationConstants.AUDIENCE_CLAIM: str(uuid4()),
                AuthenticationConstants.AUTHORIZED_PARTY: str(uuid4()),
            },
            is_authenticated=True,
        )

        turn_context.turn_state[self.BOT_IDENTITY_KEY] = claims_identity

        return turn_context
Example #16
0
    async def create_connector_client(self,
                                      service_url: str,
                                      identity: ClaimsIdentity = None,
                                      audience: str = None) -> ConnectorClient:
        """
        Creates the connector client
        :param service_url: The service URL
        :param identity: The claims identity
        :param audience:

        :return: An instance of the :class:`ConnectorClient` class
        """

        if not identity:
            # This is different from C# where an exception is raised.  In this case
            # we are creating a ClaimsIdentity to retain compatibility with this
            # method.
            identity = ClaimsIdentity(
                claims={
                    AuthenticationConstants.AUDIENCE_CLAIM:
                    self.settings.app_id,
                    AuthenticationConstants.APP_ID_CLAIM: self.settings.app_id,
                },
                is_authenticated=True,
            )

        # For requests from channel App Id is in Audience claim of JWT token. For emulator it is in AppId claim.
        # For unauthenticated requests we have anonymous claimsIdentity provided auth is disabled.
        # For Activities coming from Emulator AppId claim contains the Bot's AAD AppId.
        bot_app_id = identity.claims.get(
            AuthenticationConstants.AUDIENCE_CLAIM) or identity.claims.get(
                AuthenticationConstants.APP_ID_CLAIM)

        # Anonymous claims and non-skill claims should fall through without modifying the scope.
        credentials = None
        if bot_app_id:
            scope = audience
            if not scope:
                scope = (JwtTokenValidation.get_app_id_from_claims(
                    identity.claims)
                         if SkillValidation.is_skill_claim(identity.claims)
                         else self.__get_botframework_oauth_scope())

            credentials = await self.__get_app_credentials(bot_app_id, scope)

        return self._get_or_create_connector_client(service_url, credentials)
Example #17
0
    async def test_delivery_mode_expect_replies(self):
        mock_credential_provider = unittest.mock.create_autospec(CredentialProvider)

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

        async def callback(context: TurnContext):
            await context.send_activity("activity 1")
            await context.send_activity("activity 2")
            await context.send_activity("activity 3")

        inbound_activity = Activity(
            type=ActivityTypes.message,
            channel_id="emulator",
            service_url="http://tempuri.org/whatever",
            delivery_mode=DeliveryModes.expect_replies,
            text="hello world",
        )

        identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: "bot_id",
                AuthenticationConstants.APP_ID_CLAIM: "bot_id",
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        invoke_response = await adapter.process_activity_with_identity(
            inbound_activity, identity, callback
        )
        assert invoke_response
        assert invoke_response.status == 200
        activities = ExpectedReplies().deserialize(invoke_response.body).activities
        assert len(activities) == 3
        assert activities[0].text == "activity 1"
        assert activities[1].text == "activity 2"
        assert activities[2].text == "activity 3"
        assert (
            adapter.connector_client_mock.conversations.send_to_conversation.call_count
            == 0
        )
    def setUpClass(cls):
        cls.bot_id = str(uuid4())
        cls.skill_id = str(uuid4())

        cls._test_id_factory = ConversationIdFactoryForTest()

        cls._claims_identity = ClaimsIdentity({}, False)

        cls._claims_identity.claims[
            AuthenticationConstants.AUDIENCE_CLAIM] = cls.bot_id
        cls._claims_identity.claims[
            AuthenticationConstants.APP_ID_CLAIM] = cls.skill_id
        cls._claims_identity.claims[
            AuthenticationConstants.
            SERVICE_URL_CLAIM] = "http://testbot.com/api/messages"
        cls._conversation_reference = ConversationReference(
            conversation=ConversationAccount(id=str(uuid4())),
            service_url="http://testbot.com/api/messages",
        )
Example #19
0
    async def _authenticate(self, auth_header: str) -> ClaimsIdentity:
        if not auth_header:
            is_auth_disabled = (
                await self._credential_provider.is_authentication_disabled())
            if is_auth_disabled:
                # In the scenario where Auth is disabled, we still want to have the
                # IsAuthenticated flag set in the ClaimsIdentity. To do this requires
                # adding in an empty claim.
                return ClaimsIdentity({}, True)

            raise PermissionError()

        return await JwtTokenValidation.validate_auth_header(
            auth_header,
            self._credential_provider,
            self._channel_provider,
            "unknown",
            auth_configuration=self._auth_config,
        )
Example #20
0
    async def test_process_activity_creates_correct_creds_and_client(self):
        bot_app_id = "00000000-0000-0000-0000-000000000001"
        identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: bot_app_id,
                AuthenticationConstants.APP_ID_CLAIM: bot_app_id,
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

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

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

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

        settings = BotFrameworkAdapterSettings(bot_app_id)
        sut = BotFrameworkAdapter(settings)
        await sut.process_activity_with_identity(
            Activity(
                channel_id="emulator",
                service_url=service_url,
                text="test",
            ),
            identity,
            callback,
        )
Example #21
0
        async def logic(context: TurnContext):
            if test_case != FlowTestCase.root_bot_only:
                claims_identity = ClaimsIdentity(
                    {
                        AuthenticationConstants.VERSION_CLAIM:
                        "2.0",
                        AuthenticationConstants.AUDIENCE_CLAIM:
                        self.skill_bot_id,
                        AuthenticationConstants.AUTHORIZED_PARTY:
                        self.parent_bot_id,
                    },
                    True,
                )
                context.turn_state[
                    BotAdapter.BOT_IDENTITY_KEY] = claims_identity

                if test_case == FlowTestCase.root_bot_consuming_skill:
                    context.turn_state[
                        SkillHandler.
                        SKILL_CONVERSATION_REFERENCE_KEY] = SkillConversationReference(
                            None, AuthenticationConstants.
                            TO_CHANNEL_FROM_BOT_OAUTH_SCOPE)

                if test_case == FlowTestCase.middle_skill:
                    context.turn_state[
                        SkillHandler.
                        SKILL_CONVERSATION_REFERENCE_KEY] = SkillConversationReference(
                            None, self.parent_bot_id)

            async def capture_eoc(inner_context: TurnContext,
                                  activities: List[Activity], next):  # pylint: disable=unused-argument
                for activity in activities:
                    if activity.type == ActivityTypes.end_of_conversation:
                        self.eoc_sent = activity
                        break
                return await next()

            context.on_send_activities(capture_eoc)

            await DialogExtensions.run_dialog(
                dialog, context, convo_state.create_property("DialogState"))
Example #22
0
    async def _authenticate_request(self, request: Activity,
                                    auth_header: str) -> ClaimsIdentity:
        self.tester.assertIsNotNone(
            request, "authenticate_request() not passed request.")
        self.tester.assertEqual(
            auth_header,
            self.expect_auth_header,
            "authenticateRequest() not passed expected authHeader.",
        )

        if self.fail_auth:
            raise PermissionError(
                "Unauthorized Access. Request is not authorized")

        return ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: self.settings.app_id,
                AuthenticationConstants.APP_ID_CLAIM: self.settings.app_id,
            },
            is_authenticated=True,
        )
Example #23
0
    async def test_delivery_mode_normal(self):
        mock_credential_provider = unittest.mock.create_autospec(CredentialProvider)

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

        async def callback(context: TurnContext):
            await context.send_activity("activity 1")
            await context.send_activity("activity 2")
            await context.send_activity("activity 3")

        inbound_activity = Activity(
            type=ActivityTypes.message,
            channel_id="emulator",
            service_url="http://tempuri.org/whatever",
            delivery_mode=DeliveryModes.normal,
            text="hello world",
            conversation=ConversationAccount(id="conversationId"),
        )

        identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: "bot_id",
                AuthenticationConstants.APP_ID_CLAIM: "bot_id",
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        invoke_response = await adapter.process_activity_with_identity(
            inbound_activity, identity, callback
        )
        assert not invoke_response
        assert (
            adapter.connector_client_mock.conversations.send_to_conversation.call_count
            == 3
        )
Example #24
0
    async def continue_conversation(
            self,
            reference: ConversationReference,
            callback: Callable,
            bot_id: str = None,
            claims_identity: ClaimsIdentity = None,  # pylint: disable=unused-argument
    ):
        """
        Continues a conversation with a user. This is often referred to as the bots "Proactive Messaging"
        flow as its lets the bot proactively send messages to a conversation or user that its already
        communicated with. Scenarios like sending notifications or coupons to a user are enabled by this
        method.
        :param bot_id:
        :param reference:
        :param callback:
        :param claims_identity:
        :return:
        """

        # TODO: proactive messages
        if not claims_identity:
            if not bot_id:
                raise TypeError("Expected bot_id: str but got None instead")

            claims_identity = ClaimsIdentity(
                claims={
                    AuthenticationConstants.AUDIENCE_CLAIM: bot_id,
                    AuthenticationConstants.APP_ID_CLAIM: bot_id,
                },
                is_authenticated=True,
            )

        context = TurnContext(self, get_continuation_activity(reference))
        context.turn_state[BOT_IDENTITY_KEY] = claims_identity
        context.turn_state["BotCallbackHandler"] = callback
        return await self.run_pipeline(context, callback)
Example #25
0
    async def continue_conversation(
        self,
        reference: ConversationReference,
        callback: Callable,
        bot_id: str = None,
        claims_identity: ClaimsIdentity = None,
        audience: str = None,
    ):
        """
        Continues a conversation with a user.

        :param reference: A reference to the conversation to continue
        :type reference: :class:`botbuilder.schema.ConversationReference
        :param callback: The method to call for the resulting bot turn
        :type callback: :class:`typing.Callable`
        :param bot_id: The application Id of the bot. This is the appId returned by the Azure portal registration,
        and is generally found in the `MicrosoftAppId` parameter in `config.py`.
        :type bot_id: :class:`typing.str`
        :param claims_identity: The bot claims identity
        :type claims_identity: :class:`botframework.connector.auth.ClaimsIdentity`
        :param audience:
        :type audience: :class:`typing.str`

        :raises: It raises an argument null exception.

        :return: A task that represents the work queued to execute.

        .. remarks::
            This is often referred to as the bots *proactive messaging* flow as it lets the bot proactively
            send messages to a conversation or user that are already in a communication.
            Scenarios such as sending notifications or coupons to a user are enabled by this function.
        """

        if not reference:
            raise TypeError(
                "Expected reference: ConversationReference but got None instead"
            )
        if not callback:
            raise TypeError("Expected callback: Callable but got None instead")

        # This has to have either a bot_id, in which case a ClaimsIdentity will be created, or
        # a ClaimsIdentity.  In either case, if an audience isn't supplied one will be created.
        if not (bot_id or claims_identity):
            raise TypeError("Expected bot_id or claims_identity")

        if bot_id and not claims_identity:
            claims_identity = ClaimsIdentity(
                claims={
                    AuthenticationConstants.AUDIENCE_CLAIM: bot_id,
                    AuthenticationConstants.APP_ID_CLAIM: bot_id,
                },
                is_authenticated=True,
            )

        if not audience:
            audience = self.__get_botframework_oauth_scope()

        context = TurnContext(self, get_continuation_activity(reference))
        context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = claims_identity
        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)

        client = await self.create_connector_client(reference.service_url,
                                                    claims_identity, audience)
        context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = client

        return await self.run_pipeline(context, callback)
 async def test_enterprise_channel_validation_no_authentication_fails(self):
     with pytest.raises(Exception) as excinfo:
         await EnterpriseChannelValidation.validate_identity(
             ClaimsIdentity({}, False), None)
     assert "Unauthorized" in str(excinfo.value)
Example #27
0
 async def test_enterprise_channel_validation_wrong_issuer_fails(self):
     credentials = SimpleCredentialProvider("", "")
     with pytest.raises(Exception) as excinfo:
         await EnterpriseChannelValidation.validate_identity(
             ClaimsIdentity({"iss": "peanut"}, True), credentials)
     assert "Unauthorized" in str(excinfo.value)
    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

            # 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
        )
Example #29
0
    async def create_conversation(
        self,
        reference: ConversationReference,
        logic: Callable[[TurnContext], Awaitable] = None,
        conversation_parameters: ConversationParameters = None,
        channel_id: str = None,
        service_url: str = None,
        credentials: AppCredentials = None,
    ):
        """
        Starts a new conversation with a user. Used to direct message to a member of a group.

        :param reference: The conversation reference that contains the tenant
        :type reference: :class:`botbuilder.schema.ConversationReference`
        :param logic: The logic to use for the creation of the conversation
        :type logic: :class:`typing.Callable`
        :param conversation_parameters: The information to use to create the conversation
        :type conversation_parameters:
        :param channel_id: The ID for the channel.
        :type channel_id: :class:`typing.str`
        :param service_url: The channel's service URL endpoint.
        :type service_url: :class:`typing.str`
        :param credentials: The application credentials for the bot.
        :type credentials: :class:`botframework.connector.auth.AppCredentials`

        :raises: It raises a generic exception error.

        :return: A task representing the work queued to execute.

        .. remarks::
            To start a conversation, your bot must know its account information and the user's
            account information on that channel.
            Most channels only support initiating a direct message (non-group) conversation.
            The adapter attempts to create a new conversation on the channel, and
            then sends a conversation update activity through its middleware pipeline
            to the the callback method.
            If the conversation is established with the specified users, the ID of the activity
            will contain the ID of the new conversation.
        """
        try:
            if not service_url:
                service_url = reference.service_url
                if not service_url:
                    raise TypeError(
                        "BotFrameworkAdapter.create_conversation(): service_url or reference.service_url is required."
                    )

            if not channel_id:
                channel_id = reference.channel_id
                if not channel_id:
                    raise TypeError(
                        "BotFrameworkAdapter.create_conversation(): channel_id or reference.channel_id is required."
                    )

            parameters = (conversation_parameters if conversation_parameters
                          else ConversationParameters(bot=reference.bot,
                                                      members=[reference.user],
                                                      is_group=False))

            # Mix in the tenant ID if specified. This is required for MS Teams.
            if reference.conversation and reference.conversation.tenant_id:
                # Putting tenant_id in channel_data is a temporary while we wait for the Teams API to be updated
                parameters.channel_data = {
                    "tenant": {
                        "id": reference.conversation.tenant_id
                    }
                }

                # Permanent solution is to put tenant_id in parameters.tenant_id
                parameters.tenant_id = reference.conversation.tenant_id

            # This is different from C# where credentials are required in the method call.
            # Doing this for compatibility.
            app_credentials = (credentials if credentials else await
                               self.__get_app_credentials(
                                   self.settings.app_id,
                                   self.__get_botframework_oauth_scope()))

            # Create conversation
            client = self._get_or_create_connector_client(
                service_url, app_credentials)

            resource_response = await client.conversations.create_conversation(
                parameters)

            event_activity = Activity(
                type=ActivityTypes.event,
                name="CreateConversation",
                channel_id=channel_id,
                service_url=service_url,
                id=resource_response.activity_id
                if resource_response.activity_id else str(uuid.uuid4()),
                conversation=ConversationAccount(
                    id=resource_response.id,
                    tenant_id=parameters.tenant_id,
                ),
                channel_data=parameters.channel_data,
                recipient=parameters.bot,
            )

            context = self._create_context(event_activity)
            context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = client

            claims_identity = ClaimsIdentity(
                claims={
                    AuthenticationConstants.AUDIENCE_CLAIM:
                    app_credentials.microsoft_app_id,
                    AuthenticationConstants.APP_ID_CLAIM:
                    app_credentials.microsoft_app_id,
                    AuthenticationConstants.SERVICE_URL_CLAIM: service_url,
                },
                is_authenticated=True,
            )
            context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = claims_identity

            return await self.run_pipeline(context, logic)

        except Exception as error:
            raise error