From 7a7610302e3479e57aea228b01c5df0f464e5e33 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 24 Sep 2026 15:54:24 -0700 Subject: [PATCH 1/6] Making BasicCard obsolete --- .../microsoft_agents/activity/__init__.py | 2 - .../microsoft_agents/activity/basic_card.py | 137 ------------------ .../core/authorization/claims_identity.py | 28 ---- .../app_style/echo_proactive_agent.py | 5 - tests/activity/test_card_builders.py | 12 +- 5 files changed, 1 insertion(+), 183 deletions(-) delete mode 100644 libraries/microsoft-agents-activity/microsoft_agents/activity/basic_card.py diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py index 02a4980ca..9dab9a3e6 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py @@ -17,7 +17,6 @@ from .attachment_info import AttachmentInfo from .attachment_view import AttachmentView from .audio_card import AudioCard -from .basic_card import BasicCard from .card import Card from .card_action import CardAction from .card_image import CardImage @@ -127,7 +126,6 @@ "AttachmentInfo", "AttachmentView", "AudioCard", - "BasicCard", "Card", "CardAction", "CardImage", diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/basic_card.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/basic_card.py deleted file mode 100644 index 24565fa80..000000000 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/basic_card.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from typing import overload - -from typing_extensions import deprecated - -from .action_types import ActionTypes -from .agents_model import AgentsModel -from .card_image import CardImage -from .card_action import CardAction -from ._model_utils import pick_model, SkipNone -from ._type_aliases import NonEmptyString - - -@deprecated( - "BasicCard is not an Activity Protocol card type (it has no content type) " - "and will be removed in a future release." -) -class BasicCard(AgentsModel): - """A basic card. - - .. deprecated:: - BasicCard is not an Activity Protocol card type (it has no content type) - and will be removed in a future release. - - :param title: Title of the card - :type title: str - :param subtitle: Subtitle of the card - :type subtitle: str - :param text: Text for the card - :type text: str - :param images: Array of images for the card - :type images: list[~microsoft_agents.activity.CardImage] - :param buttons: Set of actions applicable to the current card - :type buttons: list[~microsoft_agents.activity.CardAction] - :param tap: This action will be activated when user taps on the card - itself - :type tap: ~microsoft_agents.activity.CardAction - """ - - title: NonEmptyString = None - subtitle: NonEmptyString = None - text: str = None - images: list[CardImage] = None - buttons: list[CardAction] = None - tap: CardAction = None - - @overload - def add_image(self, image: CardImage) -> "BasicCard": ... - - @overload - def add_image( - self, *, url: NonEmptyString, alt: NonEmptyString | None = None - ) -> "BasicCard": ... - - def add_image( - self, - image: CardImage | None = None, - *, - url: NonEmptyString | None = None, - alt: NonEmptyString | None = None, - ) -> "BasicCard": - """ - Adds an image and returns this card. - - :param image: The image to add. - :param url: The URL of the image, used when no image instance is provided. - :param alt: The alternate text for the image built from a URL. - :returns: This card, to allow for method chaining. - """ - if image is None: - if url is None: - raise ValueError( - "Either provide a CardImage instance or the url parameter." - ) - image = pick_model(CardImage, url=url, alt=SkipNone(alt)) - - self.images = self.images or [] - self.images.append(image) - return self - - @overload - def add_button(self, button: CardAction) -> "BasicCard": ... - - @overload - def add_button( - self, - *, - title: NonEmptyString, - type: NonEmptyString = ActionTypes.im_back, - value: object | None = None, - ) -> "BasicCard": ... - - def add_button( - self, - button: CardAction | None = None, - *, - title: NonEmptyString | None = None, - type: NonEmptyString = ActionTypes.im_back, - value: object | None = None, - ) -> "BasicCard": - """ - Adds a button and returns this card. - - :param button: The button to add. - :param title: The title of the button, used when no button instance is provided. - :param type: The action type of the button built from a title. - :param value: The value of the button built from a title. Defaults to the title. - :returns: This card, to allow for method chaining. - """ - if button is None: - if title is None: - raise ValueError( - "Either provide a CardAction instance or the title parameter." - ) - button = CardAction( - type=type, title=title, value=value if value is not None else title - ) - - self.buttons = self.buttons or [] - self.buttons.append(button) - return self - - def add_buttons(self, *buttons: CardAction) -> "BasicCard": - """ - Adds one or more buttons and returns this card. - - :param buttons: The buttons to add. - :returns: This card, to allow for method chaining. - """ - if not buttons: - return self - - self.buttons = self.buttons or [] - self.buttons.extend(buttons) - return self diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py index 65b529d94..9004b11f9 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py @@ -21,30 +21,21 @@ class ClaimsIdentity: def __init__( self, claims: dict[str, Any] | None = None, - is_authenticated: bool | None = None, authentication_type: str | None = None, security_token: str | None = None, ): """Creates a new instance of the ClaimsIdentity class. :param claims: A dictionary of claims associated with the identity. - :param is_authenticated: A boolean indicating whether the identity is authenticated. (Deprecated) :param authentication_type: A string representing the type of authentication used. :param security_token: The security token associated with the identity. """ if claims is None: claims = {} self.claims = claims - if is_authenticated is not None: - warnings.warn( - "The 'is_authenticated' parameter is deprecated and will be removed in future versions.", - DeprecationWarning, - stacklevel=2, - ) self.authentication_type = authentication_type self.security_token = security_token - self._is_authenticated = is_authenticated def get_claim_value(self, claim_type: str) -> Any: """Gets the value of a specific claim type from the claims dictionary. @@ -62,25 +53,6 @@ def allow_anonymous(self) -> bool: or self.authentication_type.lower() == "anonymous" ) and not self.claims - @property - def is_authenticated(self) -> bool: - """Returns True if the identity is authenticated, otherwise False.""" - warnings.warn( - "The 'is_authenticated' property is deprecated and will be removed in future versions.", - DeprecationWarning, - stacklevel=2, - ) - return bool(self.claims) - - @is_authenticated.setter - def is_authenticated(self, value: bool) -> None: - """(Deprecated). This is now a no-op.""" - warnings.warn( - "The 'is_authenticated' property is deprecated and will be removed in future versions.", - DeprecationWarning, - stacklevel=2, - ) - def get_app_id(self) -> str | None: """ Gets the AppId from the current ClaimsIdentity. diff --git a/test_samples/app_style/echo_proactive_agent.py b/test_samples/app_style/echo_proactive_agent.py index 9298fcdbd..3f5def525 100644 --- a/test_samples/app_style/echo_proactive_agent.py +++ b/test_samples/app_style/echo_proactive_agent.py @@ -62,7 +62,6 @@ class ConversationReferenceRecord(StoreItem): """Persistent envelope for a conversation reference and associated identity.""" claims: dict[str, str] - is_authenticated: bool authentication_type: Optional[str] reference: ConversationReference @@ -80,7 +79,6 @@ def from_context(cls, context: TurnContext) -> "ConversationReferenceRecord": reference = context.activity.get_conversation_reference() return cls( claims=dict(identity.claims), - is_authenticated=identity.is_authenticated, authentication_type=identity.authentication_type, reference=reference, ) @@ -88,14 +86,12 @@ def from_context(cls, context: TurnContext) -> "ConversationReferenceRecord": def to_identity(self) -> ClaimsIdentity: return ClaimsIdentity( claims=dict(self.claims), - is_authenticated=self.is_authenticated, authentication_type=self.authentication_type, ) def store_item_to_json(self) -> Dict[str, Any]: return { "claims": dict(self.claims), - "is_authenticated": self.is_authenticated, "authentication_type": self.authentication_type, "reference": self.reference.model_dump(mode="json"), } @@ -113,7 +109,6 @@ def from_json_to_store_item( ) return ConversationReferenceRecord( claims=json_data.get("claims", {}), - is_authenticated=json_data.get("is_authenticated", False), authentication_type=json_data.get("authentication_type"), reference=reference, ) diff --git a/tests/activity/test_card_builders.py b/tests/activity/test_card_builders.py index 69b93522a..93f9e1a9b 100644 --- a/tests/activity/test_card_builders.py +++ b/tests/activity/test_card_builders.py @@ -6,7 +6,6 @@ AdaptiveCardCard, AnimationCard, AudioCard, - BasicCard, CardAction, CardImage, ContentTypes, @@ -54,21 +53,12 @@ def test_hero_card_fluent_builders(self): class TestThumbnailAndBasicCardBuilders: @pytest.mark.filterwarnings("ignore::DeprecationWarning") - def test_thumbnail_and_basic_card_builders(self): + def test_thumbnail_builder(self): thumb = ThumbnailCard(title="t").add_image(url="u").add_button(title="b") assert thumb.title == "t" assert len(thumb.images) == 1 assert len(thumb.buttons) == 1 - basic = ( - BasicCard(text="x") - .add_image(CardImage(url="u")) - .add_button(CardAction(type=ActionTypes.im_back, title="b")) - ) - assert basic.text == "x" - assert len(basic.images) == 1 - assert len(basic.buttons) == 1 - class TestMediaCardBuilders: @pytest.mark.filterwarnings("ignore::DeprecationWarning") From 1dcbab6ca5a9f1482e45959b3f7aa18d6aaaee3d Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 24 Sep 2026 15:57:36 -0700 Subject: [PATCH 2/6] Making MediaCard obsolete --- .../microsoft_agents/activity/__init__.py | 1 - .../microsoft_agents/activity/media_card.py | 117 ------------------ 2 files changed, 118 deletions(-) delete mode 100644 libraries/microsoft-agents-activity/microsoft_agents/activity/media_card.py diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py index 9dab9a3e6..58195d4b4 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py @@ -56,7 +56,6 @@ from .hero_card import HeroCard from .inner_http_error import InnerHttpError from .invoke_response import InvokeResponse -from .media_card import MediaCard from .media_event_value import MediaEventValue from .media_url import MediaUrl from .message_reaction import MessageReaction diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/media_card.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/media_card.py deleted file mode 100644 index 284982878..000000000 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/media_card.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from typing import overload - -from typing_extensions import deprecated - -from .thumbnail_url import ThumbnailUrl -from .media_url import MediaUrl -from .card_action import CardAction -from .agents_model import AgentsModel -from ._model_utils import pick_model, SkipNone -from ._type_aliases import NonEmptyString - - -@deprecated( - "MediaCard is a structural base without an Activity Protocol content type. " - "Use AnimationCard, AudioCard, or VideoCard instead. " - "Will be removed in a future release." -) -class MediaCard(AgentsModel): - """Media card. - - .. deprecated:: - MediaCard is a structural base without an Activity Protocol content type. - Use AnimationCard, AudioCard, or VideoCard instead. Will be removed in a - future release. - - :param title: Title of this card - :type title: str - :param subtitle: Subtitle of this card - :type subtitle: str - :param text: Text of this card - :type text: str - :param image: Thumbnail placeholder - :type image: ~microsoft_agents.activity.ThumbnailUrl - :param media: Media URLs for this card. When this field contains more than - one URL, each URL is an alternative format of the same content. - :type media: list[~microsoft_agents.activity.MediaUrl] - :param buttons: Actions on this card - :type buttons: list[~microsoft_agents.activity.CardAction] - :param shareable: This content may be shared with others (default:true) - :type shareable: bool - :param autoloop: Should the client loop playback at end of content - (default:true) - :type autoloop: bool - :param autostart: Should the client automatically start playback of media - in this card (default:true) - :type autostart: bool - :param aspect: Aspect ratio of thumbnail/media placeholder. Allowed values - are "16:9" and "4:3" - :type aspect: str - :param duration: Describes the length of the media content without - requiring a receiver to open the content. Formatted as an ISO 8601 - Duration field. - :type duration: str - :param value: Supplementary parameter for this card - :type value: object - """ - - title: NonEmptyString = None - subtitle: NonEmptyString = None - text: str = None - image: ThumbnailUrl = None - media: list[MediaUrl] = None - buttons: list[CardAction] = None - shareable: bool = None - autoloop: bool = None - autostart: bool = None - aspect: NonEmptyString = None - duration: NonEmptyString = None - value: object = None - - @overload - def add_media(self, media: MediaUrl) -> "MediaCard": ... - - @overload - def add_media( - self, *, url: NonEmptyString, profile: NonEmptyString | None = None - ) -> "MediaCard": ... - - def add_media( - self, - media: MediaUrl | None = None, - *, - url: NonEmptyString | None = None, - profile: NonEmptyString | None = None, - ) -> "MediaCard": - """ - Adds a media URL and returns this card. - - :param media: The media URL to add. - :param url: The URL of the media, used when no media instance is provided. - :param profile: The profile of the media built from a URL. - :returns: This card, to allow for method chaining. - """ - if media is None: - if url is None: - raise ValueError( - "Either provide a MediaUrl instance or the url parameter." - ) - media = pick_model(MediaUrl, url=url, profile=SkipNone(profile)) - - self.media = self.media or [] - self.media.append(media) - return self - - def add_button(self, button: CardAction) -> "MediaCard": - """ - Adds a button and returns this card. - - :param button: The button to add. - :returns: This card, to allow for method chaining. - """ - self.buttons = self.buttons or [] - self.buttons.append(button) - return self From 3ccf76979c34d315f31b1847326daa591af69b46 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 24 Sep 2026 16:10:33 -0700 Subject: [PATCH 3/6] Fixing tests --- .../hosting/core/app/agent_application.py | 33 ------------------- .../core/authorization/claims_identity.py | 2 +- .../hosting/core/card_factory.py | 3 -- .../connector/client/user_token_client.py | 16 ++------- 4 files changed, 3 insertions(+), 51 deletions(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py index 9163d2511..da866ea35 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py @@ -899,39 +899,6 @@ def _remove_mentions(self, context: TurnContext): ): context.activity.text = context.remove_recipient_mention(context.activity) - @staticmethod - @deprecated( - "Use `load_configuration_from_env` from `microsoft_agents.activity` instead." - ) - def parse_env_vars_configuration(vars: dict[str, Any]) -> dict: - """ - Parses environment variables and returns a dictionary with the relevant configuration. - - :param vars: Dictionary of environment variable names and values. - :type vars: dict[str, Any] - :return: Parsed configuration dictionary with nested structure. - :rtype: dict - """ - result = {} - for key, value in vars.items(): - levels = key.split("__") - current_level = result - last_level = None - for next_level in levels: - if next_level not in current_level: - current_level[next_level] = {} - last_level = current_level - current_level = current_level[next_level] - logger.debug(f"Using environment variable '{key}'") - last_level[levels[-1]] = value - - return { - "AGENT_APPLICATION": result["AGENT_APPLICATION"], - "COPILOT_STUDIO_AGENT": result["COPILOT_STUDIO_AGENT"], - "CONNECTIONS": result["CONNECTIONS"], - "CONNECTIONS_MAP": result["CONNECTIONS_MAP"], - } - async def _initialize_state(self, context: TurnContext) -> StateT: if self._turn_state_factory: logger.debug("Using custom turn state factory") diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py index 9004b11f9..a6dbd9181 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py @@ -16,7 +16,7 @@ class ClaimsIdentity: claims: dict[str, Any] authentication_type: str | None - security_token: str | None # deprecated, will be removed in future versions + security_token: str | None def __init__( self, diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/card_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/card_factory.py index 86434d315..834fb35f9 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/card_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/card_factory.py @@ -160,6 +160,3 @@ def video_card(card: VideoCard) -> Attachment: ) return card.to_attachment() - - # Deprecated alias; use microsoft_agents.activity.ContentTypes instead. - content_types: type[ContentTypes] = ContentTypes diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py index 0a00543c0..61e7787f5 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py @@ -36,7 +36,7 @@ def __init__( endpoint: str, token: str, *, - app_id: str | None = None, + app_id: str, session: ClientSession | None = None, ): """ @@ -49,10 +49,7 @@ def __init__( """ self._app_id = app_id if not self._app_id: - logger.warning( - "App ID is not provided. Some operations may not work without an App ID." - " In the future, creation of UserTokenClient without an App ID will be deprecated." - ) + raise ValueError("App ID cannot be empty") if not endpoint.endswith("/"): endpoint += "/" @@ -158,11 +155,6 @@ async def get_sign_in_resource( :param final_redirect: The final redirect URL after sign-in. :return: The sign-in resource. """ - if not self._app_id: - raise ValueError( - "App ID must be provided in the creation of UserTokenClient to get sign-in resource." - ) - state = UserTokenClient._create_token_exchange_state( self._app_id, connection_name, activity ) @@ -275,10 +267,6 @@ async def get_token_or_sign_in_resource( raise ValueError( "Activity must have a channel_id to get token or sign-in resource." ) - if not self._app_id: - raise ValueError( - "App ID must be provided in the creation of UserTokenClient to get the token or sign-in resource." - ) state = UserTokenClient._create_token_exchange_state( self._app_id, connection_name, activity From b5b29399fadd595a6c36b9b4842f7f2703897116 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 24 Sep 2026 16:13:02 -0700 Subject: [PATCH 4/6] Adding tests --- .../adapters/mock_testing_adapter.py | 2 +- tests/activity/test_card_builders.py | 7 +-- .../test_msal_connection_manager.py | 30 ++++------ .../test_jwt_authorization_middleware.py | 4 +- .../app/proactive/test_conversation.py | 12 ++-- .../proactive/test_conversation_builder.py | 14 ++--- .../test_create_conversation_options.py | 2 +- .../app/proactive/test_proactive.py | 4 +- .../authorization/test_authorize_request.py | 4 +- .../authorization/test_claims_identity.py | 56 ++----------------- .../authorization/test_jwt_token_validator.py | 46 +++++++-------- .../connector/test_user_token_client.py | 24 +++----- .../test_transcript_logger_middleware.py | 8 +-- .../telemetry/test_proactive_spans.py | 2 +- .../test_channel_service_adapter.py | 3 - tests/hosting_core/test_connection_manager.py | 28 +++++----- tests/hosting_dialogs/helpers.py | 4 +- tests/hosting_dialogs/test_dialog_manager.py | 2 +- .../test_jwt_authorization_middleware.py | 4 +- tests/testing_package/test_test_adapter.py | 6 +- 20 files changed, 93 insertions(+), 169 deletions(-) diff --git a/tests/_common/testing_objects/adapters/mock_testing_adapter.py b/tests/_common/testing_objects/adapters/mock_testing_adapter.py index eafc4a490..444084b83 100644 --- a/tests/_common/testing_objects/adapters/mock_testing_adapter.py +++ b/tests/_common/testing_objects/adapters/mock_testing_adapter.py @@ -76,7 +76,7 @@ def __init__( self.active_queue = deque() # Identity for the adapter - self.claims_identity = ClaimsIdentity({}, True) + self.claims_identity = ClaimsIdentity({}, authentication_type="Bearer") @property def enable_trace(self) -> bool: diff --git a/tests/activity/test_card_builders.py b/tests/activity/test_card_builders.py index 93f9e1a9b..e078c500a 100644 --- a/tests/activity/test_card_builders.py +++ b/tests/activity/test_card_builders.py @@ -11,7 +11,6 @@ ContentTypes, Fact, HeroCard, - MediaCard, MediaUrl, ReceiptCard, ReceiptItem, @@ -60,7 +59,7 @@ def test_thumbnail_builder(self): assert len(thumb.buttons) == 1 -class TestMediaCardBuilders: +class TestMediaSpecificCardBuilders: @pytest.mark.filterwarnings("ignore::DeprecationWarning") def test_media_card_builders_add_media_and_buttons(self): animation = ( @@ -79,10 +78,6 @@ def test_media_card_builders_add_media_and_buttons(self): video = VideoCard().add_media(url="https://v", profile="profile") assert video.media[0].profile == "profile" - media = MediaCard(text="m").add_media(url="https://x") - assert media.text == "m" - assert len(media.media) == 1 - class TestReceiptCardBuilders: def test_receipt_card_builders(self): diff --git a/tests/authentication_msal/test_msal_connection_manager.py b/tests/authentication_msal/test_msal_connection_manager.py index 56e9d980c..23bbc023d 100644 --- a/tests/authentication_msal/test_msal_connection_manager.py +++ b/tests/authentication_msal/test_msal_connection_manager.py @@ -54,10 +54,10 @@ def test_init_from_config(self): [None, ""], [None, None], [None, "agentic"], - [ClaimsIdentity(claims={}, is_authenticated=False), None], - [ClaimsIdentity(claims={}, is_authenticated=False), ""], - [ClaimsIdentity(claims={}, is_authenticated=False), "https://example.com"], - [ClaimsIdentity(claims={"aud": "api://misc"}, is_authenticated=False), ""], + [ClaimsIdentity(claims={}), None], + [ClaimsIdentity(claims={}), ""], + [ClaimsIdentity(claims={}), "https://example.com"], + [ClaimsIdentity(claims={"aud": "api://misc"}), ""], ], ) def test_get_token_provider_errors(self, claims_identity, service_url): @@ -68,9 +68,7 @@ def test_get_token_provider_errors(self, claims_identity, service_url): def test_get_token_provider_no_map(self, config): del config["CONNECTIONSMAP"] connection_manager = MsalConnectionManager(**config) - claims_identity = ClaimsIdentity( - claims={"aud": "api://misc"}, is_authenticated=True - ) + claims_identity = ClaimsIdentity(claims={"aud": "api://misc"}) token_provider = connection_manager.get_token_provider( claims_identity, "https://example.com" ) @@ -78,9 +76,7 @@ def test_get_token_provider_no_map(self, config): def test_get_token_provider_aud_match(self, config): connection_manager = MsalConnectionManager(**config) - claims_identity = ClaimsIdentity( - claims={"aud": "api://misc"}, is_authenticated=True - ) + claims_identity = ClaimsIdentity(claims={"aud": "api://misc"}) token_provider = connection_manager.get_token_provider( claims_identity, "https://example.com" ) @@ -88,9 +84,7 @@ def test_get_token_provider_aud_match(self, config): def test_get_token_provider_aud_and_service_url_match(self, config): connection_manager = MsalConnectionManager(**config) - claims_identity = ClaimsIdentity( - claims={"aud": "api://service"}, is_authenticated=True - ) + claims_identity = ClaimsIdentity(claims={"aud": "api://service"}) token_provider = connection_manager.get_token_provider( claims_identity, "https://service.com/api" ) @@ -98,9 +92,7 @@ def test_get_token_provider_aud_and_service_url_match(self, config): def test_get_token_provider_service_url_wildcard_star(self, config): connection_manager = MsalConnectionManager(**config) - claims_identity = ClaimsIdentity( - claims={"aud": "api://misc"}, is_authenticated=False - ) + claims_identity = ClaimsIdentity(claims={"aud": "api://misc"}) token_provider = connection_manager.get_token_provider( claims_identity, "https://service.com/api" ) @@ -108,9 +100,7 @@ def test_get_token_provider_service_url_wildcard_star(self, config): def test_get_token_provider_service_url_wildcard_empty(self, config): connection_manager = MsalConnectionManager(**config) - claims_identity = ClaimsIdentity( - claims={"aud": "api://misc_other"}, is_authenticated=False - ) + claims_identity = ClaimsIdentity(claims={"aud": "api://misc_other"}) token_provider = connection_manager.get_token_provider( claims_identity, "https://service.com/api" ) @@ -129,7 +119,7 @@ def test_get_token_provider_service_url_match( self, config, service_url, expected_connection ): connection_manager = MsalConnectionManager(**config) - claims_identity = ClaimsIdentity(claims={}, is_authenticated=False) + claims_identity = ClaimsIdentity(claims={}) token_provider = connection_manager.get_token_provider( claims_identity, service_url ) diff --git a/tests/hosting_aiohttp/test_jwt_authorization_middleware.py b/tests/hosting_aiohttp/test_jwt_authorization_middleware.py index 6cfb6d002..e351128fe 100644 --- a/tests/hosting_aiohttp/test_jwt_authorization_middleware.py +++ b/tests/hosting_aiohttp/test_jwt_authorization_middleware.py @@ -46,7 +46,7 @@ def _response_json(response): @pytest.mark.asyncio async def test_aiohttp_middleware_stores_claims_and_calls_handler(): auth_config = AgentAuthConfiguration() - claims = ClaimsIdentity({"aud": "app-id"}, True) + claims = ClaimsIdentity({"aud": "app-id"}, authentication_type="Bearer") async def handler(request): return web.json_response({"aud": request["claims_identity"].claims["aud"]}) @@ -95,7 +95,7 @@ async def test_aiohttp_middleware_converts_http_response(): @pytest.mark.asyncio async def test_aiohttp_decorator_uses_authorization_helper(): auth_config = AgentAuthConfiguration() - claims = ClaimsIdentity({"aud": "decorator-app"}, True) + claims = ClaimsIdentity({"aud": "decorator-app"}, authentication_type="Bearer") @jwt_authorization_decorator async def handler(request): diff --git a/tests/hosting_core/app/proactive/test_conversation.py b/tests/hosting_core/app/proactive/test_conversation.py index 2694fc469..75a7d9117 100644 --- a/tests/hosting_core/app/proactive/test_conversation.py +++ b/tests/hosting_core/app/proactive/test_conversation.py @@ -49,7 +49,6 @@ def test_init_with_dict_filters_unknown_claims(self): def test_init_with_claims_identity_filters_correctly(self): identity = ClaimsIdentity( claims={"aud": "app-id", "tid": "tenant", "unrelated": "drop"}, - is_authenticated=True, ) conv = Conversation(claims=identity, conversation_reference=_make_reference()) assert conv.claims["aud"] == "app-id" @@ -69,9 +68,7 @@ def test_init_empty_claims_dict(self): class TestConversationFromTurnContext: def test_from_turn_context_extracts_reference_and_identity(self): ref = _make_reference("ctx-conv") - identity = ClaimsIdentity( - claims={"aud": "app-id", "tid": "t"}, is_authenticated=True - ) + identity = ClaimsIdentity(claims={"aud": "app-id", "tid": "t"}) ctx = MagicMock() ctx.activity.get_conversation_reference.return_value = ref @@ -99,20 +96,19 @@ class TestConversationClaimsHelpers: def test_claims_from_identity_keeps_allowed_keys(self): identity = ClaimsIdentity( claims={"aud": "a", "tid": "t", "ver": "2.0", "other": "drop"}, - is_authenticated=True, ) result = Conversation.claims_from_identity(identity) assert result == {"aud": "a", "tid": "t", "ver": "2.0"} def test_claims_from_identity_empty_claims(self): - identity = ClaimsIdentity(claims={}, is_authenticated=True) + identity = ClaimsIdentity(claims={}) result = Conversation.claims_from_identity(identity) assert result == {} - def test_identity_from_claims_is_authenticated(self): + def test_identity_from_claims_disallows_anonymous(self): claims = {"aud": "app-id", "tid": "tenant"} identity = Conversation.identity_from_claims(claims) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False def test_identity_from_empty_claims_allows_anonymous(self): identity = Conversation.identity_from_claims({}) diff --git a/tests/hosting_core/app/proactive/test_conversation_builder.py b/tests/hosting_core/app/proactive/test_conversation_builder.py index 2f3a8dd46..e8cc336ac 100644 --- a/tests/hosting_core/app/proactive/test_conversation_builder.py +++ b/tests/hosting_core/app/proactive/test_conversation_builder.py @@ -83,14 +83,13 @@ def test_create_returns_builder_instance(self): class TestConversationBuilderCreateFromIdentity: def test_create_from_identity_sets_channel_id(self): - identity = ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True) + identity = ClaimsIdentity(claims={"aud": "app-id"}) builder = ConversationBuilder.create_from_identity(identity, "msteams") assert builder._channel_id == "msteams" def test_create_from_identity_filters_claims(self): identity = ClaimsIdentity( claims={"aud": "app-id", "tid": "tenant", "unrelated": "drop"}, - is_authenticated=True, ) builder = ConversationBuilder.create_from_identity(identity, "msteams") assert builder._claims["aud"] == "app-id" @@ -98,29 +97,29 @@ def test_create_from_identity_filters_claims(self): assert "unrelated" not in builder._claims def test_create_from_identity_teams_prefixes_agent(self): - identity = ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True) + identity = ClaimsIdentity(claims={"aud": "app-id"}) builder = ConversationBuilder.create_from_identity(identity, "msteams") assert builder._agent_id == "28:app-id" def test_create_from_identity_non_teams_no_prefix(self): - identity = ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True) + identity = ClaimsIdentity(claims={"aud": "app-id"}) builder = ConversationBuilder.create_from_identity(identity, "directline") assert builder._agent_id == "app-id" def test_create_from_identity_no_app_id_no_agent(self): - identity = ClaimsIdentity(claims={}, is_authenticated=True) + identity = ClaimsIdentity(claims={}) builder = ConversationBuilder.create_from_identity(identity, "msteams") assert builder._agent_id is None def test_create_from_identity_custom_service_url(self): - identity = ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True) + identity = ClaimsIdentity(claims={"aud": "app-id"}) builder = ConversationBuilder.create_from_identity( identity, "msteams", service_url="https://override/" ) assert builder._service_url == "https://override/" def test_create_from_identity_returns_builder_instance(self): - identity = ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True) + identity = ClaimsIdentity(claims={"aud": "app-id"}) result = ConversationBuilder.create_from_identity(identity, "msteams") assert isinstance(result, ConversationBuilder) @@ -267,7 +266,6 @@ def test_build_requires_channel_id(self): def test_build_with_identity_preserves_claims(self): identity = ClaimsIdentity( claims={"aud": "app-id", "tid": "tenant", "ver": "2.0"}, - is_authenticated=True, ) conv = _prep_build( ConversationBuilder.create_from_identity(identity, "msteams") diff --git a/tests/hosting_core/app/proactive/test_create_conversation_options.py b/tests/hosting_core/app/proactive/test_create_conversation_options.py index 665c8193b..4ab42026e 100644 --- a/tests/hosting_core/app/proactive/test_create_conversation_options.py +++ b/tests/hosting_core/app/proactive/test_create_conversation_options.py @@ -11,7 +11,7 @@ def _make_identity(): - return ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True) + return ClaimsIdentity(claims={"aud": "app-id"}) def _make_params(): diff --git a/tests/hosting_core/app/proactive/test_proactive.py b/tests/hosting_core/app/proactive/test_proactive.py index 8469acab7..fe9918ac5 100644 --- a/tests/hosting_core/app/proactive/test_proactive.py +++ b/tests/hosting_core/app/proactive/test_proactive.py @@ -158,7 +158,7 @@ async def test_store_overwrites_existing_conversation(self, proactive): @pytest.mark.asyncio async def test_store_from_turn_context(self, proactive): ref = _make_reference("ctx-conv") - identity = ClaimsIdentity(claims={"aud": "ctx-app"}, is_authenticated=True) + identity = ClaimsIdentity(claims={"aud": "ctx-app"}) # spec=TurnContext is required: store_conversation checks isinstance(ctx, TurnContext) ctx = MagicMock(spec=TurnContext) @@ -469,7 +469,7 @@ def proactive(self, storage): @pytest.fixture def identity(self): - return ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True) + return ClaimsIdentity(claims={"aud": "app-id"}) @pytest.fixture def options(self, identity): diff --git a/tests/hosting_core/authorization/test_authorize_request.py b/tests/hosting_core/authorization/test_authorize_request.py index 2d8269788..f997b775a 100644 --- a/tests/hosting_core/authorization/test_authorize_request.py +++ b/tests/hosting_core/authorization/test_authorize_request.py @@ -41,7 +41,7 @@ async def test_authorize_request_returns_401_when_header_is_missing_and_anonymou @pytest.mark.asyncio async def test_authorize_request_returns_anonymous_claims_when_header_is_missing_and_anonymous_enabled(): auth_config = AgentAuthConfiguration(anonymous_allowed=True) - claims = ClaimsIdentity({}, False, authentication_type="Anonymous") + claims = ClaimsIdentity({}, authentication_type="Anonymous") validator = MagicMock() validator.get_anonymous_claims.return_value = claims @@ -69,7 +69,7 @@ async def test_authorize_request_returns_401_for_invalid_authorization_header_fo @pytest.mark.asyncio async def test_authorize_request_validates_bearer_token(): auth_config = AgentAuthConfiguration() - claims = ClaimsIdentity({"aud": "app-id"}, True) + claims = ClaimsIdentity({"aud": "app-id"}, authentication_type="Bearer") validator = MagicMock() validator.validate_token = AsyncMock(return_value=claims) diff --git a/tests/hosting_core/authorization/test_claims_identity.py b/tests/hosting_core/authorization/test_claims_identity.py index e29725f34..c6a1715f0 100644 --- a/tests/hosting_core/authorization/test_claims_identity.py +++ b/tests/hosting_core/authorization/test_claims_identity.py @@ -14,7 +14,6 @@ def test_default_identity_is_anonymous(self): assert identity.authentication_type is None assert identity.security_token is None assert identity.allow_anonymous is True - assert identity.is_authenticated is False def test_default_claims_are_not_shared(self): first = ClaimsIdentity() @@ -36,75 +35,32 @@ def test_constructor_preserves_values(self): assert identity.claims is claims assert identity.authentication_type == "Bearer" assert identity.security_token == "token" - assert identity.is_authenticated is True - - def test_is_authenticated_parameter_is_deprecated(self): - with pytest.warns(DeprecationWarning, match="is_authenticated"): - identity = ClaimsIdentity(is_authenticated=True) - - assert identity.allow_anonymous is True class TestClaimsIdentityAnonymousAccess: @pytest.mark.parametrize( - ("claims", "is_authenticated", "authentication_type", "expected"), + ("claims", "authentication_type", "expected"), [ - (None, None, None, True), - ({}, False, None, True), - ({}, True, None, True), - ({"aud": "app-id"}, None, None, False), - ({}, None, "Bearer", False), + (None, None, True), + ({}, None, True), + ({"aud": "app-id"}, None, False), + ({}, "Anonymous", True), + ({}, "Bearer", False), ], ) def test_allow_anonymous( self, claims, - is_authenticated, authentication_type, expected, ): identity = ClaimsIdentity( claims=claims, - is_authenticated=is_authenticated, authentication_type=authentication_type, ) assert identity.allow_anonymous is expected - @pytest.mark.parametrize("is_authenticated", [False, True]) - def test_deprecated_is_authenticated_does_not_affect_allow_anonymous( - self, is_authenticated - ): - identity = ClaimsIdentity( - claims={}, - is_authenticated=is_authenticated, - ) - - assert identity.allow_anonymous is True - - -class TestClaimsIdentityAuthenticationCompatibility: - @pytest.mark.parametrize( - ("claims", "expected"), - [ - ({}, False), - ({"aud": "app-id"}, True), - ], - ) - def test_is_authenticated_is_derived_from_claims(self, claims, expected): - identity = ClaimsIdentity(claims=claims) - - assert identity.is_authenticated is expected - - def test_is_authenticated_setter_is_deprecated_no_op(self): - identity = ClaimsIdentity(claims={"aud": "app-id"}) - - with pytest.warns(DeprecationWarning, match="is_authenticated"): - identity.is_authenticated = False - - with pytest.warns(DeprecationWarning, match="is_authenticated"): - assert identity.is_authenticated is True - def test_get_claim_value_returns_matching_claim(): identity = ClaimsIdentity(claims={"aud": "app-id"}) diff --git a/tests/hosting_core/authorization/test_jwt_token_validator.py b/tests/hosting_core/authorization/test_jwt_token_validator.py index dea055bbb..030f2a61a 100644 --- a/tests/hosting_core/authorization/test_jwt_token_validator.py +++ b/tests/hosting_core/authorization/test_jwt_token_validator.py @@ -43,7 +43,7 @@ async def test_validate_token_success_returns_authenticated_claims( token = make_signed_jwt(private_key, {"aud": "client-1"}) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert identity.claims["aud"] == "client-1" @pytest.mark.asyncio @@ -145,7 +145,7 @@ async def test_list_issuer_does_not_crash_routing_or_tenant_binding( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False # Falls through to default (non-Bot-Framework) routing. assert captured_uris == [ "https://login.microsoftonline.com/tenant-1/discovery/v2.0/keys" @@ -168,7 +168,7 @@ async def test_dict_issuer_does_not_crash_routing_or_tenant_binding( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == [ "https://login.microsoftonline.com/tenant-1/discovery/v2.0/keys" ] @@ -213,7 +213,7 @@ async def test_non_string_tid_skips_tenant_binding(self, monkeypatch): ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False class TestJwtTokenValidatorIssuerOptIn: @@ -268,7 +268,7 @@ async def test_missing_tid_skips_binding_even_when_issuer_validation_disabled( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_noncanonical_entra_issuer_variants_skip_binding(self, monkeypatch): @@ -289,7 +289,7 @@ async def test_noncanonical_entra_issuer_variants_skip_binding(self, monkeypatch {"aud": "client-1", "iss": issuer, "tid": mismatched_tid}, ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_issuer_allow_list_not_enforced_when_disabled(self, monkeypatch): @@ -314,7 +314,7 @@ async def test_issuer_allow_list_not_enforced_when_disabled(self, monkeypatch): ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_default_issuer_accepted(self, monkeypatch): @@ -336,7 +336,7 @@ async def test_validate_issuer_enabled_default_issuer_accepted(self, monkeypatch ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_unrecognized_issuer_rejected( @@ -385,7 +385,7 @@ async def test_validate_issuer_enabled_v1_issuer_recognized_and_bound( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_tid_mismatch_rejected(self, monkeypatch): @@ -433,7 +433,7 @@ async def test_validate_issuer_enabled_missing_tid_skips_binding(self, monkeypat ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_bot_framework_issuer_skips_binding( @@ -457,7 +457,7 @@ async def test_validate_issuer_enabled_bot_framework_issuer_skips_binding( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == ["https://login.botframework.com/v1/.well-known/keys"] @pytest.mark.asyncio @@ -487,7 +487,7 @@ async def test_validate_issuer_enabled_alias_tenant_issuer_skips_binding( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_well_known_first_party_issuer_accepted( @@ -515,7 +515,7 @@ async def test_validate_issuer_enabled_well_known_first_party_issuer_accepted( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_explicit_issuers_used(self, monkeypatch): @@ -535,7 +535,7 @@ async def test_validate_issuer_enabled_explicit_issuers_used(self, monkeypatch): ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_common_tenant_accepts_any_same_cloud_tenant( @@ -559,7 +559,7 @@ async def test_validate_issuer_enabled_common_tenant_accepts_any_same_cloud_tena ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_organizations_tenant_accepts_any_same_cloud_tenant( @@ -583,7 +583,7 @@ async def test_validate_issuer_enabled_organizations_tenant_accepts_any_same_clo ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False @pytest.mark.asyncio async def test_validate_issuer_enabled_gov_authority_routes_and_accepts_gov_issuer( @@ -611,7 +611,7 @@ async def test_validate_issuer_enabled_gov_authority_routes_and_accepts_gov_issu ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == [ f"https://login.microsoftonline.us/{tenant_id}/discovery/v2.0/keys" ] @@ -678,7 +678,7 @@ async def test_public_jwks_routing_preserves_root_connection_endpoint( token = make_signed_jwt(private_key, {"aud": "client-b"}) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == [ f"https://login.microsoftonline.com/{tenant_a}/discovery/v2.0/keys" ] @@ -712,7 +712,7 @@ async def test_gov_jwks_routing_uses_matching_connection_by_audience( token = make_signed_jwt(private_key, {"aud": "client-b"}) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == [ f"https://login.microsoftonline.us/{gov_tenant}/discovery/v2.0/keys" ] @@ -755,7 +755,7 @@ async def test_validate_token_multi_connection_issuer_validation_uses_matched_te ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False class TestJwtTokenValidatorEffectiveTenant: @@ -784,7 +784,7 @@ async def test_public_jwks_routing_defaults_to_common_without_tenant( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == [ "https://login.microsoftonline.com/common/discovery/v2.0/keys" ] @@ -817,7 +817,7 @@ async def test_public_jwks_routing_ignores_authority_embedded_common_tenant( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == [ "https://login.microsoftonline.com/concrete-tenant-id/discovery/v2.0/keys" ] @@ -850,7 +850,7 @@ async def test_public_jwks_routing_ignores_authority_embedded_concrete_tenant( ) identity = await validator.validate_token(token) - assert identity.is_authenticated is True + assert identity.allow_anonymous is False assert captured_uris == [ "https://login.microsoftonline.com/common/discovery/v2.0/keys" ] diff --git a/tests/hosting_core/connector/test_user_token_client.py b/tests/hosting_core/connector/test_user_token_client.py index ff08f7faf..b6e78aea0 100644 --- a/tests/hosting_core/connector/test_user_token_client.py +++ b/tests/hosting_core/connector/test_user_token_client.py @@ -346,29 +346,19 @@ async def handler(request): @pytest.mark.asyncio async def test_missing_context_is_rejected_before_a_sign_in_request(self): - client_without_app_id = UserTokenClient( - "https://token.example", token="", app_id=None - ) + with pytest.raises(ValueError, match="App ID cannot be empty"): + UserTokenClient("https://token.example", token="", app_id=None) + activity = Activity( type="message", from_property=ChannelAccount(id="user-1"), ) + client = UserTokenClient("https://token.example", token="", app_id="app-id") try: - with pytest.raises(ValueError, match="App ID must be provided"): - await client_without_app_id.get_sign_in_resource("connection", activity) - - client_with_app_id = UserTokenClient( - "https://token.example", token="", app_id="app-id" - ) - try: - with pytest.raises(ValueError, match="Activity must have a channel_id"): - await client_with_app_id.get_token_or_sign_in_resource( - "connection", activity - ) - finally: - await client_with_app_id.close() + with pytest.raises(ValueError, match="Activity must have a channel_id"): + await client.get_token_or_sign_in_resource("connection", activity) finally: - await client_without_app_id.close() + await client.close() @pytest.mark.asyncio async def test_missing_user_token_returns_an_empty_token_response(self): diff --git a/tests/hosting_core/storage/test_transcript_logger_middleware.py b/tests/hosting_core/storage/test_transcript_logger_middleware.py index 1a01ace7d..ed00403d6 100644 --- a/tests/hosting_core/storage/test_transcript_logger_middleware.py +++ b/tests/hosting_core/storage/test_transcript_logger_middleware.py @@ -31,7 +31,7 @@ async def test_should_round_trip_via_middleware(): adapter = MockTestingAdapter(channelName) adapter.use(transcript_middleware) - id = ClaimsIdentity({}, True) + id = ClaimsIdentity({}, authentication_type="Bearer") async def callback(tc): print("process callback") @@ -61,7 +61,7 @@ async def test_should_log_outgoing_activity_sent_by_callback(): adapter = MockTestingAdapter(channelName) adapter.use(transcript_middleware) - id = ClaimsIdentity({}, True) + id = ClaimsIdentity({}, authentication_type="Bearer") async def callback(tc): await tc.send_activity("bot response") @@ -99,7 +99,7 @@ async def test_should_write_to_file(): adapter = MockTestingAdapter(channelName) adapter.use(transcript_middleware) - id = ClaimsIdentity({}, True) + id = ClaimsIdentity({}, authentication_type="Bearer") async def callback(tc): print("process callback") @@ -127,7 +127,7 @@ async def test_should_write_to_console(): adapter = MockTestingAdapter(channelName) adapter.use(transcript_middleware) - id = ClaimsIdentity({}, True) + id = ClaimsIdentity({}, authentication_type="Bearer") async def callback(tc): print("process callback") diff --git a/tests/hosting_core/telemetry/test_proactive_spans.py b/tests/hosting_core/telemetry/test_proactive_spans.py index adb772a83..c6ef3b948 100644 --- a/tests/hosting_core/telemetry/test_proactive_spans.py +++ b/tests/hosting_core/telemetry/test_proactive_spans.py @@ -49,7 +49,7 @@ def _make_create_options( else: params = ConversationParameters(members=members) return CreateConversationOptions( - identity=ClaimsIdentity(claims={"aud": "app-id"}, is_authenticated=True), + identity=ClaimsIdentity(claims={"aud": "app-id"}), channel_id=channel_id, parameters=params, service_url="https://smba.trafficmanager.net/teams/", diff --git a/tests/hosting_core/test_channel_service_adapter.py b/tests/hosting_core/test_channel_service_adapter.py index 3530c77a2..4dd678f6f 100644 --- a/tests/hosting_core/test_channel_service_adapter.py +++ b/tests/hosting_core/test_channel_service_adapter.py @@ -140,7 +140,6 @@ async def callback(context: TurnContext): "ver": "2.0", "azp": "outgoing_app_id", }, - is_authenticated=True, ) await adapter.process_activity( @@ -209,7 +208,6 @@ async def callback(context: TurnContext): "ver": "2.0", "azp": "outgoing_app_id", }, - is_authenticated=True, ) with pytest.raises(Exception) as exc_info: @@ -244,7 +242,6 @@ async def callback(context: TurnContext): "ver": "2.0", "azp": "outgoing_app_id", }, - is_authenticated=True, ) await adapter.process_proactive( diff --git a/tests/hosting_core/test_connection_manager.py b/tests/hosting_core/test_connection_manager.py index 12d9455fe..f676a09c1 100644 --- a/tests/hosting_core/test_connection_manager.py +++ b/tests/hosting_core/test_connection_manager.py @@ -125,21 +125,21 @@ def test_get_connection_none_defaults_to_service(self): def test_token_provider_aud_and_service_url_match(self): cm = self._make(**ENV_CONFIG) - claims = ClaimsIdentity(claims={"aud": "api://service"}, is_authenticated=True) + claims = ClaimsIdentity(claims={"aud": "api://service"}) assert cm.get_token_provider( claims, "https://service.com/api" ) is cm.get_connection("SERVICE_CONNECTION") def test_token_provider_service_url_match(self): cm = self._make(**ENV_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) assert cm.get_token_provider(claims, "agentic") is cm.get_connection("AGENTIC") def test_service_url_is_regex_unanchored(self): # SERVICEURL is a regex matched with re.search (mirrors .NET Regex.Match), # so a bare substring pattern matches anywhere in the service URL. cm = self._make(**ENV_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) assert cm.get_token_provider( claims, "https://host/agentic/path" ) is cm.get_connection("AGENTIC") @@ -148,7 +148,7 @@ def test_service_url_regex_dot_is_wildcard(self): # '.' in a SERVICEURL regex is a wildcard (regex semantics, matching .NET), # so "https://microsoft.com/*" also matches a host like "microsoftXcom". cm = self._make(**ENV_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) assert cm.get_token_provider( claims, "https://microsoftXcom/foo" ) is cm.get_connection("MISC") @@ -161,14 +161,14 @@ def test_invalid_service_url_regex_raises_clear_value_error(self): "CONNECTIONSMAP": [{"CONNECTION": "SERVICE_CONNECTION", "SERVICEURL": "["}], } cm = self._make(**config) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) with pytest.raises(ValueError, match="Invalid SERVICEURL regex"): cm.get_token_provider(claims, "https://example.com") def test_token_provider_no_map_returns_default(self): config = {k: v for k, v in ENV_CONFIG.items() if k != "CONNECTIONSMAP"} cm = self._make(**config) - claims = ClaimsIdentity(claims={"aud": "api://misc"}, is_authenticated=True) + claims = ClaimsIdentity(claims={"aud": "api://misc"}) assert ( cm.get_token_provider(claims, "https://example.com") is cm.get_default_connection() @@ -176,7 +176,7 @@ def test_token_provider_no_map_returns_default(self): def test_token_provider_from_activity_uses_activity_service_url(self): cm = self._make(**ENV_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) activity = self._activity(service_url="https://host/agentic/path") assert cm.get_token_provider_from_activity( @@ -187,7 +187,7 @@ def test_token_provider_from_activity_regular_role_ignores_alternate_blueprint( self, ): cm = self._make(**ALT_BLUEPRINT_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) activity = self._activity(recipient_role=RoleTypes.agent) assert cm.get_token_provider_from_activity( @@ -202,7 +202,7 @@ def test_token_provider_from_activity_agentic_role_uses_alternate_blueprint( self, recipient_role ): cm = self._make(**ALT_BLUEPRINT_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) activity = self._activity(recipient_role=recipient_role) assert cm.get_token_provider_from_activity( @@ -213,7 +213,7 @@ def test_token_provider_from_activity_agentic_without_alternate_uses_mapped_prov self, ): cm = self._make(**ENV_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) activity = self._activity( service_url="https://host/agentic/path", recipient_role=RoleTypes.agentic_identity, @@ -225,7 +225,7 @@ def test_token_provider_from_activity_agentic_without_alternate_uses_mapped_prov def test_token_provider_from_activity_without_recipient_is_not_agentic(self): cm = self._make(**ALT_BLUEPRINT_CONFIG) - claims = ClaimsIdentity(claims={}, is_authenticated=False) + claims = ClaimsIdentity(claims={}) activity = self._activity(recipient_role=None) assert cm.get_token_provider_from_activity( @@ -236,8 +236,8 @@ def test_token_provider_from_activity_without_recipient_is_not_agentic(self): "claims, service_url", [ [None, ""], - [ClaimsIdentity(claims={}, is_authenticated=False), None], - [ClaimsIdentity(claims={"aud": "api://misc"}, is_authenticated=False), ""], + [ClaimsIdentity(claims={}), None], + [ClaimsIdentity(claims={"aud": "api://misc"}), ""], ], ) def test_token_provider_errors(self, claims, service_url): @@ -265,7 +265,7 @@ def test_explicit_empty_connections_map_overrides_kwargs(self): **ENV_CONFIG, ) assert cm._connections_map == [] - claims = ClaimsIdentity(claims={"aud": "api://service"}, is_authenticated=True) + claims = ClaimsIdentity(claims={"aud": "api://service"}) assert ( cm.get_token_provider(claims, "https://service.com/api") is cm.get_default_connection() diff --git a/tests/hosting_dialogs/helpers.py b/tests/hosting_dialogs/helpers.py index f693ed5d9..35b0ee040 100644 --- a/tests/hosting_dialogs/helpers.py +++ b/tests/hosting_dialogs/helpers.py @@ -315,7 +315,9 @@ def __init__(self, callback: AgentCallbackHandler = None, **kwargs): # Dialog-specific token client that implements the user_token API self._dialog_token_client = DialogUserTokenClient() # OAuthPrompt reads claims["aud"] from the turn context identity. - self.claims_identity = ClaimsIdentity({"aud": "test-app-id"}, True) + self.claims_identity = ClaimsIdentity( + {"aud": "test-app-id"}, authentication_type="Bearer" + ) def add_user_token( self, diff --git a/tests/hosting_dialogs/test_dialog_manager.py b/tests/hosting_dialogs/test_dialog_manager.py index 28af29edf..2e779c2f5 100644 --- a/tests/hosting_dialogs/test_dialog_manager.py +++ b/tests/hosting_dialogs/test_dialog_manager.py @@ -119,7 +119,7 @@ async def create_test_flow( 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 = ClaimsIdentity({}, authentication_type="Anonymous") claims_identity.claims["ver"] = ( "2.0" # AuthenticationConstants.VersionClaim ) diff --git a/tests/hosting_fastapi/test_jwt_authorization_middleware.py b/tests/hosting_fastapi/test_jwt_authorization_middleware.py index 79c06320e..b88981696 100644 --- a/tests/hosting_fastapi/test_jwt_authorization_middleware.py +++ b/tests/hosting_fastapi/test_jwt_authorization_middleware.py @@ -63,7 +63,7 @@ def _status(messages): @pytest.mark.asyncio async def test_fastapi_middleware_stores_claims_and_calls_downstream_app(): auth_config = AgentAuthConfiguration() - claims = ClaimsIdentity({"aud": "app-id"}, True) + claims = ClaimsIdentity({"aud": "app-id"}, authentication_type="Bearer") messages = [] downstream_called = False @@ -118,7 +118,7 @@ async def test_fastapi_middleware_converts_http_response_without_calling_downstr @pytest.mark.asyncio async def test_fastapi_decorator_stores_claims_and_calls_handler(): auth_config = AgentAuthConfiguration() - claims = ClaimsIdentity({"aud": "decorator-app"}, True) + claims = ClaimsIdentity({"aud": "decorator-app"}, authentication_type="Bearer") @jwt_authorization_decorator async def route(request: Request): diff --git a/tests/testing_package/test_test_adapter.py b/tests/testing_package/test_test_adapter.py index 90c31c29d..f545eca3b 100644 --- a/tests/testing_package/test_test_adapter.py +++ b/tests/testing_package/test_test_adapter.py @@ -25,7 +25,7 @@ @pytest.mark.asyncio async def test_process_activity_provides_a_channel_shaped_turn_to_agent_code(): adapter = TestAdapter(channel_id=Channels.ms_teams) - identity = ClaimsIdentity({"sub": "test-user"}, True) + identity = ClaimsIdentity({"sub": "test-user"}, authentication_type="Bearer") received_context: TurnContext | None = None async def callback(context: TurnContext): @@ -214,7 +214,7 @@ async def test_update_and_delete_unknown_replies_leave_the_queue_unchanged(): @pytest.mark.asyncio async def test_proactive_turn_uses_the_supplied_activity_and_identity(): adapter = TestAdapter() - identity = ClaimsIdentity({"sub": "proactive-user"}, True) + identity = ClaimsIdentity({"sub": "proactive-user"}, authentication_type="Bearer") continuation = adapter.create_activity("") continuation.type = ActivityTypes.event continuation.name = "continue" @@ -250,7 +250,7 @@ async def callback(context: TurnContext): @pytest.mark.asyncio async def test_continue_conversation_with_claims_uses_activity_identity_and_services(): adapter = TestAdapter() - identity = ClaimsIdentity({"sub": "proactive-user"}, True) + identity = ClaimsIdentity({"sub": "proactive-user"}, authentication_type="Bearer") continuation = adapter.conversation.get_continuation_activity() async def callback(context: TurnContext): From eaf2131f460d69ac9e2242249435962b987b698b Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 25 Sep 2026 09:47:34 -0700 Subject: [PATCH 5/6] Reverting UserTokenClient changes --- .../microsoft_agents/activity/__init__.py | 1 - .../connector/client/user_token_client.py | 16 +++++++++++-- .../microsoft_agents/testing/test_adapter.py | 2 +- .../connector/test_user_token_client.py | 24 +++++++++++++------ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py index 58195d4b4..3bc5d483e 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/__init__.py @@ -155,7 +155,6 @@ "HeroCard", "InnerHttpError", "InvokeResponse", - "MediaCard", "MediaEventValue", "MediaUrl", "Mention", diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py index 61e7787f5..0a00543c0 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py @@ -36,7 +36,7 @@ def __init__( endpoint: str, token: str, *, - app_id: str, + app_id: str | None = None, session: ClientSession | None = None, ): """ @@ -49,7 +49,10 @@ def __init__( """ self._app_id = app_id if not self._app_id: - raise ValueError("App ID cannot be empty") + logger.warning( + "App ID is not provided. Some operations may not work without an App ID." + " In the future, creation of UserTokenClient without an App ID will be deprecated." + ) if not endpoint.endswith("/"): endpoint += "/" @@ -155,6 +158,11 @@ async def get_sign_in_resource( :param final_redirect: The final redirect URL after sign-in. :return: The sign-in resource. """ + if not self._app_id: + raise ValueError( + "App ID must be provided in the creation of UserTokenClient to get sign-in resource." + ) + state = UserTokenClient._create_token_exchange_state( self._app_id, connection_name, activity ) @@ -267,6 +275,10 @@ async def get_token_or_sign_in_resource( raise ValueError( "Activity must have a channel_id to get token or sign-in resource." ) + if not self._app_id: + raise ValueError( + "App ID must be provided in the creation of UserTokenClient to get the token or sign-in resource." + ) state = UserTokenClient._create_token_exchange_state( self._app_id, connection_name, activity diff --git a/libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py b/libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py index e4918ede9..13e06a987 100644 --- a/libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py +++ b/libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py @@ -104,7 +104,7 @@ def __init__( self._activity_queue = [] self._queued_requests = [] - self.claims_identity = ClaimsIdentity({}, True) + self.claims_identity = ClaimsIdentity({}) @property def conversation(self) -> ConversationReference: diff --git a/tests/hosting_core/connector/test_user_token_client.py b/tests/hosting_core/connector/test_user_token_client.py index b6e78aea0..ff08f7faf 100644 --- a/tests/hosting_core/connector/test_user_token_client.py +++ b/tests/hosting_core/connector/test_user_token_client.py @@ -346,19 +346,29 @@ async def handler(request): @pytest.mark.asyncio async def test_missing_context_is_rejected_before_a_sign_in_request(self): - with pytest.raises(ValueError, match="App ID cannot be empty"): - UserTokenClient("https://token.example", token="", app_id=None) - + client_without_app_id = UserTokenClient( + "https://token.example", token="", app_id=None + ) activity = Activity( type="message", from_property=ChannelAccount(id="user-1"), ) - client = UserTokenClient("https://token.example", token="", app_id="app-id") try: - with pytest.raises(ValueError, match="Activity must have a channel_id"): - await client.get_token_or_sign_in_resource("connection", activity) + with pytest.raises(ValueError, match="App ID must be provided"): + await client_without_app_id.get_sign_in_resource("connection", activity) + + client_with_app_id = UserTokenClient( + "https://token.example", token="", app_id="app-id" + ) + try: + with pytest.raises(ValueError, match="Activity must have a channel_id"): + await client_with_app_id.get_token_or_sign_in_resource( + "connection", activity + ) + finally: + await client_with_app_id.close() finally: - await client.close() + await client_without_app_id.close() @pytest.mark.asyncio async def test_missing_user_token_returns_an_empty_token_response(self): From f88e5dae83bcc604da55d63124b95a24e989e6a0 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 25 Sep 2026 11:12:38 -0700 Subject: [PATCH 6/6] Addressing PR feedback --- .../microsoft_agents/hosting/core/app/agent_application.py | 1 - .../hosting/core/authorization/claims_identity.py | 2 -- test_samples/app_style/echo_proactive_agent.py | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py index da866ea35..9f7875d2f 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py @@ -10,7 +10,6 @@ from contextlib import nullcontext from copy import copy from functools import partial -from typing_extensions import deprecated import re from typing import ( diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py index a6dbd9181..1067c9eff 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py @@ -1,8 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -import warnings - from typing import Any from .authentication_constants import AuthenticationConstants diff --git a/test_samples/app_style/echo_proactive_agent.py b/test_samples/app_style/echo_proactive_agent.py index 3f5def525..d5119520a 100644 --- a/test_samples/app_style/echo_proactive_agent.py +++ b/test_samples/app_style/echo_proactive_agent.py @@ -75,7 +75,7 @@ def key(self) -> str: @classmethod def from_context(cls, context: TurnContext) -> "ConversationReferenceRecord": - identity = context.identity or ClaimsIdentity({}, False) + identity = context.identity or ClaimsIdentity() reference = context.activity.get_conversation_reference() return cls( claims=dict(identity.claims),