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)
예제 #2
0
    async def _authenticate(self, auth_header: str) -> ClaimsIdentity:
        """
        Helper to authenticate the header.

        This code is very similar to the code in JwtTokenValidation.authenticate_request,
        we should move this code somewhere in that library when we refactor auth,
        for now we keep it private to avoid adding more public static functions that we will need to deprecate later.
        """
        if not auth_header:
            is_auth_disabled = (
                await self._credential_provider.is_authentication_disabled())
            if not is_auth_disabled:
                # No auth header. Auth is required. Request is not authorized.
                raise PermissionError()

            # 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.
            # Since ChannelServiceHandler calls are always a skill callback call, we set the skill claim too.
            return SkillValidation.create_anonymous_skill_claim()

        # Validate the header and extract claims.
        return await JwtTokenValidation.validate_auth_header(
            auth_header,
            self._credential_provider,
            self._channel_provider,
            "unknown",
            auth_configuration=self._auth_config,
        )
 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.'
             )
예제 #4
0
    def __is_from_parent_to_skill(turn_context: TurnContext) -> bool:
        if turn_context.turn_state.get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY):
            return False

        claims_identity = turn_context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY)
        return isinstance(
            claims_identity, ClaimsIdentity
        ) and SkillValidation.is_skill_claim(claims_identity.claims)
예제 #5
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
예제 #6
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
    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
예제 #8
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
예제 #9
0
    def test_is_skill_claim_test(self):
        claims = {}
        audience = str(uuid.uuid4())
        app_id = str(uuid.uuid4())

        # Empty list of claims
        assert not SkillValidation.is_skill_claim(claims)

        # No Audience claim
        claims[AuthenticationConstants.VERSION_CLAIM] = "1.0"
        assert not SkillValidation.is_skill_claim(claims)

        # Emulator Audience claim
        claims[
            AuthenticationConstants.
            AUDIENCE_CLAIM] = AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER
        assert not SkillValidation.is_skill_claim(claims)

        # No AppId claim
        del claims[AuthenticationConstants.AUDIENCE_CLAIM]
        claims[AuthenticationConstants.AUDIENCE_CLAIM] = audience
        assert not SkillValidation.is_skill_claim(claims)

        # AppId != Audience
        claims[AuthenticationConstants.APP_ID_CLAIM] = audience
        assert not SkillValidation.is_skill_claim(claims)

        # All checks pass, should be good now
        del claims[AuthenticationConstants.AUDIENCE_CLAIM]
        claims[AuthenticationConstants.AUDIENCE_CLAIM] = app_id
        assert SkillValidation.is_skill_claim(claims)
예제 #10
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
예제 #11
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)
    def __send_eoc_to_parent(turn_context: TurnContext) -> bool:
        claims_identity = turn_context.turn_state.get(
            BotAdapter.BOT_IDENTITY_KEY)
        if isinstance(claims_identity,
                      ClaimsIdentity) and SkillValidation.is_skill_claim(
                          claims_identity.claims):
            # EoC Activities returned by skills are bounced back to the bot by SkillHandler.
            # In those cases we will have a SkillConversationReference instance in state.
            skill_conversation_reference: SkillConversationReference = turn_context.turn_state.get(
                SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY)
            if skill_conversation_reference:
                # If the skillConversationReference.OAuthScope is for one of the supported channels,
                # we are at the root and we should not send an EoC.
                return (skill_conversation_reference.oauth_scope !=
                        AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
                        and skill_conversation_reference.oauth_scope !=
                        GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE)
            return True

        return False
예제 #13
0
 async def _send_state_snapshot_trace(dialog_context: DialogContext):
     """
     Helper to send a trace activity with a memory snapshot of the active dialog DC.
     :param dialog_context:
     :return:
     """
     claims_identity = dialog_context.context.turn_state.get(
         BotAdapter.BOT_IDENTITY_KEY, None)
     trace_label = (
         "Skill State" if isinstance(claims_identity, ClaimsIdentity)
         and SkillValidation.is_skill_claim(claims_identity.claims) else
         "Bot State")
     # send trace of memory
     snapshot = DialogExtensions._get_active_dialog_context(
         dialog_context).state.get_memory_snapshot()
     trace_activity = Activity.create_trace_activity(
         "BotState",
         "https://www.botframework.com/schemas/botState",
         snapshot,
         trace_label,
     )
     await dialog_context.context.send_activity(trace_activity)
