microsoft-agents-authentication-msal 1.10.0.dev5__tar.gz → 1.10.0.dev6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {microsoft_agents_authentication_msal-1.10.0.dev5/microsoft_agents_authentication_msal.egg-info → microsoft_agents_authentication_msal-1.10.0.dev6}/PKG-INFO +32 -2
- microsoft_agents_authentication_msal-1.10.0.dev6/VERSION.txt +1 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents/authentication/msal/__init__.py +2 -0
- microsoft_agents_authentication_msal-1.10.0.dev6/microsoft_agents/authentication/msal/connection_settings.py +87 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents/authentication/msal/msal_auth.py +95 -56
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents/authentication/msal/msal_connection_manager.py +9 -2
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents/authentication/msal/msal_token_credential.py +6 -3
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6/microsoft_agents_authentication_msal.egg-info}/PKG-INFO +32 -2
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents_authentication_msal.egg-info/SOURCES.txt +1 -0
- microsoft_agents_authentication_msal-1.10.0.dev6/microsoft_agents_authentication_msal.egg-info/requires.txt +4 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/readme.md +30 -0
- microsoft_agents_authentication_msal-1.10.0.dev5/VERSION.txt +0 -1
- microsoft_agents_authentication_msal-1.10.0.dev5/microsoft_agents_authentication_msal.egg-info/requires.txt +0 -4
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/LICENSE +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/MANIFEST.in +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents/authentication/msal/errors/__init__.py +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents/authentication/msal/errors/error_resources.py +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents/authentication/msal/py.typed +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents_authentication_msal.egg-info/dependency_links.txt +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/microsoft_agents_authentication_msal.egg-info/top_level.txt +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/pyproject.toml +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/setup.cfg +0 -0
- {microsoft_agents_authentication_msal-1.10.0.dev5 → microsoft_agents_authentication_msal-1.10.0.dev6}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microsoft-agents-authentication-msal
|
|
3
|
-
Version: 1.10.0.
|
|
3
|
+
Version: 1.10.0.dev6
|
|
4
4
|
Summary: A msal-based authentication library for Microsoft Agents
|
|
5
5
|
Author: Microsoft Corporation
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,7 @@ Classifier: Operating System :: OS Independent
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: microsoft-agents-hosting-core==1.10.0.
|
|
18
|
+
Requires-Dist: microsoft-agents-hosting-core==1.10.0.dev6
|
|
19
19
|
Requires-Dist: azure-core
|
|
20
20
|
Requires-Dist: msal>=1.34.0
|
|
21
21
|
Requires-Dist: requests>=2.32.3
|
|
@@ -253,6 +253,36 @@ def start_server(
|
|
|
253
253
|
[...]
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
## Typed Connection Settings
|
|
257
|
+
|
|
258
|
+
`MsalAuth` accepts `Configuration` or `MsalConnectionSettings` as well as the existing
|
|
259
|
+
`AgentAuthConfiguration` input. Use `provider.config` to read bound MSAL settings.
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from microsoft_agents.activity.config import Configuration
|
|
263
|
+
from microsoft_agents.authentication.msal import MsalConnectionManager
|
|
264
|
+
|
|
265
|
+
connections = MsalConnectionManager(
|
|
266
|
+
config=Configuration(
|
|
267
|
+
connections={
|
|
268
|
+
"SERVICE_CONNECTION": {
|
|
269
|
+
"settings": {
|
|
270
|
+
"auth_type": "ClientSecret",
|
|
271
|
+
"client_id": "your-client-id",
|
|
272
|
+
"tenant_id": "your-tenant-id",
|
|
273
|
+
"client_secret": "your-client-secret",
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
settings = connections.get_default_connection().config
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Load credentials from your application's secure configuration rather than hardcoding
|
|
283
|
+
them. Legacy constructor calls and the `provider.configuration` compatibility view
|
|
284
|
+
remain supported. Token credentials reuse the provider's bound settings.
|
|
285
|
+
|
|
256
286
|
## Authentication Types
|
|
257
287
|
The M365 Agents SDK in Python supports the following Auth types:
|
|
258
288
|
```python
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.10.0.dev6
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
from .msal_auth import MsalAuth
|
|
5
5
|
from .msal_connection_manager import MsalConnectionManager
|
|
6
6
|
from .msal_token_credential import MsalTokenCredential
|
|
7
|
+
from .connection_settings import MsalConnectionSettings
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
10
|
+
"MsalConnectionSettings",
|
|
9
11
|
"MsalAuth",
|
|
10
12
|
"MsalConnectionManager",
|
|
11
13
|
"MsalTokenCredential",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
|
|
6
|
+
from microsoft_agents.activity.config import Configuration, xpath
|
|
7
|
+
from microsoft_agents.hosting.core import (
|
|
8
|
+
AgentAuthConfiguration,
|
|
9
|
+
AuthTypes,
|
|
10
|
+
ConnectionSettingsBase,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class MsalConnectionSettings(ConnectionSettingsBase):
|
|
16
|
+
"""MSAL-specific settings, bound independently of inbound JWT policy."""
|
|
17
|
+
|
|
18
|
+
auth_type: str = AuthTypes.client_secret
|
|
19
|
+
client_secret: str | None = field(default=None, repr=False)
|
|
20
|
+
cert_pfx_file: str | None = None
|
|
21
|
+
send_x5c: bool = False
|
|
22
|
+
connection_name: str | None = None
|
|
23
|
+
federated_client_id: str | None = None
|
|
24
|
+
federated_token_file: str | None = None
|
|
25
|
+
idpm_resource: str | None = None
|
|
26
|
+
azure_region: str | None = None
|
|
27
|
+
|
|
28
|
+
def __post_init__(self):
|
|
29
|
+
if self.auth_type == AuthTypes.certificate and not self.cert_pfx_file:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"CERT_PFX_FILE is required for certificate authentication."
|
|
32
|
+
)
|
|
33
|
+
if (
|
|
34
|
+
self.auth_type == AuthTypes.federated_credentials
|
|
35
|
+
and not self.federated_client_id
|
|
36
|
+
):
|
|
37
|
+
raise ValueError(
|
|
38
|
+
"FEDERATED_CLIENT_ID is required for federated_credentials authentication."
|
|
39
|
+
)
|
|
40
|
+
if (
|
|
41
|
+
self.auth_type == AuthTypes.workload_identity
|
|
42
|
+
and not self.federated_token_file
|
|
43
|
+
):
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"FEDERATED_TOKEN_FILE is required for workload_identity authentication."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _get_init_kwargs(cls, config: Configuration) -> dict:
|
|
50
|
+
base = super()._get_init_kwargs(config)
|
|
51
|
+
base.update(
|
|
52
|
+
{
|
|
53
|
+
"auth_type": config.get_str(
|
|
54
|
+
xpath("auth_type"), default=AuthTypes.client_secret
|
|
55
|
+
),
|
|
56
|
+
"client_secret": config.get_strn(xpath("client_secret")),
|
|
57
|
+
"cert_pfx_file": config.get_strn(xpath("cert_pfx_file")),
|
|
58
|
+
"send_x5c": config.get_bool(xpath("send_x5c"), default=False),
|
|
59
|
+
"connection_name": config.get_strn(xpath("connection_name")),
|
|
60
|
+
"federated_client_id": config.get_strn(xpath("federated_client_id")),
|
|
61
|
+
"federated_token_file": config.get_strn(xpath("federated_token_file")),
|
|
62
|
+
"idpm_resource": config.get_strn(xpath("idpm_resource")),
|
|
63
|
+
"azure_region": config.get_strn(
|
|
64
|
+
xpath("azure_region"), xpath("regional_authority")
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
return base
|
|
69
|
+
|
|
70
|
+
def _to_agent_auth_config(self) -> AgentAuthConfiguration:
|
|
71
|
+
fields = dict(
|
|
72
|
+
auth_type=self.auth_type,
|
|
73
|
+
client_id=self.client_id,
|
|
74
|
+
tenant_id=self.tenant_id,
|
|
75
|
+
authority=self.authority,
|
|
76
|
+
client_secret=self.client_secret,
|
|
77
|
+
cert_pfx_file=self.cert_pfx_file,
|
|
78
|
+
send_x5c=self.send_x5c,
|
|
79
|
+
connection_name=self.connection_name,
|
|
80
|
+
federated_client_id=self.federated_client_id,
|
|
81
|
+
federated_token_file=self.federated_token_file,
|
|
82
|
+
ALT_BLUEPRINT_NAME=self.alternate_blueprint_connection_name,
|
|
83
|
+
idpm_resource=self.idpm_resource,
|
|
84
|
+
azure_region=self.azure_region,
|
|
85
|
+
scopes=self.scopes,
|
|
86
|
+
)
|
|
87
|
+
return AgentAuthConfiguration(**{**self.extras, **fields})
|
|
@@ -8,7 +8,9 @@ import asyncio
|
|
|
8
8
|
import logging
|
|
9
9
|
import time
|
|
10
10
|
import jwt
|
|
11
|
-
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
12
14
|
from urllib.parse import urlparse, ParseResult as URI
|
|
13
15
|
from msal import (
|
|
14
16
|
ConfidentialClientApplication,
|
|
@@ -23,6 +25,7 @@ from azure.core.credentials_async import AsyncTokenCredential
|
|
|
23
25
|
|
|
24
26
|
from requests import Session
|
|
25
27
|
|
|
28
|
+
from microsoft_agents.activity.config import Configuration
|
|
26
29
|
from microsoft_agents.activity._utils import _DeferredString
|
|
27
30
|
|
|
28
31
|
from microsoft_agents.hosting.core import (
|
|
@@ -31,7 +34,8 @@ from microsoft_agents.hosting.core import (
|
|
|
31
34
|
AgentAuthConfiguration,
|
|
32
35
|
)
|
|
33
36
|
from microsoft_agents.hosting.core.authorization.telemetry import spans
|
|
34
|
-
from
|
|
37
|
+
from .connection_settings import MsalConnectionSettings
|
|
38
|
+
from .errors import authentication_errors
|
|
35
39
|
|
|
36
40
|
logger = logging.getLogger(__name__)
|
|
37
41
|
|
|
@@ -47,29 +51,69 @@ async def _async_acquire_token_for_client(msal_auth_client, *args, **kwargs):
|
|
|
47
51
|
|
|
48
52
|
class MsalAuth(AccessTokenProviderBase):
|
|
49
53
|
|
|
50
|
-
def __init__(
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
msal_configuration: (
|
|
57
|
+
Configuration | MsalConnectionSettings | AgentAuthConfiguration
|
|
58
|
+
),
|
|
59
|
+
):
|
|
51
60
|
"""Initializes the MsalAuth class with the given configuration.
|
|
52
61
|
|
|
53
|
-
:param msal_configuration:
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
:param msal_configuration: Raw Configuration or bound MsalConnectionSettings.
|
|
63
|
+
AgentAuthConfiguration remains accepted for deprecated compatibility.
|
|
64
|
+
Do not mutate settings or their nested values after passing them in.
|
|
65
|
+
:type config: Configuration | MsalConnectionSettings | AgentAuthConfiguration
|
|
56
66
|
"""
|
|
67
|
+
config = msal_configuration
|
|
68
|
+
if isinstance(config, MsalConnectionSettings):
|
|
69
|
+
self._deprecated_config = None
|
|
70
|
+
_msal_configuration = config
|
|
71
|
+
elif isinstance(config, Configuration):
|
|
72
|
+
self._deprecated_config = None
|
|
73
|
+
_msal_configuration = MsalConnectionSettings.from_configuration(config)
|
|
74
|
+
else:
|
|
75
|
+
warnings.warn(
|
|
76
|
+
"AgentAuthConfiguration is deprecated and will be removed in future versions. "
|
|
77
|
+
"Please pass a config of type Mapping instead.",
|
|
78
|
+
DeprecationWarning,
|
|
79
|
+
stacklevel=2,
|
|
80
|
+
)
|
|
81
|
+
self._deprecated_config = config
|
|
82
|
+
_msal_configuration = MsalConnectionSettings._from_agent_auth_config(config)
|
|
57
83
|
|
|
58
|
-
self._msal_configuration =
|
|
84
|
+
self._msal_configuration = _msal_configuration
|
|
59
85
|
self._msal_auth_client_map: dict[
|
|
60
86
|
str, ConfidentialClientApplication | ManagedIdentityClient
|
|
61
87
|
] = {}
|
|
62
88
|
|
|
63
89
|
# TokenCache is thread-safe and async-safe per MSAL documentation
|
|
64
90
|
self._token_cache = TokenCache()
|
|
65
|
-
logger.debug("Initializing MsalAuth")
|
|
91
|
+
logger.debug("Initializing MsalAuth provider.")
|
|
66
92
|
|
|
67
93
|
@property
|
|
68
94
|
def configuration(self) -> AgentAuthConfiguration:
|
|
69
|
-
"""
|
|
95
|
+
"""Return the deprecated AgentAuthConfiguration compatibility view.
|
|
96
|
+
|
|
97
|
+
Use :attr:`config` for the bound MsalConnectionSettings instead.
|
|
70
98
|
|
|
71
|
-
:return: The
|
|
72
|
-
:rtype:
|
|
99
|
+
:return: The legacy authentication configuration.
|
|
100
|
+
:rtype: AgentAuthConfiguration
|
|
101
|
+
"""
|
|
102
|
+
warnings.warn(
|
|
103
|
+
"Accessing the deprecated configuration. This will be removed in future versions.",
|
|
104
|
+
DeprecationWarning,
|
|
105
|
+
stacklevel=2,
|
|
106
|
+
)
|
|
107
|
+
if self._deprecated_config is None:
|
|
108
|
+
self._deprecated_config = self._msal_configuration._to_agent_auth_config()
|
|
109
|
+
return self._deprecated_config
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def config(self) -> MsalConnectionSettings:
|
|
113
|
+
"""Return the bound MSAL settings; nested mutable values remain mutable.
|
|
114
|
+
|
|
115
|
+
:return: The MSAL connection settings.
|
|
116
|
+
:rtype: MsalConnectionSettings
|
|
73
117
|
"""
|
|
74
118
|
return self._msal_configuration
|
|
75
119
|
|
|
@@ -110,12 +154,10 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
110
154
|
"""
|
|
111
155
|
with spans.GetAccessToken(
|
|
112
156
|
scopes,
|
|
113
|
-
self._msal_configuration.
|
|
157
|
+
self._msal_configuration.auth_type,
|
|
114
158
|
):
|
|
115
159
|
logger.debug(
|
|
116
|
-
"Requesting access token for resource:
|
|
117
|
-
resource_url,
|
|
118
|
-
scopes,
|
|
160
|
+
f"Requesting access token for resource: {resource_url}, scopes: {scopes}"
|
|
119
161
|
)
|
|
120
162
|
valid_uri, instance_uri = self._uri_validator(resource_url)
|
|
121
163
|
if not valid_uri:
|
|
@@ -191,7 +233,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
191
233
|
)
|
|
192
234
|
|
|
193
235
|
if "access_token" not in token:
|
|
194
|
-
logger.error("Failed to acquire token on behalf of user.")
|
|
236
|
+
logger.error(f"Failed to acquire token on behalf of user.")
|
|
195
237
|
raise ValueError(
|
|
196
238
|
authentication_errors.FailedToAcquireToken.format(str(token))
|
|
197
239
|
)
|
|
@@ -199,8 +241,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
199
241
|
return token["access_token"]
|
|
200
242
|
|
|
201
243
|
logger.error(
|
|
202
|
-
"On-behalf-of flow is not supported with the current authentication type:
|
|
203
|
-
msal_auth_client.__class__.__name__,
|
|
244
|
+
f"On-behalf-of flow is not supported with the current authentication type: {msal_auth_client.__class__.__name__}"
|
|
204
245
|
)
|
|
205
246
|
raise NotImplementedError(
|
|
206
247
|
authentication_errors.OnBehalfOfFlowNotSupportedAuthType.format(
|
|
@@ -210,49 +251,49 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
210
251
|
|
|
211
252
|
@staticmethod
|
|
212
253
|
def _resolve_authority(
|
|
213
|
-
config:
|
|
254
|
+
config: MsalConnectionSettings, tenant_id: str | None = None
|
|
214
255
|
) -> str:
|
|
215
256
|
tenant_id = MsalAuth._resolve_tenant_id(config, tenant_id)
|
|
216
257
|
if not tenant_id:
|
|
217
258
|
return (
|
|
218
|
-
config.
|
|
219
|
-
or f"https://login.microsoftonline.com/{config.
|
|
259
|
+
config.authority
|
|
260
|
+
or f"https://login.microsoftonline.com/{config.tenant_id}"
|
|
220
261
|
)
|
|
221
262
|
|
|
222
|
-
if config.
|
|
263
|
+
if config.authority:
|
|
223
264
|
return re.sub(
|
|
224
265
|
r"/(?:common|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(?=/|$)",
|
|
225
266
|
f"/{tenant_id}",
|
|
226
|
-
config.
|
|
267
|
+
config.authority,
|
|
227
268
|
)
|
|
228
269
|
|
|
229
270
|
return f"https://login.microsoftonline.com/{tenant_id}"
|
|
230
271
|
|
|
231
272
|
@staticmethod
|
|
232
|
-
def _resolve_azure_region(config:
|
|
273
|
+
def _resolve_azure_region(config: MsalConnectionSettings) -> str | None:
|
|
233
274
|
"""Resolves the Azure regional token service (ESTS-R) to use, if configured.
|
|
234
275
|
|
|
235
|
-
:param config: The
|
|
236
|
-
:type config: :class:`microsoft_agents.
|
|
276
|
+
:param config: The bound MSAL connection settings.
|
|
277
|
+
:type config: :class:`microsoft_agents.authentication.msal.MsalConnectionSettings`
|
|
237
278
|
:return: The resolved Azure region or None if not configured.
|
|
238
279
|
:rtype: str | None
|
|
239
280
|
|
|
240
281
|
Returns the configured region only when it is populated and non-whitespace,
|
|
241
282
|
otherwise None so that MSAL falls back to the global token service.
|
|
242
283
|
"""
|
|
243
|
-
azure_region =
|
|
284
|
+
azure_region = config.azure_region
|
|
244
285
|
if azure_region and azure_region.strip():
|
|
245
286
|
return azure_region
|
|
246
287
|
return None
|
|
247
288
|
|
|
248
289
|
@staticmethod
|
|
249
|
-
def _resolve_idpm_resource(config:
|
|
290
|
+
def _resolve_idpm_resource(config: MsalConnectionSettings) -> str:
|
|
250
291
|
"""Resolves the resource URL for Identity Proxy Manager (IDPM) token acquisition.
|
|
251
292
|
|
|
252
293
|
When no resource is configured, defaults to the AzureAdTokenExchange resource.
|
|
253
294
|
Otherwise the configured value must be a valid absolute URI.
|
|
254
295
|
"""
|
|
255
|
-
idpm_resource =
|
|
296
|
+
idpm_resource = config.idpm_resource
|
|
256
297
|
if not idpm_resource:
|
|
257
298
|
return "api://AzureAdTokenExchange/.default"
|
|
258
299
|
|
|
@@ -264,35 +305,35 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
264
305
|
|
|
265
306
|
@staticmethod
|
|
266
307
|
def _resolve_tenant_id(
|
|
267
|
-
config:
|
|
308
|
+
config: MsalConnectionSettings, tenant_id: str | None = None
|
|
268
309
|
) -> str:
|
|
269
310
|
|
|
270
|
-
if not config.
|
|
311
|
+
if not config.tenant_id:
|
|
271
312
|
if tenant_id:
|
|
272
313
|
return tenant_id
|
|
273
314
|
raise ValueError("TENANT_ID is not set in the configuration.")
|
|
274
315
|
|
|
275
|
-
if tenant_id or config.
|
|
316
|
+
if tenant_id or config.tenant_id.lower() == "common":
|
|
276
317
|
return tenant_id
|
|
277
318
|
|
|
278
|
-
return config.
|
|
319
|
+
return config.tenant_id
|
|
279
320
|
|
|
280
321
|
def _create_client_application(
|
|
281
322
|
self, tenant_id: str | None = None
|
|
282
323
|
) -> ConfidentialClientApplication | ManagedIdentityClient:
|
|
283
324
|
|
|
284
|
-
if self._msal_configuration.
|
|
325
|
+
if self._msal_configuration.auth_type in (
|
|
285
326
|
AuthTypes.user_managed_identity,
|
|
286
327
|
AuthTypes.identity_proxy_manager,
|
|
287
328
|
):
|
|
288
329
|
return ManagedIdentityClient(
|
|
289
330
|
UserAssignedManagedIdentity(
|
|
290
|
-
client_id=self._msal_configuration.
|
|
331
|
+
client_id=self._msal_configuration.client_id
|
|
291
332
|
),
|
|
292
333
|
http_client=Session(),
|
|
293
334
|
)
|
|
294
335
|
|
|
295
|
-
elif self._msal_configuration.
|
|
336
|
+
elif self._msal_configuration.auth_type == AuthTypes.system_managed_identity:
|
|
296
337
|
return ManagedIdentityClient(
|
|
297
338
|
SystemAssignedManagedIdentity(),
|
|
298
339
|
http_client=Session(),
|
|
@@ -301,18 +342,18 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
301
342
|
authority = MsalAuth._resolve_authority(self._msal_configuration, tenant_id)
|
|
302
343
|
client_credential = None
|
|
303
344
|
|
|
304
|
-
if self._msal_configuration.
|
|
305
|
-
client_credential = self._msal_configuration.
|
|
306
|
-
elif self._msal_configuration.
|
|
345
|
+
if self._msal_configuration.auth_type == AuthTypes.client_secret:
|
|
346
|
+
client_credential = self._msal_configuration.client_secret
|
|
347
|
+
elif self._msal_configuration.auth_type == AuthTypes.certificate:
|
|
307
348
|
client_credential = {
|
|
308
|
-
"private_key_pfx_path": self._msal_configuration.
|
|
349
|
+
"private_key_pfx_path": self._msal_configuration.cert_pfx_file,
|
|
309
350
|
}
|
|
310
|
-
if self._msal_configuration.
|
|
351
|
+
if self._msal_configuration.send_x5c:
|
|
311
352
|
client_credential["public_certificate"] = True
|
|
312
|
-
elif self._msal_configuration.
|
|
353
|
+
elif self._msal_configuration.auth_type == AuthTypes.federated_credentials:
|
|
313
354
|
mi_client = ManagedIdentityClient(
|
|
314
355
|
UserAssignedManagedIdentity(
|
|
315
|
-
client_id=self._msal_configuration.
|
|
356
|
+
client_id=self._msal_configuration.federated_client_id
|
|
316
357
|
),
|
|
317
358
|
http_client=Session(),
|
|
318
359
|
)
|
|
@@ -323,8 +364,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
323
364
|
)
|
|
324
365
|
if "access_token" not in result:
|
|
325
366
|
logger.error(
|
|
326
|
-
"Failed to acquire token for federated credentials:
|
|
327
|
-
result,
|
|
367
|
+
f"Failed to acquire token for federated credentials: {result}"
|
|
328
368
|
)
|
|
329
369
|
raise ValueError(
|
|
330
370
|
authentication_errors.FailedToAcquireToken.format(
|
|
@@ -334,13 +374,13 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
334
374
|
return result["access_token"]
|
|
335
375
|
|
|
336
376
|
client_credential = {"client_assertion": get_assertion}
|
|
337
|
-
elif self._msal_configuration.
|
|
338
|
-
if not self._msal_configuration.
|
|
377
|
+
elif self._msal_configuration.auth_type == AuthTypes.workload_identity:
|
|
378
|
+
if not self._msal_configuration.federated_token_file:
|
|
339
379
|
raise ValueError(
|
|
340
380
|
"FEDERATED_TOKEN_FILE must be set in configuration."
|
|
341
381
|
)
|
|
342
382
|
|
|
343
|
-
federated_token_file = self._msal_configuration.
|
|
383
|
+
federated_token_file = self._msal_configuration.federated_token_file
|
|
344
384
|
|
|
345
385
|
def get_assertion() -> str:
|
|
346
386
|
with open(federated_token_file, encoding="utf-8") as f:
|
|
@@ -349,15 +389,14 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
349
389
|
client_credential = {"client_assertion": get_assertion}
|
|
350
390
|
else:
|
|
351
391
|
logger.error(
|
|
352
|
-
"Unsupported authentication type:
|
|
353
|
-
self._msal_configuration.AUTH_TYPE,
|
|
392
|
+
f"Unsupported authentication type: {self._msal_configuration.auth_type}"
|
|
354
393
|
)
|
|
355
394
|
raise NotImplementedError(
|
|
356
395
|
str(authentication_errors.AuthenticationTypeNotSupported)
|
|
357
396
|
)
|
|
358
397
|
|
|
359
398
|
return ConfidentialClientApplication(
|
|
360
|
-
client_id=self._msal_configuration.
|
|
399
|
+
client_id=self._msal_configuration.client_id,
|
|
361
400
|
authority=authority,
|
|
362
401
|
client_credential=client_credential,
|
|
363
402
|
azure_region=MsalAuth._resolve_azure_region(self._msal_configuration),
|
|
@@ -368,7 +407,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
368
407
|
) -> str:
|
|
369
408
|
# Create a unique representation for the client based on tenant_id and instance_id
|
|
370
409
|
# instance_id None is for when no agentic instance is associated with the request.
|
|
371
|
-
tenant_id = tenant_id or self._msal_configuration.
|
|
410
|
+
tenant_id = tenant_id or self._msal_configuration.tenant_id
|
|
372
411
|
return f"tenant:{tenant_id}.instance:{instance_id}"
|
|
373
412
|
|
|
374
413
|
def _get_client(
|
|
@@ -388,7 +427,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
388
427
|
result = urlparse(url_str)
|
|
389
428
|
return all([result.scheme, result.netloc]), result
|
|
390
429
|
except AttributeError:
|
|
391
|
-
logger.error("URI parsing error for
|
|
430
|
+
logger.error(f"URI parsing error for {url_str}")
|
|
392
431
|
return False, None
|
|
393
432
|
|
|
394
433
|
def _resolve_scopes_list(self, instance_url: URI, scopes=None) -> list[str]:
|
|
@@ -396,7 +435,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
396
435
|
return scopes
|
|
397
436
|
|
|
398
437
|
temp_list: list[str] = []
|
|
399
|
-
lst = self._msal_configuration.
|
|
438
|
+
lst = self._msal_configuration.scopes or []
|
|
400
439
|
for scope in lst:
|
|
401
440
|
scope_placeholder = scope
|
|
402
441
|
if "{instance}" in scope_placeholder.lower():
|
|
@@ -404,7 +443,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
404
443
|
"{instance}", f"{instance_url.scheme}://{instance_url.hostname}"
|
|
405
444
|
)
|
|
406
445
|
temp_list.append(scope_placeholder)
|
|
407
|
-
logger.debug("Resolved scopes:
|
|
446
|
+
logger.debug(f"Resolved scopes: {temp_list}")
|
|
408
447
|
return temp_list
|
|
409
448
|
|
|
410
449
|
# the call to MSAL is blocking, but in the future we want to create an asyncio task
|
|
@@ -446,7 +485,7 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
446
485
|
return None
|
|
447
486
|
|
|
448
487
|
if (
|
|
449
|
-
self._msal_configuration.
|
|
488
|
+
self._msal_configuration.auth_type == AuthTypes.identity_proxy_manager
|
|
450
489
|
and isinstance(msal_auth_client, ManagedIdentityClient)
|
|
451
490
|
):
|
|
452
491
|
resource = MsalAuth._resolve_idpm_resource(self._msal_configuration)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
2
|
# Licensed under the MIT License.
|
|
3
3
|
|
|
4
|
+
from microsoft_agents.activity.config import Configuration
|
|
4
5
|
from microsoft_agents.hosting.core import (
|
|
5
6
|
AgentAuthConfiguration,
|
|
6
7
|
ConnectionManager,
|
|
@@ -23,15 +24,20 @@ class MsalConnectionManager(ConnectionManager):
|
|
|
23
24
|
self,
|
|
24
25
|
connections_configurations: dict[str, AgentAuthConfiguration] | None = None,
|
|
25
26
|
connections_map: list[dict[str, str]] | None = None,
|
|
27
|
+
*,
|
|
28
|
+
config: Configuration | None = None,
|
|
26
29
|
**kwargs,
|
|
27
30
|
):
|
|
28
31
|
"""
|
|
29
32
|
Initialize the MSAL connection manager.
|
|
30
33
|
|
|
31
|
-
:param connections_configurations:
|
|
34
|
+
:param connections_configurations: Deprecated legacy connection configurations.
|
|
32
35
|
:type connections_configurations: dict[str, :class:`microsoft_agents.hosting.core.AgentAuthConfiguration`]
|
|
33
|
-
:param connections_map:
|
|
36
|
+
:param connections_map: Deprecated list of connection mappings.
|
|
34
37
|
:type connections_map: list[dict[str, str]]
|
|
38
|
+
:param config: Application Configuration containing connections and routing.
|
|
39
|
+
Prefer this over the deprecated positional configuration arguments.
|
|
40
|
+
:type config: Configuration | None
|
|
35
41
|
:raises ValueError: If no service connection configuration is provided.
|
|
36
42
|
"""
|
|
37
43
|
|
|
@@ -39,5 +45,6 @@ class MsalConnectionManager(ConnectionManager):
|
|
|
39
45
|
provider_factory=MsalAuth,
|
|
40
46
|
connections_configurations=connections_configurations,
|
|
41
47
|
connections_map=connections_map,
|
|
48
|
+
config=config,
|
|
42
49
|
**kwargs,
|
|
43
50
|
)
|
|
@@ -8,6 +8,7 @@ from azure.core.credentials_async import AsyncTokenCredential
|
|
|
8
8
|
|
|
9
9
|
from microsoft_agents.hosting.core import AgentAuthConfiguration
|
|
10
10
|
|
|
11
|
+
from .connection_settings import MsalConnectionSettings
|
|
11
12
|
from .msal_auth import MsalAuth
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
@@ -28,14 +29,16 @@ class MsalTokenCredential(AsyncTokenCredential):
|
|
|
28
29
|
|
|
29
30
|
def __init__(
|
|
30
31
|
self,
|
|
31
|
-
config: AgentAuthConfiguration,
|
|
32
|
+
config: MsalConnectionSettings | AgentAuthConfiguration,
|
|
32
33
|
*,
|
|
33
34
|
provider: MsalAuth | None = None,
|
|
34
35
|
):
|
|
35
36
|
"""Initializes the MsalTokenCredential with the given configuration.
|
|
36
37
|
|
|
37
|
-
:param config:
|
|
38
|
-
|
|
38
|
+
:param config: Bound MSAL settings. AgentAuthConfiguration remains accepted
|
|
39
|
+
for deprecated compatibility.
|
|
40
|
+
:type config: MsalConnectionSettings | AgentAuthConfiguration
|
|
41
|
+
:param provider: An existing provider to reuse instead of constructing one.
|
|
39
42
|
"""
|
|
40
43
|
self._config = config
|
|
41
44
|
self._provider: MsalAuth | None = provider
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microsoft-agents-authentication-msal
|
|
3
|
-
Version: 1.10.0.
|
|
3
|
+
Version: 1.10.0.dev6
|
|
4
4
|
Summary: A msal-based authentication library for Microsoft Agents
|
|
5
5
|
Author: Microsoft Corporation
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,7 @@ Classifier: Operating System :: OS Independent
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: microsoft-agents-hosting-core==1.10.0.
|
|
18
|
+
Requires-Dist: microsoft-agents-hosting-core==1.10.0.dev6
|
|
19
19
|
Requires-Dist: azure-core
|
|
20
20
|
Requires-Dist: msal>=1.34.0
|
|
21
21
|
Requires-Dist: requests>=2.32.3
|
|
@@ -253,6 +253,36 @@ def start_server(
|
|
|
253
253
|
[...]
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
## Typed Connection Settings
|
|
257
|
+
|
|
258
|
+
`MsalAuth` accepts `Configuration` or `MsalConnectionSettings` as well as the existing
|
|
259
|
+
`AgentAuthConfiguration` input. Use `provider.config` to read bound MSAL settings.
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from microsoft_agents.activity.config import Configuration
|
|
263
|
+
from microsoft_agents.authentication.msal import MsalConnectionManager
|
|
264
|
+
|
|
265
|
+
connections = MsalConnectionManager(
|
|
266
|
+
config=Configuration(
|
|
267
|
+
connections={
|
|
268
|
+
"SERVICE_CONNECTION": {
|
|
269
|
+
"settings": {
|
|
270
|
+
"auth_type": "ClientSecret",
|
|
271
|
+
"client_id": "your-client-id",
|
|
272
|
+
"tenant_id": "your-tenant-id",
|
|
273
|
+
"client_secret": "your-client-secret",
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
settings = connections.get_default_connection().config
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Load credentials from your application's secure configuration rather than hardcoding
|
|
283
|
+
them. Legacy constructor calls and the `provider.configuration` compatibility view
|
|
284
|
+
remain supported. Token credentials reuse the provider's bound settings.
|
|
285
|
+
|
|
256
286
|
## Authentication Types
|
|
257
287
|
The M365 Agents SDK in Python supports the following Auth types:
|
|
258
288
|
```python
|
|
@@ -5,6 +5,7 @@ pyproject.toml
|
|
|
5
5
|
readme.md
|
|
6
6
|
setup.py
|
|
7
7
|
microsoft_agents/authentication/msal/__init__.py
|
|
8
|
+
microsoft_agents/authentication/msal/connection_settings.py
|
|
8
9
|
microsoft_agents/authentication/msal/msal_auth.py
|
|
9
10
|
microsoft_agents/authentication/msal/msal_connection_manager.py
|
|
10
11
|
microsoft_agents/authentication/msal/msal_token_credential.py
|
|
@@ -229,6 +229,36 @@ def start_server(
|
|
|
229
229
|
[...]
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
+
## Typed Connection Settings
|
|
233
|
+
|
|
234
|
+
`MsalAuth` accepts `Configuration` or `MsalConnectionSettings` as well as the existing
|
|
235
|
+
`AgentAuthConfiguration` input. Use `provider.config` to read bound MSAL settings.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from microsoft_agents.activity.config import Configuration
|
|
239
|
+
from microsoft_agents.authentication.msal import MsalConnectionManager
|
|
240
|
+
|
|
241
|
+
connections = MsalConnectionManager(
|
|
242
|
+
config=Configuration(
|
|
243
|
+
connections={
|
|
244
|
+
"SERVICE_CONNECTION": {
|
|
245
|
+
"settings": {
|
|
246
|
+
"auth_type": "ClientSecret",
|
|
247
|
+
"client_id": "your-client-id",
|
|
248
|
+
"tenant_id": "your-tenant-id",
|
|
249
|
+
"client_secret": "your-client-secret",
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
settings = connections.get_default_connection().config
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Load credentials from your application's secure configuration rather than hardcoding
|
|
259
|
+
them. Legacy constructor calls and the `provider.configuration` compatibility view
|
|
260
|
+
remain supported. Token credentials reuse the provider's bound settings.
|
|
261
|
+
|
|
232
262
|
## Authentication Types
|
|
233
263
|
The M365 Agents SDK in Python supports the following Auth types:
|
|
234
264
|
```python
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.10.0.dev5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|