Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ def __init__(self, msal_configuration: AgentAuthConfiguration):
f"Initializing MsalAuth with configuration: {self._msal_configuration}"
)

@property
def configuration(self) -> AgentAuthConfiguration:
"""
The configuration for the access token provider.

:return: The configuration as an AgentAuthConfiguration object.
"""
return self._msal_configuration

async def get_access_token(
self, resource_url: str, scopes: list[str], force_refresh: bool = False
) -> str:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from typing import Protocol, Optional
from abc import abstractmethod
from abc import ABC, abstractmethod

from .agent_auth_configuration import AgentAuthConfiguration


class AccessTokenProviderBase(ABC):

@property
@abstractmethod
def configuration(self) -> AgentAuthConfiguration:
"""
The configuration for the access token provider.
Comment on lines +11 to +15

:return: The configuration as an AgentAuthConfiguration object.
"""
raise NotImplementedError()

class AccessTokenProviderBase(Protocol):
@abstractmethod
async def get_access_token(
self, resource_url: str, scopes: list[str], force_refresh: bool = False
Expand All @@ -18,7 +30,7 @@ async def get_access_token(
:param force_refresh: True to force a refresh of the token; or false to get the token only if it is necessary.
:return: The access token as a string.
"""
pass
raise NotImplementedError()

async def acquire_token_on_behalf_of(
self, scopes: list[str], user_assertion: str
Expand All @@ -34,7 +46,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(
Expand All @@ -48,5 +60,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()
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from typing import Optional

from .agent_auth_configuration import AgentAuthConfiguration
from .access_token_provider_base import AccessTokenProviderBase


Expand All @@ -12,6 +13,14 @@ class AnonymousTokenProvider(AccessTokenProviderBase):
This is used when no authentication is required.
"""

@property
def configuration(self) -> AgentAuthConfiguration:
"""
The configuration for the anonymous token provider.
Since this provider does not require any configuration, it returns None.
"""
Comment thread
rodrigobr-msft marked this conversation as resolved.
return AgentAuthConfiguration()

async def get_access_token(
self, resource_url: str, scopes: list[str], force_refresh: bool = False
) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

from collections.abc import Callable

from microsoft_agents.activity import (
Activity,
RoleTypes
)

from .agent_auth_configuration import AgentAuthConfiguration
from .access_token_provider_base import AccessTokenProviderBase
from .claims_identity import ClaimsIdentity
Expand Down Expand Up @@ -151,7 +156,7 @@ def _service_url_matches(pattern: str, service_url: str) -> bool:
raise ValueError(
f"Invalid SERVICEURL regex '{pattern}' in connections map: {exc}"
) from exc

def get_token_provider(
self, claims_identity: ClaimsIdentity, service_url: str
) -> AccessTokenProviderBase:
Expand Down Expand Up @@ -189,6 +194,40 @@ def get_token_provider(
raise ValueError(
f"No connection found for audience '{aud}' and serviceUrl '{service_url}'."
)

def get_token_provider_from_activity(
self,
claims_identity: ClaimsIdentity,
activity: Activity
) -> AccessTokenProviderBase:
"""
Comment on lines +198 to +203
Get the OAuth token provider for the agent from an activity.

:param claims_identity: The claims identity of the bot.
:type claims_identity: :class:`microsoft_agents.hosting.core.ClaimsIdentity`
:param activity: The activity of the bot.
:type activity: dict
:return: The OAuth token provider for the agent.
:rtype: :class:`microsoft_agents.hosting.core.AccessTokenProviderBase`
:raises ValueError: If no connection is found for the given audience and service URL.
"""
Comment on lines +206 to +213
connection: AccessTokenProviderBase | None = None
try:
connection = self.get_token_provider(claims_identity, activity.service_url)
finally:
if (connection is not None and (
activity.recipient.role == RoleTypes.agentic_identity or
activity.recipient.role == RoleTypes.agentic_user
)):
if connection.configuration.ALT_BLUEPRINT_ID:
connection = self.get_connection(connection.configuration.ALT_BLUEPRINT_ID)

if connection:
return connection

raise RuntimeError(
"The connection returned by get_token_provider is not compatible with the activity's recipient role."
)
Comment on lines +214 to +230

def get_default_connection_configuration(self) -> AgentAuthConfiguration:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from abc import abstractmethod
from typing import Protocol

from microsoft_agents.activity import Activity

from .agent_auth_configuration import AgentAuthConfiguration
from .access_token_provider_base import AccessTokenProviderBase
from .claims_identity import ClaimsIdentity
Expand All @@ -15,13 +17,18 @@ 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 OAuth connection for the agent.
"""
raise NotImplementedError()

@abstractmethod
def get_default_connection(self) -> AccessTokenProviderBase:
"""
Get the default OAuth connection for the agent.

:return: The default OAuth connection for the agent.
"""
raise NotImplementedError()

Expand All @@ -31,12 +38,31 @@ 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 of the agent.
:return: The OAuth token provider for the agent.
"""
raise NotImplementedError()

@abstractmethod
def get_token_provider_from_activity(
self, claims_identity: ClaimsIdentity, activity: Activity
) -> AccessTokenProviderBase:
"""
Get the OAuth token provider for the agent from an activity.

:param claims_identity: The claims identity of the agent.
:param activity: The activity from which to get the token provider.
"""
raise NotImplementedError(
)
Comment on lines +58 to +59

@abstractmethod
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()
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading