microsoft-agents-authentication-msal 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl
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/msal_auth.py +210 -3
- microsoft_agents/authentication/msal/msal_connection_manager.py +67 -5
- {microsoft_agents_authentication_msal-0.3.2.dist-info → microsoft_agents_authentication_msal-0.4.0.dist-info}/METADATA +2 -2
- microsoft_agents_authentication_msal-0.4.0.dist-info/RECORD +7 -0
- microsoft_agents_authentication_msal-0.3.2.dist-info/RECORD +0 -7
- {microsoft_agents_authentication_msal-0.3.2.dist-info → microsoft_agents_authentication_msal-0.4.0.dist-info}/WHEEL +0 -0
- {microsoft_agents_authentication_msal-0.3.2.dist-info → microsoft_agents_authentication_msal-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
1
4
|
from __future__ import annotations
|
|
2
5
|
|
|
3
6
|
import logging
|
|
7
|
+
import jwt
|
|
4
8
|
from typing import Optional
|
|
5
9
|
from urllib.parse import urlparse, ParseResult as URI
|
|
6
10
|
from msal import (
|
|
@@ -23,6 +27,18 @@ from microsoft_agents.hosting.core import (
|
|
|
23
27
|
logger = logging.getLogger(__name__)
|
|
24
28
|
|
|
25
29
|
|
|
30
|
+
# this is deferred because jwt.decode is expensive and we don't want to do it unless we
|
|
31
|
+
# have logging.DEBUG enabled
|
|
32
|
+
class _DeferredLogOfBlueprintId:
|
|
33
|
+
def __init__(self, jwt_token: str):
|
|
34
|
+
self.jwt_token = jwt_token
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
payload = jwt.decode(self.jwt_token, options={"verify_signature": False})
|
|
38
|
+
agentic_blueprint_id = payload.get("xms_par_app_azp")
|
|
39
|
+
return f"Agentic blueprint id: {agentic_blueprint_id}"
|
|
40
|
+
|
|
41
|
+
|
|
26
42
|
class MsalAuth(AccessTokenProviderBase):
|
|
27
43
|
|
|
28
44
|
_client_credential_cache = None
|
|
@@ -56,11 +72,16 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
56
72
|
auth_result_payload = msal_auth_client.acquire_token_for_client(
|
|
57
73
|
scopes=local_scopes
|
|
58
74
|
)
|
|
75
|
+
else:
|
|
76
|
+
auth_result_payload = None
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
78
|
+
res = auth_result_payload.get("access_token") if auth_result_payload else None
|
|
79
|
+
if not res:
|
|
80
|
+
logger.error("Failed to acquire token for resource %s", auth_result_payload)
|
|
81
|
+
raise ValueError(f"Failed to acquire token. {str(auth_result_payload)}")
|
|
82
|
+
return res
|
|
62
83
|
|
|
63
|
-
async def
|
|
84
|
+
async def acquire_token_on_behalf_of(
|
|
64
85
|
self, scopes: list[str], user_assertion: str
|
|
65
86
|
) -> str:
|
|
66
87
|
"""
|
|
@@ -186,3 +207,189 @@ class MsalAuth(AccessTokenProviderBase):
|
|
|
186
207
|
temp_list.append(scope_placeholder)
|
|
187
208
|
logger.debug(f"Resolved scopes: {temp_list}")
|
|
188
209
|
return temp_list
|
|
210
|
+
|
|
211
|
+
# the call to MSAL is blocking, but in the future we want to create an asyncio task
|
|
212
|
+
# to avoid this
|
|
213
|
+
async def get_agentic_application_token(
|
|
214
|
+
self, agent_app_instance_id: str
|
|
215
|
+
) -> Optional[str]:
|
|
216
|
+
"""Gets the agentic application token for the given agent application instance ID.
|
|
217
|
+
|
|
218
|
+
:param agent_app_instance_id: The agent application instance ID.
|
|
219
|
+
:type agent_app_instance_id: str
|
|
220
|
+
:return: The agentic application token, or None if not found.
|
|
221
|
+
:rtype: Optional[str]
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
if not agent_app_instance_id:
|
|
225
|
+
raise ValueError("Agent application instance Id must be provided.")
|
|
226
|
+
|
|
227
|
+
logger.info(
|
|
228
|
+
"Attempting to get agentic application token from agent_app_instance_id %s",
|
|
229
|
+
agent_app_instance_id,
|
|
230
|
+
)
|
|
231
|
+
msal_auth_client = self._create_client_application()
|
|
232
|
+
|
|
233
|
+
if isinstance(msal_auth_client, ConfidentialClientApplication):
|
|
234
|
+
|
|
235
|
+
# https://github.dev/AzureAD/microsoft-authentication-library-for-dotnet
|
|
236
|
+
auth_result_payload = msal_auth_client.acquire_token_for_client(
|
|
237
|
+
["api://AzureAdTokenExchange/.default"],
|
|
238
|
+
data={"fmi_path": agent_app_instance_id},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if auth_result_payload:
|
|
242
|
+
return auth_result_payload.get("access_token")
|
|
243
|
+
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
async def get_agentic_instance_token(
|
|
247
|
+
self, agent_app_instance_id: str
|
|
248
|
+
) -> tuple[str, str]:
|
|
249
|
+
"""Gets the agentic instance token for the given agent application instance ID.
|
|
250
|
+
|
|
251
|
+
:param agent_app_instance_id: The agent application instance ID.
|
|
252
|
+
:type agent_app_instance_id: str
|
|
253
|
+
:return: A tuple containing the agentic instance token and the agent application token.
|
|
254
|
+
:rtype: tuple[str, str]
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
if not agent_app_instance_id:
|
|
258
|
+
raise ValueError("Agent application instance Id must be provided.")
|
|
259
|
+
|
|
260
|
+
logger.info(
|
|
261
|
+
"Attempting to get agentic instance token from agent_app_instance_id %s",
|
|
262
|
+
agent_app_instance_id,
|
|
263
|
+
)
|
|
264
|
+
agent_token_result = await self.get_agentic_application_token(
|
|
265
|
+
agent_app_instance_id
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if not agent_token_result:
|
|
269
|
+
logger.error(
|
|
270
|
+
"Failed to acquire agentic instance token or agent token for agent_app_instance_id %s",
|
|
271
|
+
agent_app_instance_id,
|
|
272
|
+
)
|
|
273
|
+
raise Exception(
|
|
274
|
+
f"Failed to acquire agentic instance token or agent token for agent_app_instance_id {agent_app_instance_id}"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
authority = (
|
|
278
|
+
f"https://login.microsoftonline.com/{self._msal_configuration.TENANT_ID}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
instance_app = ConfidentialClientApplication(
|
|
282
|
+
client_id=agent_app_instance_id,
|
|
283
|
+
authority=authority,
|
|
284
|
+
client_credential={"client_assertion": agent_token_result},
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
agentic_instance_token = instance_app.acquire_token_for_client(
|
|
288
|
+
["api://AzureAdTokenExchange/.default"]
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if not agentic_instance_token:
|
|
292
|
+
logger.error(
|
|
293
|
+
"Failed to acquire agentic instance token or agent token for agent_app_instance_id %s",
|
|
294
|
+
agent_app_instance_id,
|
|
295
|
+
)
|
|
296
|
+
raise Exception(
|
|
297
|
+
f"Failed to acquire agentic instance token or agent token for agent_app_instance_id {agent_app_instance_id}"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# future scenario where we don't know the blueprint id upfront
|
|
301
|
+
|
|
302
|
+
token = agentic_instance_token.get("access_token")
|
|
303
|
+
if not token:
|
|
304
|
+
logger.error(
|
|
305
|
+
"Failed to acquire agentic instance token, %s", agentic_instance_token
|
|
306
|
+
)
|
|
307
|
+
raise ValueError(f"Failed to acquire token. {str(agentic_instance_token)}")
|
|
308
|
+
|
|
309
|
+
logger.debug(_DeferredLogOfBlueprintId(token))
|
|
310
|
+
|
|
311
|
+
return agentic_instance_token["access_token"], agent_token_result
|
|
312
|
+
|
|
313
|
+
async def get_agentic_user_token(
|
|
314
|
+
self, agent_app_instance_id: str, upn: str, scopes: list[str]
|
|
315
|
+
) -> Optional[str]:
|
|
316
|
+
"""Gets the agentic user token for the given agent application instance ID and user principal name and the scopes.
|
|
317
|
+
|
|
318
|
+
:param agent_app_instance_id: The agent application instance ID.
|
|
319
|
+
:type agent_app_instance_id: str
|
|
320
|
+
:param upn: The user principal name.
|
|
321
|
+
:type upn: str
|
|
322
|
+
:param scopes: The scopes to request for the token.
|
|
323
|
+
:type scopes: list[str]
|
|
324
|
+
:return: The agentic user token, or None if not found.
|
|
325
|
+
:rtype: Optional[str]
|
|
326
|
+
"""
|
|
327
|
+
if not agent_app_instance_id or not upn:
|
|
328
|
+
raise ValueError(
|
|
329
|
+
"Agent application instance Id and user principal name must be provided."
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
logger.info(
|
|
333
|
+
"Attempting to get agentic user token from agent_app_instance_id %s and upn %s",
|
|
334
|
+
agent_app_instance_id,
|
|
335
|
+
upn,
|
|
336
|
+
)
|
|
337
|
+
instance_token, agent_token = await self.get_agentic_instance_token(
|
|
338
|
+
agent_app_instance_id
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if not instance_token or not agent_token:
|
|
342
|
+
logger.error(
|
|
343
|
+
"Failed to acquire instance token or agent token for agent_app_instance_id %s and upn %s",
|
|
344
|
+
agent_app_instance_id,
|
|
345
|
+
upn,
|
|
346
|
+
)
|
|
347
|
+
raise Exception(
|
|
348
|
+
f"Failed to acquire instance token or agent token for agent_app_instance_id {agent_app_instance_id} and upn {upn}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
authority = (
|
|
352
|
+
f"https://login.microsoftonline.com/{self._msal_configuration.TENANT_ID}"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
instance_app = ConfidentialClientApplication(
|
|
356
|
+
client_id=agent_app_instance_id,
|
|
357
|
+
authority=authority,
|
|
358
|
+
client_credential={"client_assertion": agent_token},
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
logger.info(
|
|
362
|
+
"Acquiring agentic user token for agent_app_instance_id %s and upn %s",
|
|
363
|
+
agent_app_instance_id,
|
|
364
|
+
upn,
|
|
365
|
+
)
|
|
366
|
+
auth_result_payload = instance_app.acquire_token_for_client(
|
|
367
|
+
scopes,
|
|
368
|
+
data={
|
|
369
|
+
"username": upn,
|
|
370
|
+
"user_federated_identity_credential": instance_token,
|
|
371
|
+
"grant_type": "user_fic",
|
|
372
|
+
},
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
if not auth_result_payload:
|
|
376
|
+
logger.error(
|
|
377
|
+
"Failed to acquire agentic user token for agent_app_instance_id %s and upn %s, %s",
|
|
378
|
+
agent_app_instance_id,
|
|
379
|
+
upn,
|
|
380
|
+
auth_result_payload,
|
|
381
|
+
)
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
access_token = auth_result_payload.get("access_token")
|
|
385
|
+
if not access_token:
|
|
386
|
+
logger.error(
|
|
387
|
+
"Failed to acquire agentic user token for agent_app_instance_id %s and upn %s, %s",
|
|
388
|
+
agent_app_instance_id,
|
|
389
|
+
upn,
|
|
390
|
+
auth_result_payload,
|
|
391
|
+
)
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
logger.info("Acquired agentic user token response.")
|
|
395
|
+
return access_token
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import re
|
|
1
5
|
from typing import Dict, List, Optional
|
|
2
6
|
from microsoft_agents.hosting.core import (
|
|
3
7
|
AgentAuthConfiguration,
|
|
@@ -10,15 +14,28 @@ from .msal_auth import MsalAuth
|
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
class MsalConnectionManager(Connections):
|
|
17
|
+
_connections: Dict[str, MsalAuth]
|
|
18
|
+
_connections_map: List[Dict[str, str]]
|
|
19
|
+
_service_connection_configuration: AgentAuthConfiguration
|
|
13
20
|
|
|
14
21
|
def __init__(
|
|
15
22
|
self,
|
|
16
|
-
connections_configurations: Dict[str, AgentAuthConfiguration] = None,
|
|
17
|
-
connections_map: List[Dict[str, str]] = None,
|
|
18
|
-
**kwargs
|
|
23
|
+
connections_configurations: Optional[Dict[str, AgentAuthConfiguration]] = None,
|
|
24
|
+
connections_map: Optional[List[Dict[str, str]]] = None,
|
|
25
|
+
**kwargs,
|
|
19
26
|
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the MSAL connection manager.
|
|
29
|
+
|
|
30
|
+
:arg connections_configurations: A dictionary of connection configurations.
|
|
31
|
+
:type connections_configurations: Dict[str, AgentAuthConfiguration]
|
|
32
|
+
:arg connections_map: A list of connection mappings.
|
|
33
|
+
:type connections_map: List[Dict[str, str]]
|
|
34
|
+
:raises ValueError: If no service connection configuration is provided.
|
|
35
|
+
"""
|
|
36
|
+
|
|
20
37
|
self._connections: Dict[str, MsalAuth] = {}
|
|
21
|
-
self._connections_map = connections_map or kwargs.get("
|
|
38
|
+
self._connections_map = connections_map or kwargs.get("CONNECTIONSMAP", {})
|
|
22
39
|
self._service_connection_configuration: AgentAuthConfiguration = None
|
|
23
40
|
|
|
24
41
|
if connections_configurations:
|
|
@@ -45,13 +62,20 @@ class MsalConnectionManager(Connections):
|
|
|
45
62
|
def get_connection(self, connection_name: Optional[str]) -> AccessTokenProviderBase:
|
|
46
63
|
"""
|
|
47
64
|
Get the OAuth connection for the agent.
|
|
65
|
+
|
|
66
|
+
:arg connection_name: The name of the connection.
|
|
67
|
+
:type connection_name: str
|
|
68
|
+
:return: The OAuth connection for the agent.
|
|
69
|
+
:rtype: AccessTokenProviderBase
|
|
48
70
|
"""
|
|
71
|
+
# should never be None
|
|
49
72
|
return self._connections.get(connection_name, None)
|
|
50
73
|
|
|
51
74
|
def get_default_connection(self) -> AccessTokenProviderBase:
|
|
52
75
|
"""
|
|
53
76
|
Get the default OAuth connection for the agent.
|
|
54
77
|
"""
|
|
78
|
+
# should never be None
|
|
55
79
|
return self._connections.get("SERVICE_CONNECTION", None)
|
|
56
80
|
|
|
57
81
|
def get_token_provider(
|
|
@@ -59,11 +83,49 @@ class MsalConnectionManager(Connections):
|
|
|
59
83
|
) -> AccessTokenProviderBase:
|
|
60
84
|
"""
|
|
61
85
|
Get the OAuth token provider for the agent.
|
|
86
|
+
|
|
87
|
+
:arg claims_identity: The claims identity of the bot.
|
|
88
|
+
:type claims_identity: ClaimsIdentity
|
|
89
|
+
:arg service_url: The service URL of the bot.
|
|
90
|
+
:type service_url: str
|
|
91
|
+
:return: The OAuth token provider for the agent.
|
|
92
|
+
:rtype: AccessTokenProviderBase
|
|
93
|
+
:raises ValueError: If no connection is found for the given audience and service URL.
|
|
62
94
|
"""
|
|
95
|
+
if not claims_identity or not service_url:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"Claims identity and Service URL are required to get the token provider."
|
|
98
|
+
)
|
|
99
|
+
|
|
63
100
|
if not self._connections_map:
|
|
64
101
|
return self.get_default_connection()
|
|
65
102
|
|
|
66
|
-
|
|
103
|
+
aud = claims_identity.get_app_id() or ""
|
|
104
|
+
for item in self._connections_map:
|
|
105
|
+
audience_match = True
|
|
106
|
+
item_aud = item.get("AUDIENCE", "")
|
|
107
|
+
if item_aud:
|
|
108
|
+
audience_match = item_aud.lower() == aud.lower()
|
|
109
|
+
|
|
110
|
+
if audience_match:
|
|
111
|
+
item_service_url = item.get("SERVICEURL", "")
|
|
112
|
+
if item_service_url == "*" or item_service_url == "":
|
|
113
|
+
connection_name = item.get("CONNECTION")
|
|
114
|
+
connection = self.get_connection(connection_name)
|
|
115
|
+
if connection:
|
|
116
|
+
return connection
|
|
117
|
+
|
|
118
|
+
else:
|
|
119
|
+
res = re.match(item_service_url, service_url, re.IGNORECASE)
|
|
120
|
+
if res:
|
|
121
|
+
connection_name = item.get("CONNECTION")
|
|
122
|
+
connection = self.get_connection(connection_name)
|
|
123
|
+
if connection:
|
|
124
|
+
return connection
|
|
125
|
+
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"No connection found for audience '{aud}' and serviceUrl '{service_url}'."
|
|
128
|
+
)
|
|
67
129
|
|
|
68
130
|
def get_default_connection_configuration(self) -> AgentAuthConfiguration:
|
|
69
131
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microsoft-agents-authentication-msal
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A msal-based authentication library for Microsoft Agents
|
|
5
5
|
Author: Microsoft Corporation
|
|
6
6
|
Project-URL: Homepage, https://github.com/microsoft/Agents
|
|
@@ -8,7 +8,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Requires-Python: >=3.9
|
|
11
|
-
Requires-Dist: microsoft-agents-hosting-core==0.
|
|
11
|
+
Requires-Dist: microsoft-agents-hosting-core==0.4.0
|
|
12
12
|
Requires-Dist: msal>=1.31.1
|
|
13
13
|
Requires-Dist: requests>=2.32.3
|
|
14
14
|
Requires-Dist: cryptography>=44.0.0
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
microsoft_agents/authentication/msal/__init__.py,sha256=hjPpakL4zyqeCTEBOUCcHaRnSpG80q-L0csG5HMalYI,151
|
|
2
|
+
microsoft_agents/authentication/msal/msal_auth.py,sha256=A5CuO-JY7g03tzuuLPeKzUUQ-fsg9PLMwNe_2k7U_pY,15337
|
|
3
|
+
microsoft_agents/authentication/msal/msal_connection_manager.py,sha256=4aRJPi0uZMMnRsyewJMte5mbHiWpjnHTtHSiVr8-cyY,5258
|
|
4
|
+
microsoft_agents_authentication_msal-0.4.0.dist-info/METADATA,sha256=S6Ex-6f5bzK9y_NR9ddN77OjoxCiXFh9WDc7grjkc8c,575
|
|
5
|
+
microsoft_agents_authentication_msal-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
microsoft_agents_authentication_msal-0.4.0.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
|
|
7
|
+
microsoft_agents_authentication_msal-0.4.0.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
microsoft_agents/authentication/msal/__init__.py,sha256=hjPpakL4zyqeCTEBOUCcHaRnSpG80q-L0csG5HMalYI,151
|
|
2
|
-
microsoft_agents/authentication/msal/msal_auth.py,sha256=FNqMr7cFELTbYnboXjqBRRU2uKitS1VYx-NWv0afK6o,7518
|
|
3
|
-
microsoft_agents/authentication/msal/msal_connection_manager.py,sha256=XBgtG-9WhVXfkKKvGEAaVZjGwuqWGDKJPr-dFHOCfC0,2723
|
|
4
|
-
microsoft_agents_authentication_msal-0.3.2.dist-info/METADATA,sha256=oM1DgekEfkmA9M_pMDwihEp8SRUPp5YQFrLhHB_m6CM,575
|
|
5
|
-
microsoft_agents_authentication_msal-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
-
microsoft_agents_authentication_msal-0.3.2.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
|
|
7
|
-
microsoft_agents_authentication_msal-0.3.2.dist-info/RECORD,,
|
|
File without changes
|