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)
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.' )
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)
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
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
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
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)
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
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
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)
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
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)
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)
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)
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