From 31d8e34dc5b8fc6738806dd52b1a68a16780e776 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 22 Jul 2026 13:43:41 -0700 Subject: [PATCH 1/2] AccessTokenProviderBase interface improvements --- .../authentication/msal/msal_auth.py | 9 +++++++++ .../access_token_provider_base.py | 19 ++++++++++++++++--- .../authorization/anonymous_token_provider.py | 9 +++++++++ .../hosting/core/authorization/connections.py | 11 +++++++++++ .../rest_channel_service_client_factory.py | 14 +++----------- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py index e3400d860..2a346ea66 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py @@ -61,6 +61,15 @@ def __init__(self, msal_configuration: AgentAuthConfiguration): f"Initializing MsalAuth with configuration: {self._msal_configuration}" ) + @property + def configuration(self) -> AgentAuthConfiguration: + """Returns the MSAL authentication configuration. + + :return: The MSAL authentication configuration. + :rtype: :class:`microsoft_agents.hosting.core.authorization.agent_auth_configuration.AgentAuthConfiguration` + """ + return self._msal_configuration + async def get_access_token( self, resource_url: str, scopes: list[str], force_refresh: bool = False ) -> str: diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/access_token_provider_base.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/access_token_provider_base.py index 26c748a1f..5cd29e296 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/access_token_provider_base.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/access_token_provider_base.py @@ -1,11 +1,24 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import Protocol, Optional +from typing import Protocol from abc import abstractmethod +from .agent_auth_configuration import AgentAuthConfiguration + class AccessTokenProviderBase(Protocol): + + @property + @abstractmethod + def configuration(self) -> AgentAuthConfiguration: + """ + The configuration for the access token provider. + + :return: The configuration for the access token provider. + """ + raise NotImplementedError() + @abstractmethod async def get_access_token( self, resource_url: str, scopes: list[str], force_refresh: bool = False @@ -34,7 +47,7 @@ async def acquire_token_on_behalf_of( async def get_agentic_application_token( self, tenant_id: str, agent_app_instance_id: str - ) -> Optional[str]: + ) -> str | None: raise NotImplementedError() async def get_agentic_instance_token( @@ -48,5 +61,5 @@ async def get_agentic_user_token( agent_app_instance_id: str, agentic_user_id: str, scopes: list[str], - ) -> Optional[str]: + ) -> str | None: raise NotImplementedError() diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/anonymous_token_provider.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/anonymous_token_provider.py index 722b39456..801e8a9a0 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/anonymous_token_provider.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/anonymous_token_provider.py @@ -4,6 +4,7 @@ from typing import Optional from .access_token_provider_base import AccessTokenProviderBase +from .agent_auth_configuration import AgentAuthConfiguration class AnonymousTokenProvider(AccessTokenProviderBase): @@ -12,6 +13,14 @@ class AnonymousTokenProvider(AccessTokenProviderBase): This is used when no authentication is required. """ + @property + def configuration(self) -> AgentAuthConfiguration: + """ + The configuration for the access token provider. + :return: The configuration for the access token provider. + """ + return AgentAuthConfiguration() + async def get_access_token( self, resource_url: str, scopes: list[str], force_refresh: bool = False ) -> str: diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/connections.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/connections.py index e11103e22..ff4e4ab2d 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/connections.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/connections.py @@ -15,6 +15,9 @@ class Connections(Protocol): def get_connection(self, connection_name: str) -> AccessTokenProviderBase: """ Get the OAuth connection for the agent. + + :param connection_name: The name of the connection. + :return: The access token provider for the agent. """ raise NotImplementedError() @@ -22,6 +25,8 @@ def get_connection(self, connection_name: str) -> AccessTokenProviderBase: def get_default_connection(self) -> AccessTokenProviderBase: """ Get the default OAuth connection for the agent. + + :return: The access token provider for the agent. """ raise NotImplementedError() @@ -31,6 +36,10 @@ def get_token_provider( ) -> AccessTokenProviderBase: """ Get the OAuth token provider for the agent. + + :param claims_identity: The claims identity of the agent. + :param service_url: The service URL for which to get the token provider. + :return: The access token provider for the agent. """ raise NotImplementedError() @@ -38,5 +47,7 @@ def get_token_provider( def get_default_connection_configuration(self) -> AgentAuthConfiguration: """ Get the default connection configuration for the agent. + + :return: The default connection configuration for the agent. """ raise NotImplementedError() diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py index e1347b780..19640539b 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py @@ -49,17 +49,9 @@ async def _get_agentic_token(self, context: TurnContext, service_url: str) -> st context.identity, service_url ) - # Provider-agnostic access to the connection's auth configuration. MSAL - # exposes it as ``_msal_configuration``; other providers (e.g. the Entra - # sidecar) expose it as ``configuration``. The only value needed here is - # the optional alternate-blueprint connection name. - configuration = getattr(connection, "_msal_configuration", None) - if configuration is None: - configuration = getattr(connection, "configuration", None) - - alt_blueprint_id = ( - getattr(configuration, "ALT_BLUEPRINT_ID", None) if configuration else None - ) + configuration = connection.configuration + alt_blueprint_id = configuration.ALT_BLUEPRINT_ID + if alt_blueprint_id: logger.debug( "Using alternative blueprint ID for agentic token retrieval: %s", From f6af637efc5f4e38730cf36cbd4fc53cf8b35f82 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 22 Jul 2026 13:53:33 -0700 Subject: [PATCH 2/2] Fixing failing test stubs --- .../_common/testing_objects/testing_token_provider.py | 10 +++++++++- tests/hosting_core/test_connection_manager.py | 6 +++++- .../test_rest_channel_service_client_factory.py | 9 +++++---- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/_common/testing_objects/testing_token_provider.py b/tests/_common/testing_objects/testing_token_provider.py index 66dcf0025..9e3f1c1d1 100644 --- a/tests/_common/testing_objects/testing_token_provider.py +++ b/tests/_common/testing_objects/testing_token_provider.py @@ -1,4 +1,7 @@ -from microsoft_agents.hosting.core import AccessTokenProviderBase +from microsoft_agents.hosting.core import ( + AccessTokenProviderBase, + AgentAuthConfiguration, +) class TestingTokenProvider(AccessTokenProviderBase): @@ -19,6 +22,11 @@ def __init__(self, name: str): name: Identifier used to generate predictable token values """ self.name = name + self._configuration = AgentAuthConfiguration() + + @property + def configuration(self) -> AgentAuthConfiguration: + return self._configuration async def get_access_token( self, resource_url: str, scopes: list[str], force_refresh: bool = False diff --git a/tests/hosting_core/test_connection_manager.py b/tests/hosting_core/test_connection_manager.py index 0143d45cc..0eee46ad6 100644 --- a/tests/hosting_core/test_connection_manager.py +++ b/tests/hosting_core/test_connection_manager.py @@ -15,7 +15,11 @@ class FakeProvider(AccessTokenProviderBase): """Minimal provider that records the configuration it was built from.""" def __init__(self, configuration: AgentAuthConfiguration): - self.configuration = configuration + self._configuration = configuration + + @property + def configuration(self) -> AgentAuthConfiguration: + return self._configuration async def get_access_token(self, resource_url, scopes, force_refresh=False): return "fake-token" diff --git a/tests/hosting_core/test_rest_channel_service_client_factory.py b/tests/hosting_core/test_rest_channel_service_client_factory.py index 08c1d6192..22db417ef 100644 --- a/tests/hosting_core/test_rest_channel_service_client_factory.py +++ b/tests/hosting_core/test_rest_channel_service_client_factory.py @@ -292,7 +292,7 @@ async def test_create_connector_client_agentic_identity( if alt_blueprint: auth_config.ALT_BLUEPRINT_ID = "alt_blueprint_id" connection_manager.get_connection = mocker.Mock(return_value=token_provider) - token_provider._msal_configuration = auth_config + token_provider.configuration = auth_config factory = RestChannelServiceClientFactory(connection_manager) @@ -383,8 +383,8 @@ async def test_create_connector_client_agentic_identity_non_msal_provider( async def test_create_connector_client_agentic_no_configuration( self, mocker, activity_agentic_identity ): - """A provider exposing neither config attribute must not raise; it should - simply skip the alternate-blueprint redirect.""" + """A provider without an alternate blueprint configured must not raise; it + should simply skip the alternate-blueprint redirect.""" # setup mock_connector_client = mocker.Mock(spec=TeamsConnectorClient) mocker.patch.object( @@ -397,6 +397,7 @@ async def test_create_connector_client_agentic_no_configuration( token_provider.get_agentic_instance_token = mocker.AsyncMock( return_value=(DEFAULTS.token, DEFAULTS.token) ) + token_provider.configuration = AgentAuthConfiguration() connection_manager = mocker.Mock(spec=Connections) connection_manager.get_token_provider = mocker.Mock(return_value=token_provider) @@ -447,7 +448,7 @@ async def test_create_connector_client_agentic_user( if alt_blueprint: auth_config.ALT_BLUEPRINT_ID = "alt_blueprint_id" connection_manager.get_connection = mocker.Mock(return_value=token_provider) - token_provider._msal_configuration = auth_config + token_provider.configuration = auth_config factory = RestChannelServiceClientFactory(connection_manager)