예제 #14
0
    def should_send_end_of_conversation_to_parent(
        context: TurnContext, turn_result: DialogTurnResult
    ) -> bool:
        """
        Helper to determine if we should send an EndOfConversation to the parent or not.
        :param context:
        :param turn_result:
        :return:
        """
        if not (
            turn_result.status == DialogTurnStatus.Complete
            or turn_result.status == DialogTurnStatus.Cancelled
        ):
            # The dialog is still going, don't return EoC.
            return False
        claims_identity: ClaimsIdentity = context.turn_state.get(
            BotAdapter.BOT_IDENTITY_KEY, None
        )
        if isinstance(
            claims_identity, ClaimsIdentity
        ) and SkillValidation.is_skill_claim(claims_identity.claims):
            # EoC Activities returned by skills are bounced back to the bot by SkillHandler.
            # In those cases we will have a SkillConversationReference instance in state.
            skill_conversation_reference: SkillConversationReference = context.turn_state.get(
                SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY
            )
            if skill_conversation_reference:
                # If the skill_conversation_reference.OAuthScope is for one of the supported channels, we are at the
                # root and we should not send an EoC.
                return skill_conversation_reference.oauth_scope not in (
                    AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
                    GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
                )

            return True

        return False
예제 #15
0
    async def _send_oauth_card(self,
                               context: TurnContext,
                               prompt: Union[Activity, str] = None):
        if not isinstance(prompt, Activity):
            prompt = MessageFactory.text(prompt or "", None,
                                         InputHints.accepting_input)
        else:
            prompt.input_hint = prompt.input_hint or InputHints.accepting_input

        prompt.attachments = prompt.attachments or []

        if OAuthPrompt._channel_suppports_oauth_card(
                context.activity.channel_id):
            if not any(att.content_type == CardFactory.content_types.oauth_card
                       for att in prompt.attachments):
                adapter: ExtendedUserTokenProvider = context.adapter
                card_action_type = ActionTypes.signin
                sign_in_resource: SignInUrlResponse = await adapter.get_sign_in_resource_from_user_and_credentials(
                    context,
                    self._settings.oath_app_credentials,
                    self._settings.connection_name,
                    context.activity.from_property.id,
                )
                link = sign_in_resource.sign_in_link
                bot_identity: ClaimsIdentity = context.turn_state.get(
                    BotAdapter.BOT_IDENTITY_KEY)

                # use the SignInLink when in speech channel or bot is a skill or
                # an extra OAuthAppCredentials is being passed in
                if ((bot_identity
                     and SkillValidation.is_skill_claim(bot_identity.claims))
                        or not context.activity.service_url.startswith("http")
                        or self._settings.oath_app_credentials):
                    if context.activity.channel_id == Channels.emulator:
                        card_action_type = ActionTypes.open_url
                elif not OAuthPrompt._channel_requires_sign_in_link(
                        context.activity.channel_id):
                    link = None

                json_token_ex_resource = (
                    sign_in_resource.token_exchange_resource.as_dict()
                    if sign_in_resource.token_exchange_resource else None)
                prompt.attachments.append(
                    CardFactory.oauth_card(
                        OAuthCard(
                            text=self._settings.text,
                            connection_name=self._settings.connection_name,
                            buttons=[
                                CardAction(
                                    title=self._settings.title,
                                    text=self._settings.text,
                                    type=card_action_type,
                                    value=link,
                                )
                            ],
                            token_exchange_resource=json_token_ex_resource,
                        )))
        else:
            if not any(
                    att.content_type == CardFactory.content_types.signin_card
                    for att in prompt.attachments):
                if not hasattr(context.adapter, "get_oauth_sign_in_link"):
                    raise Exception(
                        "OAuthPrompt._send_oauth_card(): get_oauth_sign_in_link() not supported by the current adapter"
                    )

                link = await context.adapter.get_oauth_sign_in_link(
                    context,
                    self._settings.connection_name,
                    None,
                    self._settings.oath_app_credentials,
                )
                prompt.attachments.append(
                    CardFactory.signin_card(
                        SigninCard(
                            text=self._settings.text,
                            buttons=[
                                CardAction(
                                    title=self._settings.title,
                                    value=link,
                                    type=ActionTypes.signin,
                                )
                            ],
                        )))

        # Send prompt
        await context.send_activity(prompt)
