diff --git a/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/channel_service_adapter.py b/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/channel_service_adapter.py index 975a40b5..18150b5a 100644 --- a/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/channel_service_adapter.py +++ b/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/channel_service_adapter.py @@ -333,7 +333,7 @@ async def process_activity( use_anonymous_auth_callback = False if ( not claims_identity.is_authenticated - and activity.channel_id == Channels.emulator + and claims_identity.authentication_type == "Anonymous" ): use_anonymous_auth_callback = True diff --git a/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/rest_channel_service_client_factory.py b/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/rest_channel_service_client_factory.py index d5c8497c..c89d90cd 100644 --- a/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/rest_channel_service_client_factory.py +++ b/libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/rest_channel_service_client_factory.py @@ -2,6 +2,7 @@ from microsoft.agents.authorization import ( AuthenticationConstants, + AnonymousTokenProvider, ClaimsIdentity, Connections, ) @@ -15,6 +16,8 @@ class RestChannelServiceClientFactory(ChannelServiceClientFactoryBase): + _ANONYMOUS_TOKEN_PROVIDER = AnonymousTokenProvider() + def __init__( self, configuration: Any, @@ -43,11 +46,15 @@ async def create_connector_client( "RestChannelServiceClientFactory.create_connector_client: audience can't be None or Empty" ) + token_provider = ( + self._connections.get_token_provider(claims_identity, service_url) + if not use_anonymous + else self._ANONYMOUS_TOKEN_PROVIDER + ) + return ConnectorClient( endpoint=service_url, - credential_token_provider=self._connections.get_token_provider( - claims_identity, service_url - ), + credential_token_provider=token_provider, credential_resource_url=audience, credential_scopes=scopes, ) @@ -55,10 +62,15 @@ async def create_connector_client( async def create_user_token_client( self, claims_identity: ClaimsIdentity, use_anonymous: bool = False ) -> UserTokenClient: - return UserTokenClient( - credential_token_provider=self._connections.get_token_provider( + token_provider = ( + self._connections.get_token_provider( claims_identity, self._token_service_endpoint - ), + ) + if not use_anonymous + else self._ANONYMOUS_TOKEN_PROVIDER + ) + return UserTokenClient( + credential_token_provider=token_provider, credential_resource_url=self._token_service_audience, credential_scopes=[f"{self._token_service_audience}/.default"], endpoint=self._token_service_endpoint, diff --git a/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/__init__.py b/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/__init__.py index 5f589df0..4a78f57b 100644 --- a/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/__init__.py +++ b/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/__init__.py @@ -1,5 +1,6 @@ from .access_token_provider_base import AccessTokenProviderBase from .authentication_constants import AuthenticationConstants +from .anonymous_token_provider import AnonymousTokenProvider from .connections import Connections from .agent_auth_configuration import AgentAuthConfiguration from .claims_identity import ClaimsIdentity @@ -8,6 +9,7 @@ __all__ = [ "AccessTokenProviderBase", "AuthenticationConstants", + "AnonymousTokenProvider", "Connections", "AgentAuthConfiguration", "ClaimsIdentity", diff --git a/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/anonymous_token_provider.py b/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/anonymous_token_provider.py new file mode 100644 index 00000000..318566a3 --- /dev/null +++ b/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/anonymous_token_provider.py @@ -0,0 +1,13 @@ +from .access_token_provider_base import AccessTokenProviderBase + + +class AnonymousTokenProvider(AccessTokenProviderBase): + """ + A class that provides an anonymous token for authentication. + This is used when no authentication is required. + """ + + async def get_access_token( + self, resource_url: str, scopes: list[str], force_refresh: bool = False + ) -> str: + return "" diff --git a/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/jwt_token_validator.py b/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/jwt_token_validator.py index 56249ad3..837f9b15 100644 --- a/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/jwt_token_validator.py +++ b/libraries/Core/microsoft-agents-authorization/microsoft/agents/authorization/jwt_token_validator.py @@ -25,6 +25,9 @@ def validate_token(self, token: str) -> ClaimsIdentity: # This probably should return a ClaimsIdentity return ClaimsIdentity(decoded_token, True) + def get_anonymous_claims(self) -> ClaimsIdentity: + return ClaimsIdentity({}, False, authentication_type="Anonymous") + def _get_public_key_or_secret(self, token: str) -> PyJWK: header = get_unverified_header(token) unverified_payload: dict = decode(token, options={"verify_signature": False}) diff --git a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/jwt_authorization_middleware.py b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/jwt_authorization_middleware.py index e463b03a..c6efced5 100644 --- a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/jwt_authorization_middleware.py +++ b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/jwt_authorization_middleware.py @@ -17,9 +17,9 @@ async def jwt_authorization_middleware(request: Request, handler): except ValueError as e: return json_response({"error": str(e)}, status=401) else: - if (not auth_config.CLIENT_ID) and (request.app["env"] == "DEV"): - # TODO: Define anonymous strategy - request["user"] = {"name": "anonymous"} + if not auth_config.CLIENT_ID: + # TODO: Refine anonymous strategy + request["claims_identity"] = token_validator.get_anonymous_claims() else: return json_response( {"error": "Authorization header not found"}, status=401 diff --git a/test_samples/agent_to_agent/agent_1/app.py b/test_samples/agent_to_agent/agent_1/app.py index 06786aaa..b41e73d7 100644 --- a/test_samples/agent_to_agent/agent_1/app.py +++ b/test_samples/agent_to_agent/agent_1/app.py @@ -2,6 +2,7 @@ # Licensed under the MIT License. from aiohttp.web import Application, Request, Response, run_app +from dotenv import load_dotenv from microsoft.agents.builder import RestChannelServiceClientFactory from microsoft.agents.hosting.aiohttp import ( @@ -25,6 +26,8 @@ from agent1 import Agent1 from config import DefaultConfig +load_dotenv() + AUTH_PROVIDER = MsalAuth(DefaultConfig()) diff --git a/test_samples/agent_to_agent/agent_1/config.py b/test_samples/agent_to_agent/agent_1/config.py index e4c8cc5c..9533c2dd 100644 --- a/test_samples/agent_to_agent/agent_1/config.py +++ b/test_samples/agent_to_agent/agent_1/config.py @@ -1,3 +1,4 @@ +from os import environ from microsoft.agents.authentication.msal import AuthTypes, MsalAuthConfiguration from microsoft.agents.client import ( ChannelHostConfiguration, @@ -9,12 +10,13 @@ class DefaultConfig(MsalAuthConfiguration, ChannelsConfiguration): """Agent Configuration""" - AUTH_TYPE = AuthTypes.client_secret - TENANT_ID = "" - CLIENT_ID = "" - CLIENT_SECRET = "" - PORT = 3978 - SCOPES = ["https://api.botframework.com/.default"] + def __init__(self) -> None: + self.AUTH_TYPE = AuthTypes.client_secret + self.TENANT_ID = "" or environ.get("TENANT_ID") + self.CLIENT_ID = "" or environ.get("CLIENT_ID") + self.CLIENT_SECRET = "" or environ.get("CLIENT_SECRET") + self.PORT = 3978 + self.SCOPES = ["https://api.botframework.com/.default"] # ChannelHost configuration @staticmethod @@ -23,7 +25,7 @@ def CHANNEL_HOST_CONFIGURATION(): CHANNELS=[ ChannelInfo( id="EchoAgent", - app_id="", # Target agent's app_id + app_id="" or environ.get("TARGET_APP_ID"), # Target agent's app_id resource_url="http://localhost:3999/api/messages", token_provider="ChannelConnection", channel_factory="HttpAgentClient", @@ -31,5 +33,5 @@ def CHANNEL_HOST_CONFIGURATION(): ) ], HOST_ENDPOINT="http://localhost:3978/api/botresponse/", - HOST_APP_ID="", # usually the same as CLIENT_ID + HOST_APP_ID="" or environ.get("CLIENT_ID"), # usually the same as CLIENT_ID ) diff --git a/test_samples/agent_to_agent/agent_2/app.py b/test_samples/agent_to_agent/agent_2/app.py index 60f8713e..3b4497b9 100644 --- a/test_samples/agent_to_agent/agent_2/app.py +++ b/test_samples/agent_to_agent/agent_2/app.py @@ -2,6 +2,7 @@ # Licensed under the MIT License. from aiohttp.web import Application, Request, Response, run_app +from dotenv import load_dotenv from microsoft.agents.builder import RestChannelServiceClientFactory from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware @@ -15,6 +16,8 @@ from agent2 import Agent2 from config import DefaultConfig +load_dotenv() + AUTH_PROVIDER = MsalAuth(DefaultConfig()) diff --git a/test_samples/agent_to_agent/agent_2/config.py b/test_samples/agent_to_agent/agent_2/config.py index eb64d0d1..63360859 100644 --- a/test_samples/agent_to_agent/agent_2/config.py +++ b/test_samples/agent_to_agent/agent_2/config.py @@ -1,11 +1,13 @@ +from os import environ from microsoft.agents.authentication.msal import AuthTypes, MsalAuthConfiguration class DefaultConfig(MsalAuthConfiguration): """Agent Configuration""" - AUTH_TYPE = AuthTypes.client_secret - TENANT_ID = "" - CLIENT_ID = "" - CLIENT_SECRET = "" - PORT = 3999 + def __init__(self) -> None: + self.AUTH_TYPE = AuthTypes.client_secret + self.TENANT_ID = "" or environ.get("TENANT_ID") + self.CLIENT_ID = "" or environ.get("CLIENT_ID") + self.CLIENT_SECRET = "" or environ.get("CLIENT_SECRET") + self.PORT = 3999 diff --git a/test_samples/echo_agent/app.py b/test_samples/echo_agent/app.py index d3122d79..99025d64 100644 --- a/test_samples/echo_agent/app.py +++ b/test_samples/echo_agent/app.py @@ -2,6 +2,7 @@ # Licensed under the MIT License. from aiohttp.web import Application, Request, Response, run_app +from dotenv import load_dotenv from microsoft.agents.builder import RestChannelServiceClientFactory from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware @@ -15,6 +16,8 @@ from echo_agent import EchoAgent from config import DefaultConfig +load_dotenv() + AUTH_PROVIDER = MsalAuth(DefaultConfig()) diff --git a/test_samples/echo_agent/config.py b/test_samples/echo_agent/config.py index 90074c76..cb76b758 100644 --- a/test_samples/echo_agent/config.py +++ b/test_samples/echo_agent/config.py @@ -1,11 +1,13 @@ +from os import environ from microsoft.agents.authentication.msal import AuthTypes, MsalAuthConfiguration class DefaultConfig(MsalAuthConfiguration): """Agent Configuration""" - AUTH_TYPE = AuthTypes.client_secret - TENANT_ID = "" - CLIENT_ID = "" - CLIENT_SECRET = "" - PORT = 3978 + def __init__(self) -> None: + self.AUTH_TYPE = AuthTypes.client_secret + self.TENANT_ID = "" or environ.get("TENANT_ID") + self.CLIENT_ID = "" or environ.get("CLIENT_ID") + self.CLIENT_SECRET = "" or environ.get("CLIENT_SECRET") + self.PORT = 3978