Ejemplo n.º 1
0
        async def callback(context: TurnContext):
            context.turn_state[
                SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY
            ] = skill_conversation_reference

            TurnContext.apply_conversation_reference(
                activity, skill_conversation_reference.conversation_reference
            )

            context.activity.id = reply_to_activity_id

            app_id = JwtTokenValidation.get_app_id_from_claims(claims_identity.claims)
            context.activity.caller_id = (
                f"{CallerIdConstants.bot_to_bot_prefix}{app_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)
    def __init__(self, settings: BotFrameworkAdapterSettings):
        super(BotFrameworkAdapter, self).__init__()
        self.settings = settings or BotFrameworkAdapterSettings("", "")
        self.settings.channel_service = self.settings.channel_service or os.environ.get(
            AuthenticationConstants.CHANNEL_SERVICE
        )
        self.settings.open_id_metadata = (
            self.settings.open_id_metadata
            or os.environ.get(AuthenticationConstants.BOT_OPEN_ID_METADATA_KEY)
        )
        self._credentials = MicrosoftAppCredentials(
            self.settings.app_id,
            self.settings.app_password,
            self.settings.channel_auth_tenant,
        )
        self._credential_provider = SimpleCredentialProvider(
            self.settings.app_id, self.settings.app_password
        )
        self._is_emulating_oauth_cards = False

        if self.settings.open_id_metadata:
            ChannelValidation.open_id_metadata_endpoint = self.settings.open_id_metadata
            GovernmentChannelValidation.OPEN_ID_METADATA_ENDPOINT = (
                self.settings.open_id_metadata
            )

        if JwtTokenValidation.is_government(self.settings.channel_service):
            self._credentials.oauth_endpoint = (
                GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
            )
            self._credentials.oauth_scope = (
                GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
            )
Ejemplo n.º 3
0
        async def callback(context: TurnContext):
            nonlocal resource_response
            context.turn_state[
                SkillHandler.
                SKILL_CONVERSATION_REFERENCE_KEY] = skill_conversation_reference

            TurnContext.apply_conversation_reference(
                activity, skill_conversation_reference.conversation_reference)

            context.activity.id = reply_to_activity_id

            app_id = JwtTokenValidation.get_app_id_from_claims(
                claims_identity.claims)
            context.activity.caller_id = (
                f"{CallerIdConstants.bot_to_bot_prefix}{app_id}")

            if activity.type == ActivityTypes.end_of_conversation:
                await self._conversation_id_factory.delete_conversation_reference(
                    conversation_id)
                await self._send_to_bot(activity, context)
            elif activity.type == ActivityTypes.event:
                await self._send_to_bot(activity, context)
            elif activity.type in (ActivityTypes.command,
                                   ActivityTypes.command_result):
                if activity.name.startswith("application/"):
                    # Send to channel and capture the resource response for the SendActivityCall so we can return it.
                    resource_response = await context.send_activity(activity)
                else:
                    await self._send_to_bot(activity, context)
            else:
                # Capture the resource response for the SendActivityCall so we can return it.
                resource_response = await context.send_activity(activity)
 async def validate_claims(self, claims: dict):
     if SkillValidation.is_skill_claim(claims) and self.allowed_callers:
         # Check that the appId claim in the skill request is in the list of skills configured for this bot.
         app_id = JwtTokenValidation.get_app_id_from_claims(claims)
         if app_id not in self.allowed_callers:
             raise ValueError(
                 f'Received a request from an application with an appID of "{ app_id }". To enable requests from this bot, add the id to your configuration file.'
             )
Ejemplo n.º 5
0
    async def test_should_authenticate_anonymous_skill_claim(self):
        sut = TestChannelServiceHandler()
        await sut.handle_reply_to_activity(None, "123", "456", {})

        assert (sut.claims_identity.authentication_type ==
                AuthenticationConstants.ANONYMOUS_AUTH_TYPE)
        assert (JwtTokenValidation.get_app_id_from_claims(
            sut.claims_identity.claims) ==
                AuthenticationConstants.ANONYMOUS_SKILL_APP_ID)
Ejemplo n.º 6
0
    def __create_caller_info(context: TurnContext) -> CallerInfo:
        bot_identity = context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY)
        if bot_identity and SkillValidation.is_skill_claim(bot_identity.claims):
            return CallerInfo(
                caller_service_url=context.activity.service_url,
                scope=JwtTokenValidation.get_app_id_from_claims(bot_identity.claims),
            )

        return None
 def receive(self, auth_header: str, activity: Activity):
     loop = asyncio.new_event_loop()
     try:
         loop.run_until_complete(
             JwtTokenValidation.assert_valid_activity(
                 activity, auth_header, self._credential_provider))
     finally:
         loop.close()
     if self.on_receive is not None:
         self.on_receive(activity)
    async def create_connector_client(
        self, service_url: str, identity: ClaimsIdentity = None
    ) -> ConnectorClient:
        """Allows for mocking of the connector client in unit tests
        :param service_url: The service URL
        :param identity: The claims identity

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

        # Anonymous claims and non-skill claims should fall through without modifying the scope.
        credentials = self._credentials

        if identity:
            bot_app_id_claim = identity.claims.get(
                AuthenticationConstants.AUDIENCE_CLAIM
            ) or identity.claims.get(AuthenticationConstants.APP_ID_CLAIM)

            if bot_app_id_claim and SkillValidation.is_skill_claim(identity.claims):
                scope = JwtTokenValidation.get_app_id_from_claims(identity.claims)

                # Do nothing, if the current credentials and its scope are valid for the skill.
                # i.e. the adapter instance is pre-configured to talk with one skill.
                # Otherwise we will create a new instance of the AppCredentials
                # so self._credentials.oauth_scope isn't overridden.
                if self._credentials.oauth_scope != scope:
                    password = await self._credential_provider.get_app_password(
                        bot_app_id_claim
                    )
                    credentials = MicrosoftAppCredentials(
                        bot_app_id_claim, password, oauth_scope=scope
                    )
                    if (
                        self.settings.channel_provider
                        and self.settings.channel_provider.is_government()
                    ):
                        credentials.oauth_endpoint = (
                            GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
                        )
                        credentials.oauth_scope = (
                            GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
                        )

        client_key = (
            f"{service_url}{credentials.microsoft_app_id if credentials else ''}"
        )
        client = self._connector_client_cache.get(client_key)

        if not client:
            client = ConnectorClient(credentials, base_url=service_url)
            client.config.add_user_agent(USER_AGENT)
            self._connector_client_cache[client_key] = client

        return client
Ejemplo n.º 9
0
    def _get_calling_skill(
            self,
            claims_identity: ClaimsIdentity) -> Union[SkillDefinition, None]:
        app_id = JwtTokenValidation.get_app_id_from_claims(
            claims_identity.claims)

        if not app_id:
            return None

        return next(skill for skill in self._skills_config.SKILLS.values()
                    if skill.app_id == app_id)
Ejemplo n.º 10
0
    def __init__(self, settings: BotFrameworkAdapterSettings):
        """
        Initializes a new instance of the :class:`BotFrameworkAdapter` class.

        :param settings: The settings to initialize the adapter
        :type settings: :class:`BotFrameworkAdapterSettings`
        """
        super(BotFrameworkAdapter, self).__init__()
        self.settings = settings or BotFrameworkAdapterSettings("", "")

        # If settings.certificate_thumbprint & settings.certificate_private_key are provided,
        # use CertificateAppCredentials.
        if self.settings.certificate_thumbprint and settings.certificate_private_key:
            self._credentials = CertificateAppCredentials(
                self.settings.app_id,
                self.settings.certificate_thumbprint,
                self.settings.certificate_private_key,
                self.settings.channel_auth_tenant,
            )
            self._credential_provider = SimpleCredentialProvider(
                self.settings.app_id, "")
        else:
            self._credentials = MicrosoftAppCredentials(
                self.settings.app_id,
                self.settings.app_password,
                self.settings.channel_auth_tenant,
            )
            self._credential_provider = SimpleCredentialProvider(
                self.settings.app_id, self.settings.app_password)

        self._is_emulating_oauth_cards = False

        # If no channel_service or open_id_metadata values were passed in the settings, check the
        # process' Environment Variables for values.
        # These values may be set when a bot is provisioned on Azure and if so are required for
        # the bot to properly work in Public Azure or a National Cloud.
        self.settings.channel_service = self.settings.channel_service or os.environ.get(
            AuthenticationConstants.CHANNEL_SERVICE)
        self.settings.open_id_metadata = (
            self.settings.open_id_metadata or os.environ.get(
                AuthenticationConstants.BOT_OPEN_ID_METADATA_KEY))

        if self.settings.open_id_metadata:
            ChannelValidation.open_id_metadata_endpoint = self.settings.open_id_metadata
            GovernmentChannelValidation.OPEN_ID_METADATA_ENDPOINT = (
                self.settings.open_id_metadata)

        if JwtTokenValidation.is_government(self.settings.channel_service):
            self._credentials.oauth_endpoint = (
                GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL)
            self._credentials.oauth_scope = (
                GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE)

        self._connector_client_cache: Dict[str, ConnectorClient] = {}
Ejemplo n.º 11
0
        async def allow_callers_claims_validator(claims: Dict[str, object]):
            if SkillValidation.is_skill_claim(claims):
                # Check that the appId claim in the skill request is in the list of skills configured for this bot.
                app_id = JwtTokenValidation.get_app_id_from_claims(claims)
                if app_id not in self._allowed_skills:
                    raise PermissionError(
                        f'Received a request from a bot with an app ID of "{app_id}".'
                        f" To enable requests from this caller, add the app ID to your configuration file."
                    )

            return
Ejemplo n.º 12
0
    async def process_activity_with_identity(self, activity: Activity,
                                             identity: ClaimsIdentity,
                                             logic: Callable):
        context = self._create_context(activity)
        context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = identity
        context.turn_state[BotAdapter.BOT_CALLBACK_HANDLER_KEY] = logic

        # To create the correct cache key, provide the OAuthScope when calling CreateConnectorClientAsync.
        # The OAuthScope is also stored on the TurnState to get the correct AppCredentials if fetching a token
        # is required.
        scope = (self.__get_botframework_oauth_scope()
                 if not SkillValidation.is_skill_claim(identity.claims) else
                 JwtTokenValidation.get_app_id_from_claims(identity.claims))
        context.turn_state[BotAdapter.BOT_OAUTH_SCOPE_KEY] = scope

        client = await self.create_connector_client(activity.service_url,
                                                    identity, scope)
        context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = client

        # Fix to assign tenant_id from channelData to Conversation.tenant_id.
        # MS Teams currently sends the tenant ID in channelData and the correct behavior is to expose
        # this value in Activity.Conversation.tenant_id.
        # This code copies the tenant ID from channelData to Activity.Conversation.tenant_id.
        # Once MS Teams sends the tenant_id in the Conversation property, this code can be removed.
        if (Channels.ms_teams == context.activity.channel_id
                and context.activity.conversation is not None
                and not context.activity.conversation.tenant_id
                and context.activity.channel_data):
            teams_channel_data = context.activity.channel_data
            if teams_channel_data.get("tenant", {}).get("id", None):
                context.activity.conversation.tenant_id = str(
                    teams_channel_data["tenant"]["id"])

        await self.run_pipeline(context, logic)

        if activity.type == ActivityTypes.invoke:
            invoke_response = context.turn_state.get(
                BotFrameworkAdapter._INVOKE_RESPONSE_KEY  # pylint: disable=protected-access
            )
            if invoke_response is None:
                return InvokeResponse(status=501)
            return invoke_response.value

        # Return the buffered activities in the response.  In this case, the invoker
        # should deserialize accordingly:
        #    activities = [Activity().deserialize(activity) for activity in response.body]
        if context.activity.delivery_mode == DeliveryModes.buffered_replies:
            serialized_activities = [
                activity.serialize() for activity in context.buffered_replies
            ]
            return InvokeResponse(status=200, body=serialized_activities)

        return None
Ejemplo n.º 13
0
 def _handle_authentication(self, authorization, activity):
     credential_provider = SimpleCredentialProvider(self._app_id, self._app_password)
     loop = asyncio.new_event_loop()
     try:
         loop.run_until_complete(JwtTokenValidation.assert_valid_activity(
             activity, authorization, credential_provider))
         return True
     except Exception as ex:
         logger.info(ex)
         return False
     finally:
         loop.close()
Ejemplo n.º 14
0
 def __handle_authentication(self, activity):
     credential_provider = SimpleCredentialProvider(APP_ID, APP_PASSWORD)
     loop = asyncio.new_event_loop()
     try:
         loop.run_until_complete(JwtTokenValidation.authenticate_request(
             activity, self.headers.get("Authorization"), credential_provider))
         return True
     except Exception as ex:
         self.send_response(401, ex)
         self.end_headers()
         return False
     finally:
         loop.close()
Ejemplo n.º 15
0
    async def create_connector_client(
        self, service_url: str, identity: ClaimsIdentity = None
    ) -> ConnectorClient:
        """Allows for mocking of the connector client in unit tests
        :param service_url: The service URL
        :param identity: The claims identity

        :return: An instance of the :class:`ConnectorClient` class
        """
        if identity:
            bot_app_id_claim = identity.claims.get(
                AuthenticationConstants.AUDIENCE_CLAIM
            ) or identity.claims.get(AuthenticationConstants.APP_ID_CLAIM)

            credentials = None
            if bot_app_id_claim and SkillValidation.is_skill_claim(identity.claims):
                scope = JwtTokenValidation.get_app_id_from_claims(identity.claims)

                password = await self._credential_provider.get_app_password(
                    bot_app_id_claim
                )
                credentials = MicrosoftAppCredentials(
                    bot_app_id_claim, password, oauth_scope=scope
                )
                if (
                    self.settings.channel_provider
                    and self.settings.channel_provider.is_government()
                ):
                    credentials.oauth_endpoint = (
                        GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
                    )
                    credentials.oauth_scope = (
                        GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
                    )
            else:
                credentials = self._credentials
        else:
            credentials = self._credentials

        client_key = (
            f"{service_url}{credentials.microsoft_app_id if credentials else ''}"
        )
        client = self._connector_client_cache.get(client_key)

        if not client:
            client = ConnectorClient(credentials, base_url=service_url)
            client.config.add_user_agent(USER_AGENT)
            self._connector_client_cache[client_key] = client

        return client
    def oauth_api_url(self, context_or_service_url: Union[TurnContext,
                                                          str]) -> str:
        url = None
        if self._is_emulating_oauth_cards:
            url = (context_or_service_url.activity.service_url if isinstance(
                context_or_service_url, object) else context_or_service_url)
        else:
            if self.settings.oauth_endpoint:
                url = self.settings.oauth_endpoint
            else:
                url = (US_GOV_OAUTH_ENDPOINT
                       if JwtTokenValidation.is_government(
                           self.settings.channel_service) else OAUTH_ENDPOINT)

        return url
    def test_get_app_id_from_claims(self):
        v1_claims = {}
        v2_claims = {}

        app_id = str(uuid.uuid4())

        # Empty list
        assert not JwtTokenValidation.get_app_id_from_claims(v1_claims)

        # AppId there but no version (assumes v1)
        v1_claims[AuthenticationConstants.APP_ID_CLAIM] = app_id
        assert JwtTokenValidation.get_app_id_from_claims(v1_claims) == app_id

        # AppId there with v1 version
        v1_claims[AuthenticationConstants.VERSION_CLAIM] = "1.0"
        assert JwtTokenValidation.get_app_id_from_claims(v1_claims) == app_id

        # v2 version but no azp
        v2_claims[AuthenticationConstants.VERSION_CLAIM] = "2.0"
        assert not JwtTokenValidation.get_app_id_from_claims(v2_claims)

        # v2 version but no azp
        v2_claims[AuthenticationConstants.AUTHORIZED_PARTY] = app_id
        assert JwtTokenValidation.get_app_id_from_claims(v2_claims) == app_id
Ejemplo n.º 18
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)
Ejemplo n.º 19
0
 def __handle_authentication(self, activity):
     credential_provider = SimpleCredentialProvider(
         MICROSOFT_CLIENT.get_microsoft_app_id(),
         MICROSOFT_CLIENT.get_microsoft_app_password())
     loop = asyncio.new_event_loop()
     try:
         loop.run_until_complete(
             JwtTokenValidation.authenticate_request(
                 activity, self.headers.get("Authorization"),
                 credential_provider))
         return True
     except Exception as ex:
         self.send_response(401, ex)
         self.end_headers()
         return False
     finally:
         loop.close()
    async def test_channel_authentication_disabled_and_skill_should_be_anonymous(
            self):
        activity = Activity(
            channel_id=Channels.emulator,
            service_url="https://webchat.botframework.com/",
            relates_to=ConversationReference(),
            recipient=ChannelAccount(role=RoleTypes.skill),
        )
        header = ""
        credentials = SimpleCredentialProvider("", "")

        claims_principal = await JwtTokenValidation.authenticate_request(
            activity, header, credentials)

        assert (claims_principal.authentication_type ==
                AuthenticationConstants.ANONYMOUS_AUTH_TYPE)
        assert (JwtTokenValidation.get_app_id_from_claims(
            claims_principal.claims) ==
                AuthenticationConstants.ANONYMOUS_SKILL_APP_ID)
 def test_create_anonymous_skill_claim():
     sut = SkillValidation.create_anonymous_skill_claim()
     assert (JwtTokenValidation.get_app_id_from_claims(
         sut.claims) == AuthenticationConstants.ANONYMOUS_SKILL_APP_ID)
     assert sut.authentication_type == AuthenticationConstants.ANONYMOUS_AUTH_TYPE