예제 #16
0
    async def run_dialog(
        dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor
    ):
        dialog_set = DialogSet(accessor)
        dialog_set.add(dialog)

        dialog_context = await dialog_set.create_context(turn_context)

        claims = turn_context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY)
        if isinstance(claims, ClaimsIdentity) and SkillValidation.is_skill_claim(
            claims.claims
        ):
            # The bot is running as a skill.
            if (
                turn_context.activity.type == ActivityTypes.end_of_conversation
                and dialog_context.stack
                and DialogExtensions.__is_eoc_coming_from_parent(turn_context)
            ):
                remote_cancel_text = "Skill was canceled through an EndOfConversation activity from the parent."
                await turn_context.send_trace_activity(
                    f"Extension {Dialog.__name__}.run_dialog", label=remote_cancel_text,
                )

                await dialog_context.cancel_all_dialogs()
            else:
                # Process a reprompt event sent from the parent.
                if (
                    turn_context.activity.type == ActivityTypes.event
                    and turn_context.activity.name == DialogEvents.reprompt_dialog
                    and dialog_context.stack
                ):
                    await dialog_context.reprompt_dialog()
                    return

                # Run the Dialog with the new message Activity and capture the results
                # so we can send end of conversation if needed.
                result = await dialog_context.continue_dialog()
                if result.status == DialogTurnStatus.Empty:
                    start_message_text = f"Starting {dialog.id}"
                    await turn_context.send_trace_activity(
                        f"Extension {Dialog.__name__}.run_dialog",
                        label=start_message_text,
                    )
                    result = await dialog_context.begin_dialog(dialog.id)

                # Send end of conversation if it is completed or cancelled.
                if (
                    result.status == DialogTurnStatus.Complete
                    or result.status == DialogTurnStatus.Cancelled
                ):
                    end_message_text = f"Dialog {dialog.id} has **completed**. Sending EndOfConversation."
                    await turn_context.send_trace_activity(
                        f"Extension {Dialog.__name__}.run_dialog",
                        label=end_message_text,
                        value=result.result,
                    )

                    activity = Activity(
                        type=ActivityTypes.end_of_conversation, value=result.result
                    )
                    await turn_context.send_activity(activity)

        else:
            # The bot is running as a standard bot.
            results = await dialog_context.continue_dialog()
            if results.status == DialogTurnStatus.Empty:
                await dialog_context.begin_dialog(dialog.id)
예제 #17
0
    async def _send_oauth_card(self,
                               context: TurnContext,
                               prompt: Union[Activity, str] = None):
        if not isinstance(prompt, Activity):
            prompt = MessageFactory.text(prompt or "", None,
                                         InputHints.accepting_input)
        else:
            prompt.input_hint = prompt.input_hint or InputHints.accepting_input

        prompt.attachments = prompt.attachments or []

        if OAuthPrompt._channel_suppports_oauth_card(
                context.activity.channel_id):
            if not any(att.content_type == CardFactory.content_types.oauth_card
                       for att in prompt.attachments):
                link = None
                card_action_type = ActionTypes.signin
                bot_identity: ClaimsIdentity = context.turn_state.get(
                    "BotIdentity")

                # check if it's from streaming connection
                if not context.activity.service_url.startswith("http"):
                    if not hasattr(context.adapter, "get_oauth_sign_in_link"):
                        raise Exception(
                            "OAuthPrompt: get_oauth_sign_in_link() not supported by the current adapter"
                        )
                    link = await context.adapter.get_oauth_sign_in_link(
                        context,
                        self._settings.connection_name,
                        None,
                        self._settings.oath_app_credentials,
                    )
                elif bot_identity and SkillValidation.is_skill_claim(
                        bot_identity.claims):
                    link = await context.adapter.get_oauth_sign_in_link(
                        context,
                        self._settings.connection_name,
                        None,
                        self._settings.oath_app_credentials,
                    )
                    card_action_type = ActionTypes.open_url

                prompt.attachments.append(
                    CardFactory.oauth_card(
                        OAuthCard(
                            text=self._settings.text,
                            connection_name=self._settings.connection_name,
                            buttons=[
                                CardAction(
                                    title=self._settings.title,
                                    text=self._settings.text,
                                    type=card_action_type,
                                    value=link,
                                )
                            ],
                        )))
        else:
            if not any(
                    att.content_type == CardFactory.content_types.signin_card
                    for att in prompt.attachments):
                if not hasattr(context.adapter, "get_oauth_sign_in_link"):
                    raise Exception(
                        "OAuthPrompt.send_oauth_card(): get_oauth_sign_in_link() not supported by the current adapter"
                    )

                link = await context.adapter.get_oauth_sign_in_link(
                    context,
                    self._settings.connection_name,
                    None,
                    self._settings.oath_app_credentials,
                )
                prompt.attachments.append(
                    CardFactory.signin_card(
                        SigninCard(
                            text=self._settings.text,
                            buttons=[
                                CardAction(
                                    title=self._settings.title,
                                    value=link,
                                    type=ActionTypes.signin,
                                )
                            ],
                        )))

        # Send prompt
        await context.send_activity(prompt)
 def _is_skill_bot(context: TurnContext) -> bool:
     claims_identity = context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY)
     return isinstance(claims_identity,
                       ClaimsIdentity) and SkillValidation.is_skill_claim(
                           claims_identity.claims)
