auth0-api-python 1.0.0b7__tar.gz → 1.0.0b9__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.
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/PKG-INFO +134 -5
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/README.md +133 -5
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/pyproject.toml +1 -1
- auth0_api_python-1.0.0b9/src/auth0_api_python/__init__.py +38 -0
- auth0_api_python-1.0.0b9/src/auth0_api_python/act.py +64 -0
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/src/auth0_api_python/api_client.py +384 -37
- auth0_api_python-1.0.0b9/src/auth0_api_python/cache.py +168 -0
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/src/auth0_api_python/config.py +26 -6
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/src/auth0_api_python/errors.py +20 -0
- auth0_api_python-1.0.0b9/src/auth0_api_python/types.py +87 -0
- auth0_api_python-1.0.0b9/src/auth0_api_python/utils.py +273 -0
- auth0_api_python-1.0.0b7/src/auth0_api_python/__init__.py +0 -17
- auth0_api_python-1.0.0b7/src/auth0_api_python/utils.py +0 -157
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/LICENSE +0 -0
- {auth0_api_python-1.0.0b7 → auth0_api_python-1.0.0b9}/src/auth0_api_python/token_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: auth0-api-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0b9
|
|
4
4
|
Summary: SDK for verifying access tokens and securing APIs with Auth0, using Authlib.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -41,7 +41,8 @@ This SDK provides comprehensive support for securing APIs with Auth0-issued acce
|
|
|
41
41
|
|
|
42
42
|
### **Core Features**
|
|
43
43
|
- **Unified Entry Point**: `verify_request()` - automatically detects and validates Bearer or DPoP schemes
|
|
44
|
-
- **
|
|
44
|
+
- **Multi-Custom Domain (MCD)** - Accept tokens from multiple Auth0 domains with static lists or dynamic resolvers
|
|
45
|
+
- **OIDC Discovery** - Automatic fetching of Auth0 metadata and JWKS with per-issuer caching
|
|
45
46
|
- **JWT Validation** - Complete RS256 signature verification with claim validation
|
|
46
47
|
- **DPoP Proof Verification** - Full RFC 9449 compliance with ES256 signature validation
|
|
47
48
|
- **Flexible Configuration** - Support for both "Allowed" and "Required" DPoP modes
|
|
@@ -235,6 +236,92 @@ except ApiError as e:
|
|
|
235
236
|
|
|
236
237
|
More info: https://auth0.com/docs/authenticate/custom-token-exchange
|
|
237
238
|
|
|
239
|
+
#### On Behalf Of Token Exchange
|
|
240
|
+
|
|
241
|
+
Use `get_token_on_behalf_of()` when your API receives an `Auth0` access token for itself and needs
|
|
242
|
+
to exchange it for another `Auth0` access token targeting a downstream API while preserving the
|
|
243
|
+
same user identity. This is especially useful for `MCP` servers and other intermediary APIs that
|
|
244
|
+
need to call downstream APIs on behalf of the user.
|
|
245
|
+
|
|
246
|
+
The following example verifies the incoming access token for your API, exchanges it for a token for the downstream API, and then calls the downstream API with the exchanged token.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
import httpx
|
|
250
|
+
|
|
251
|
+
async def handle_calendar_request(incoming_access_token: str):
|
|
252
|
+
await api_client.verify_access_token(access_token=incoming_access_token)
|
|
253
|
+
|
|
254
|
+
result = await api_client.get_token_on_behalf_of(
|
|
255
|
+
access_token=incoming_access_token,
|
|
256
|
+
audience="https://calendar-api.example.com",
|
|
257
|
+
scope="calendar:read calendar:write"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
async with httpx.AsyncClient() as client:
|
|
261
|
+
downstream_response = await client.get(
|
|
262
|
+
"https://calendar-api.example.com/events",
|
|
263
|
+
headers={"Authorization": f"Bearer {result['access_token']}"}
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
downstream_response.raise_for_status()
|
|
267
|
+
|
|
268
|
+
return downstream_response.json()
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The OBO wrapper reuses the existing RFC 8693 exchange support and fixes both token-type parameters
|
|
272
|
+
to Auth0 access-token exchange. In the current implementation, the SDK forwards the incoming access
|
|
273
|
+
token as the `subject_token` and relies on Auth0 to handle any DPoP-specific behavior for that token.
|
|
274
|
+
The OBO result only includes access-token-oriented fields. It does not expose `id_token` or
|
|
275
|
+
`refresh_token`.
|
|
276
|
+
|
|
277
|
+
#### Inspecting Delegation After Token Verification
|
|
278
|
+
|
|
279
|
+
When a downstream API or `MCP` server receives an access token that may have been issued through
|
|
280
|
+
delegation, it can verify the token first and then inspect the `act` claim to identify the current
|
|
281
|
+
actor for authorization and the full delegation chain for logging or audit.
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
import logging
|
|
285
|
+
|
|
286
|
+
from auth0_api_python import (
|
|
287
|
+
ApiClient,
|
|
288
|
+
ApiClientOptions,
|
|
289
|
+
get_current_actor,
|
|
290
|
+
get_delegation_chain,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
logger = logging.getLogger(__name__)
|
|
294
|
+
|
|
295
|
+
api_client = ApiClient(ApiClientOptions(
|
|
296
|
+
domain="<AUTH0_DOMAIN>",
|
|
297
|
+
audience="<AUTH0_AUDIENCE>",
|
|
298
|
+
))
|
|
299
|
+
|
|
300
|
+
async def authorize_delegated_request(access_token: str):
|
|
301
|
+
claims = await api_client.verify_access_token(access_token=access_token)
|
|
302
|
+
|
|
303
|
+
current_actor = get_current_actor(claims)
|
|
304
|
+
delegation_chain = get_delegation_chain(claims)
|
|
305
|
+
|
|
306
|
+
if current_actor != "mcp_server_client_id":
|
|
307
|
+
raise PermissionError("unexpected actor")
|
|
308
|
+
|
|
309
|
+
logger.info(
|
|
310
|
+
"delegated request",
|
|
311
|
+
extra={
|
|
312
|
+
"user_sub": claims["sub"],
|
|
313
|
+
"current_actor": current_actor,
|
|
314
|
+
"delegation_chain": delegation_chain,
|
|
315
|
+
},
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
return claims
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Only the outermost `act.sub` represents the current actor and should be used for authorization
|
|
322
|
+
decisions. Nested `act` values represent prior actors in the delegation chain and are better suited
|
|
323
|
+
for logging, audit, or attribution.
|
|
324
|
+
|
|
238
325
|
#### Requiring Additional Claims
|
|
239
326
|
|
|
240
327
|
If your application demands extra claims, specify them with `required_claims`:
|
|
@@ -250,9 +337,6 @@ If the token lacks `my_custom_claim` or fails any standard check (issuer mismatc
|
|
|
250
337
|
|
|
251
338
|
### 6. DPoP Authentication
|
|
252
339
|
|
|
253
|
-
> [!NOTE]
|
|
254
|
-
> This feature is currently available in [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). Please reach out to Auth0 support to get it enabled for your tenant.
|
|
255
|
-
|
|
256
340
|
This library supports **DPoP (Demonstrating Proof-of-Possession)** for enhanced security, allowing clients to prove possession of private keys bound to access tokens.
|
|
257
341
|
|
|
258
342
|
#### Allowed Mode (Default)
|
|
@@ -303,6 +387,50 @@ api_client = ApiClient(ApiClientOptions(
|
|
|
303
387
|
))
|
|
304
388
|
```
|
|
305
389
|
|
|
390
|
+
### 7. Multi-Custom Domain (MCD) Support
|
|
391
|
+
|
|
392
|
+
If your Auth0 tenant has multiple custom domains, or you're migrating between domains, the SDK can accept tokens from any of them:
|
|
393
|
+
|
|
394
|
+
#### Static Domain List
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
from auth0_api_python import ApiClient, ApiClientOptions
|
|
398
|
+
|
|
399
|
+
api_client = ApiClient(ApiClientOptions(
|
|
400
|
+
domains=[
|
|
401
|
+
"tenant.auth0.com",
|
|
402
|
+
"auth.example.com",
|
|
403
|
+
"auth.acme.org"
|
|
404
|
+
],
|
|
405
|
+
audience="https://api.example.com"
|
|
406
|
+
))
|
|
407
|
+
|
|
408
|
+
# Tokens from any of the three domains are accepted
|
|
409
|
+
claims = await api_client.verify_access_token(access_token)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### Dynamic Resolver
|
|
413
|
+
|
|
414
|
+
For runtime domain resolution based on request context:
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from auth0_api_python import ApiClient, ApiClientOptions, DomainsResolverContext
|
|
418
|
+
|
|
419
|
+
def resolve_domains(context: DomainsResolverContext) -> list[str]:
|
|
420
|
+
# Determine allowed domains based on the request
|
|
421
|
+
return ["tenant.auth0.com", "auth.example.com"]
|
|
422
|
+
|
|
423
|
+
api_client = ApiClient(ApiClientOptions(
|
|
424
|
+
domains=resolve_domains,
|
|
425
|
+
audience="https://api.example.com"
|
|
426
|
+
))
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
For hybrid mode (migration scenarios), resolver patterns, error handling, and caching configuration, see the full guides:
|
|
430
|
+
|
|
431
|
+
- **[Multi-Custom Domain Guide](docs/MultipleCustomDomain.md)** - Configuration modes, resolver patterns, migration, error handling
|
|
432
|
+
- **[Caching Guide](docs/Caching.md)** - Cache tuning, custom adapters (Redis, Memcached)
|
|
433
|
+
|
|
306
434
|
## Feedback
|
|
307
435
|
|
|
308
436
|
### Contributing
|
|
@@ -336,3 +464,4 @@ Please do not report security vulnerabilities on the public GitHub issue tracker
|
|
|
336
464
|
<p align="center">
|
|
337
465
|
This project is licensed under the MIT license. See the <a href="https://github.com/auth0/auth0-api-python/LICENSE"> LICENSE</a> file for more info.
|
|
338
466
|
</p>
|
|
467
|
+
|
|
@@ -17,7 +17,8 @@ This SDK provides comprehensive support for securing APIs with Auth0-issued acce
|
|
|
17
17
|
|
|
18
18
|
### **Core Features**
|
|
19
19
|
- **Unified Entry Point**: `verify_request()` - automatically detects and validates Bearer or DPoP schemes
|
|
20
|
-
- **
|
|
20
|
+
- **Multi-Custom Domain (MCD)** - Accept tokens from multiple Auth0 domains with static lists or dynamic resolvers
|
|
21
|
+
- **OIDC Discovery** - Automatic fetching of Auth0 metadata and JWKS with per-issuer caching
|
|
21
22
|
- **JWT Validation** - Complete RS256 signature verification with claim validation
|
|
22
23
|
- **DPoP Proof Verification** - Full RFC 9449 compliance with ES256 signature validation
|
|
23
24
|
- **Flexible Configuration** - Support for both "Allowed" and "Required" DPoP modes
|
|
@@ -211,6 +212,92 @@ except ApiError as e:
|
|
|
211
212
|
|
|
212
213
|
More info: https://auth0.com/docs/authenticate/custom-token-exchange
|
|
213
214
|
|
|
215
|
+
#### On Behalf Of Token Exchange
|
|
216
|
+
|
|
217
|
+
Use `get_token_on_behalf_of()` when your API receives an `Auth0` access token for itself and needs
|
|
218
|
+
to exchange it for another `Auth0` access token targeting a downstream API while preserving the
|
|
219
|
+
same user identity. This is especially useful for `MCP` servers and other intermediary APIs that
|
|
220
|
+
need to call downstream APIs on behalf of the user.
|
|
221
|
+
|
|
222
|
+
The following example verifies the incoming access token for your API, exchanges it for a token for the downstream API, and then calls the downstream API with the exchanged token.
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
import httpx
|
|
226
|
+
|
|
227
|
+
async def handle_calendar_request(incoming_access_token: str):
|
|
228
|
+
await api_client.verify_access_token(access_token=incoming_access_token)
|
|
229
|
+
|
|
230
|
+
result = await api_client.get_token_on_behalf_of(
|
|
231
|
+
access_token=incoming_access_token,
|
|
232
|
+
audience="https://calendar-api.example.com",
|
|
233
|
+
scope="calendar:read calendar:write"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
async with httpx.AsyncClient() as client:
|
|
237
|
+
downstream_response = await client.get(
|
|
238
|
+
"https://calendar-api.example.com/events",
|
|
239
|
+
headers={"Authorization": f"Bearer {result['access_token']}"}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
downstream_response.raise_for_status()
|
|
243
|
+
|
|
244
|
+
return downstream_response.json()
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The OBO wrapper reuses the existing RFC 8693 exchange support and fixes both token-type parameters
|
|
248
|
+
to Auth0 access-token exchange. In the current implementation, the SDK forwards the incoming access
|
|
249
|
+
token as the `subject_token` and relies on Auth0 to handle any DPoP-specific behavior for that token.
|
|
250
|
+
The OBO result only includes access-token-oriented fields. It does not expose `id_token` or
|
|
251
|
+
`refresh_token`.
|
|
252
|
+
|
|
253
|
+
#### Inspecting Delegation After Token Verification
|
|
254
|
+
|
|
255
|
+
When a downstream API or `MCP` server receives an access token that may have been issued through
|
|
256
|
+
delegation, it can verify the token first and then inspect the `act` claim to identify the current
|
|
257
|
+
actor for authorization and the full delegation chain for logging or audit.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import logging
|
|
261
|
+
|
|
262
|
+
from auth0_api_python import (
|
|
263
|
+
ApiClient,
|
|
264
|
+
ApiClientOptions,
|
|
265
|
+
get_current_actor,
|
|
266
|
+
get_delegation_chain,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
logger = logging.getLogger(__name__)
|
|
270
|
+
|
|
271
|
+
api_client = ApiClient(ApiClientOptions(
|
|
272
|
+
domain="<AUTH0_DOMAIN>",
|
|
273
|
+
audience="<AUTH0_AUDIENCE>",
|
|
274
|
+
))
|
|
275
|
+
|
|
276
|
+
async def authorize_delegated_request(access_token: str):
|
|
277
|
+
claims = await api_client.verify_access_token(access_token=access_token)
|
|
278
|
+
|
|
279
|
+
current_actor = get_current_actor(claims)
|
|
280
|
+
delegation_chain = get_delegation_chain(claims)
|
|
281
|
+
|
|
282
|
+
if current_actor != "mcp_server_client_id":
|
|
283
|
+
raise PermissionError("unexpected actor")
|
|
284
|
+
|
|
285
|
+
logger.info(
|
|
286
|
+
"delegated request",
|
|
287
|
+
extra={
|
|
288
|
+
"user_sub": claims["sub"],
|
|
289
|
+
"current_actor": current_actor,
|
|
290
|
+
"delegation_chain": delegation_chain,
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return claims
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Only the outermost `act.sub` represents the current actor and should be used for authorization
|
|
298
|
+
decisions. Nested `act` values represent prior actors in the delegation chain and are better suited
|
|
299
|
+
for logging, audit, or attribution.
|
|
300
|
+
|
|
214
301
|
#### Requiring Additional Claims
|
|
215
302
|
|
|
216
303
|
If your application demands extra claims, specify them with `required_claims`:
|
|
@@ -226,9 +313,6 @@ If the token lacks `my_custom_claim` or fails any standard check (issuer mismatc
|
|
|
226
313
|
|
|
227
314
|
### 6. DPoP Authentication
|
|
228
315
|
|
|
229
|
-
> [!NOTE]
|
|
230
|
-
> This feature is currently available in [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). Please reach out to Auth0 support to get it enabled for your tenant.
|
|
231
|
-
|
|
232
316
|
This library supports **DPoP (Demonstrating Proof-of-Possession)** for enhanced security, allowing clients to prove possession of private keys bound to access tokens.
|
|
233
317
|
|
|
234
318
|
#### Allowed Mode (Default)
|
|
@@ -279,6 +363,50 @@ api_client = ApiClient(ApiClientOptions(
|
|
|
279
363
|
))
|
|
280
364
|
```
|
|
281
365
|
|
|
366
|
+
### 7. Multi-Custom Domain (MCD) Support
|
|
367
|
+
|
|
368
|
+
If your Auth0 tenant has multiple custom domains, or you're migrating between domains, the SDK can accept tokens from any of them:
|
|
369
|
+
|
|
370
|
+
#### Static Domain List
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from auth0_api_python import ApiClient, ApiClientOptions
|
|
374
|
+
|
|
375
|
+
api_client = ApiClient(ApiClientOptions(
|
|
376
|
+
domains=[
|
|
377
|
+
"tenant.auth0.com",
|
|
378
|
+
"auth.example.com",
|
|
379
|
+
"auth.acme.org"
|
|
380
|
+
],
|
|
381
|
+
audience="https://api.example.com"
|
|
382
|
+
))
|
|
383
|
+
|
|
384
|
+
# Tokens from any of the three domains are accepted
|
|
385
|
+
claims = await api_client.verify_access_token(access_token)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Dynamic Resolver
|
|
389
|
+
|
|
390
|
+
For runtime domain resolution based on request context:
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
from auth0_api_python import ApiClient, ApiClientOptions, DomainsResolverContext
|
|
394
|
+
|
|
395
|
+
def resolve_domains(context: DomainsResolverContext) -> list[str]:
|
|
396
|
+
# Determine allowed domains based on the request
|
|
397
|
+
return ["tenant.auth0.com", "auth.example.com"]
|
|
398
|
+
|
|
399
|
+
api_client = ApiClient(ApiClientOptions(
|
|
400
|
+
domains=resolve_domains,
|
|
401
|
+
audience="https://api.example.com"
|
|
402
|
+
))
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
For hybrid mode (migration scenarios), resolver patterns, error handling, and caching configuration, see the full guides:
|
|
406
|
+
|
|
407
|
+
- **[Multi-Custom Domain Guide](docs/MultipleCustomDomain.md)** - Configuration modes, resolver patterns, migration, error handling
|
|
408
|
+
- **[Caching Guide](docs/Caching.md)** - Cache tuning, custom adapters (Redis, Memcached)
|
|
409
|
+
|
|
282
410
|
## Feedback
|
|
283
411
|
|
|
284
412
|
### Contributing
|
|
@@ -311,4 +439,4 @@ Please do not report security vulnerabilities on the public GitHub issue tracker
|
|
|
311
439
|
</p>
|
|
312
440
|
<p align="center">
|
|
313
441
|
This project is licensed under the MIT license. See the <a href="https://github.com/auth0/auth0-api-python/LICENSE"> LICENSE</a> file for more info.
|
|
314
|
-
</p>
|
|
442
|
+
</p>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
auth0-api-python
|
|
3
|
+
|
|
4
|
+
A lightweight Python SDK for verifying Auth0-issued access tokens
|
|
5
|
+
in server-side APIs, using Authlib for OIDC discovery and JWKS fetching.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .act import get_current_actor, get_delegation_chain
|
|
9
|
+
from .api_client import ApiClient
|
|
10
|
+
from .cache import CacheAdapter, InMemoryCache
|
|
11
|
+
from .config import ApiClientOptions
|
|
12
|
+
from .errors import (
|
|
13
|
+
ApiError,
|
|
14
|
+
ConfigurationError,
|
|
15
|
+
DomainsResolverError,
|
|
16
|
+
GetTokenByExchangeProfileError,
|
|
17
|
+
)
|
|
18
|
+
from .types import (
|
|
19
|
+
DomainsResolver,
|
|
20
|
+
DomainsResolverContext,
|
|
21
|
+
OnBehalfOfTokenResult,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"ApiClient",
|
|
26
|
+
"ApiClientOptions",
|
|
27
|
+
"ApiError",
|
|
28
|
+
"CacheAdapter",
|
|
29
|
+
"ConfigurationError",
|
|
30
|
+
"DomainsResolver",
|
|
31
|
+
"DomainsResolverContext",
|
|
32
|
+
"DomainsResolverError",
|
|
33
|
+
"GetTokenByExchangeProfileError",
|
|
34
|
+
"get_current_actor",
|
|
35
|
+
"get_delegation_chain",
|
|
36
|
+
"InMemoryCache",
|
|
37
|
+
"OnBehalfOfTokenResult",
|
|
38
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helpers for working with the `act` claim on verified access token claims.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from .errors import VerifyAccessTokenError
|
|
9
|
+
|
|
10
|
+
INVALID_ACT_CLAIM_MESSAGE = "Invalid act claim"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_current_actor(claims: Mapping[str, Any]) -> Optional[str]:
|
|
14
|
+
"""
|
|
15
|
+
Return the current actor from the outermost `act.sub`, if present.
|
|
16
|
+
|
|
17
|
+
Only the outermost `act.sub` should be used for authorization decisions.
|
|
18
|
+
Nested `act` values represent prior actors and are informational.
|
|
19
|
+
"""
|
|
20
|
+
if not isinstance(claims, Mapping):
|
|
21
|
+
raise VerifyAccessTokenError(INVALID_ACT_CLAIM_MESSAGE)
|
|
22
|
+
|
|
23
|
+
act_claim = claims.get("act")
|
|
24
|
+
if act_claim is None:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
if not isinstance(act_claim, Mapping):
|
|
28
|
+
raise VerifyAccessTokenError(INVALID_ACT_CLAIM_MESSAGE)
|
|
29
|
+
|
|
30
|
+
sub = act_claim.get("sub")
|
|
31
|
+
if not isinstance(sub, str) or not sub.strip():
|
|
32
|
+
raise VerifyAccessTokenError(INVALID_ACT_CLAIM_MESSAGE)
|
|
33
|
+
|
|
34
|
+
return sub
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_delegation_chain(claims: Mapping[str, Any]) -> list[str]:
|
|
38
|
+
"""
|
|
39
|
+
Return the delegation chain from newest actor to oldest actor.
|
|
40
|
+
|
|
41
|
+
The first entry is the current actor (outermost `act.sub`). Later entries are
|
|
42
|
+
prior actors from nested `act` values and are typically most useful for audit
|
|
43
|
+
and attribution.
|
|
44
|
+
"""
|
|
45
|
+
if not isinstance(claims, Mapping):
|
|
46
|
+
raise VerifyAccessTokenError(INVALID_ACT_CLAIM_MESSAGE)
|
|
47
|
+
|
|
48
|
+
current = claims.get("act")
|
|
49
|
+
if current is None:
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
chain: list[str] = []
|
|
53
|
+
while current is not None:
|
|
54
|
+
if not isinstance(current, Mapping):
|
|
55
|
+
raise VerifyAccessTokenError(INVALID_ACT_CLAIM_MESSAGE)
|
|
56
|
+
|
|
57
|
+
sub = current.get("sub")
|
|
58
|
+
if not isinstance(sub, str) or not sub.strip():
|
|
59
|
+
raise VerifyAccessTokenError(INVALID_ACT_CLAIM_MESSAGE)
|
|
60
|
+
|
|
61
|
+
chain.append(sub)
|
|
62
|
+
current = current.get("act")
|
|
63
|
+
|
|
64
|
+
return chain
|