예제 #19
0
 def test_is_skill_token_test(self, expected: bool, message: str,
                              token: str):
     assert SkillValidation.is_skill_token(token) == expected, message
    async def on_turn(self, context: TurnContext) -> DialogManagerResult:
        """
        Runs dialog system in the context of an ITurnContext.
        :param context: turn context.
        :return:
        """
        # pylint: disable=too-many-statements
        # Lazy initialize RootDialog so it can refer to assets like LG function templates
        if not self._root_dialog_id:
            with self._lock:
                if not self._root_dialog_id:
                    self._root_dialog_id = self.root_dialog.id
                    # self.dialogs = self.root_dialog.telemetry_client
                    self.dialogs.add(self.root_dialog)

        bot_state_set = BotStateSet([])

        # Preload TurnState with DM TurnState.
        for key, val in self.initial_turn_state.items():
            context.turn_state[key] = val

        # register DialogManager with TurnState.
        context.turn_state[DialogManager.__name__] = self
        conversation_state_name = ConversationState.__name__
        if self.conversation_state is None:
            if conversation_state_name not in context.turn_state:
                raise Exception(
                    f"Unable to get an instance of {conversation_state_name} from turn_context."
                )
            self.conversation_state: ConversationState = context.turn_state[
                conversation_state_name
            ]
        else:
            context.turn_state[conversation_state_name] = self.conversation_state

        bot_state_set.add(self.conversation_state)

        user_state_name = UserState.__name__
        if self.user_state is None:
            self.user_state = context.turn_state.get(user_state_name, None)
        else:
            context.turn_state[user_state_name] = self.user_state

        if self.user_state is not None:
            self.user_state: UserState = self.user_state
            bot_state_set.add(self.user_state)

        # create property accessors
        # DateTime(last_access)
        last_access_property = self.conversation_state.create_property(self.last_access)
        last_access: datetime = await last_access_property.get(context, datetime.now)

        # Check for expired conversation
        if self.expire_after is not None and (
            datetime.now() - last_access
        ) >= timedelta(milliseconds=float(self.expire_after)):
            # Clear conversation state
            await self.conversation_state.clear_state(context)

        last_access = datetime.now()
        await last_access_property.set(context, last_access)

        # get dialog stack
        dialogs_property = self.conversation_state.create_property(
            self._dialog_state_property
        )
        dialog_state: DialogState = await dialogs_property.get(context, DialogState)

        # Create DialogContext
        dialog_context = DialogContext(self.dialogs, context, dialog_state)

        # promote initial TurnState into dialog_context.services for contextual services
        for key, service in dialog_context.services.items():
            dialog_context.services[key] = service

        # map TurnState into root dialog context.services
        for key, service in context.turn_state.items():
            dialog_context.services[key] = service

        # get the DialogStateManager configuration
        dialog_state_manager = DialogStateManager(
            dialog_context, self.state_configuration
        )
        await dialog_state_manager.load_all_scopes()
        dialog_context.context.turn_state[
            dialog_state_manager.__class__.__name__
        ] = dialog_state_manager

        turn_result: DialogTurnResult = None

        # Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn.

        # NOTE: We loop around this block because each pass through we either complete the turn and break out of the
        # loop or we have had an exception AND there was an OnError action which captured the error.  We need to
        # continue the turn based on the actions the OnError handler introduced.
        end_of_turn = False
        while not end_of_turn:
            try:
                claims_identity: ClaimsIdentity = context.turn_state.get(
                    BotAdapter.BOT_IDENTITY_KEY, None
                )
                if isinstance(
                    claims_identity, ClaimsIdentity
                ) and SkillValidation.is_skill_claim(claims_identity.claims):
                    # The bot is running as a skill.
                    turn_result = await self.handle_skill_on_turn(dialog_context)
                else:
                    # The bot is running as root bot.
                    turn_result = await self.handle_bot_on_turn(dialog_context)

                # turn successfully completed, break the loop
                end_of_turn = True
            except Exception as err:
                # fire error event, bubbling from the leaf.
                handled = await dialog_context.emit_event(
                    DialogEvents.error, err, bubble=True, from_leaf=True
                )

                if not handled:
                    # error was NOT handled, throw the exception and end the turn. (This will trigger the
                    # Adapter.OnError handler and end the entire dialog stack)
                    raise

        # save all state scopes to their respective botState locations.
        await dialog_state_manager.save_all_changes()

        # save BotState changes
        await bot_state_set.save_all_changes(dialog_context.context, False)

        return DialogManagerResult(turn_result=turn_result)
 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