|
2 | 2 | # All rights reserved. |
3 | 3 | # |
4 | 4 | # This code is licensed under the MIT License. |
5 | | -import hashlib |
| 5 | +import calendar |
| 6 | +import datetime |
6 | 7 | import json |
7 | 8 | import logging |
8 | 9 | import os |
| 10 | +import re |
| 11 | +import socket |
| 12 | +import hashlib |
9 | 13 | import sys |
10 | 14 | import time |
11 | 15 | from urllib.parse import urlparse # Python 3+ |
@@ -460,6 +464,37 @@ def _obtain_token( |
460 | 464 | return _obtain_token_on_azure_vm(http_client, managed_identity, resource) |
461 | 465 |
|
462 | 466 |
|
| 467 | +def _parse_expires_on(raw: str) -> int: |
| 468 | + try: |
| 469 | + return int(raw) # It is typically an epoch time |
| 470 | + except ValueError: |
| 471 | + pass |
| 472 | + try: |
| 473 | + # '2024-10-18T19:51:37.0000000+00:00' was observed in |
| 474 | + # https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4963 |
| 475 | + if sys.version_info < (3, 11): # Does not support 7-digit microseconds |
| 476 | + raw = re.sub( # Strip microseconds portion using regex |
| 477 | + r'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d+)([+-]\d{2}:\d{2})', |
| 478 | + r'\1\3', |
| 479 | + raw) |
| 480 | + if raw.endswith("Z"): # fromisoformat() doesn't support Z before 3.11 |
| 481 | + raw = raw[:-1] + "+00:00" |
| 482 | + return int(datetime.datetime.fromisoformat(raw).timestamp()) |
| 483 | + except ValueError: |
| 484 | + pass |
| 485 | + for format in ( |
| 486 | + "%m/%d/%Y %H:%M:%S %z", # Support "06/20/2019 02:57:58 +00:00" |
| 487 | + # Derived from https://github.com/Azure/azure-sdk-for-python/blob/azure-identity_1.21.0/sdk/identity/azure-identity/azure/identity/_credentials/azure_ml.py#L52 |
| 488 | + "%m/%d/%Y %I:%M:%S %p %z", # Support "1/16/2020 12:0:12 AM +00:00" |
| 489 | + # Derived from https://github.com/Azure/azure-sdk-for-python/blob/azure-identity_1.21.0/sdk/identity/azure-identity/azure/identity/_credentials/azure_ml.py#L51 |
| 490 | + ): |
| 491 | + try: |
| 492 | + return calendar.timegm(time.strptime(raw, format)) |
| 493 | + except ValueError: |
| 494 | + pass |
| 495 | + raise ManagedIdentityError(f"Cannot parse expires_on: {raw}") |
| 496 | + |
| 497 | + |
463 | 498 | def _adjust_param(params, managed_identity, types_mapping=None): |
464 | 499 | # Modify the params dict in place |
465 | 500 | id_name = (types_mapping or ManagedIdentity._types_mapping).get( |
@@ -532,7 +567,7 @@ def _obtain_token_on_app_service( |
532 | 567 | if payload.get("access_token") and payload.get("expires_on"): |
533 | 568 | return { # Normalizing the payload into OAuth2 format |
534 | 569 | "access_token": payload["access_token"], |
535 | | - "expires_in": int(payload["expires_on"]) - int(time.time()), |
| 570 | + "expires_in": _parse_expires_on(payload["expires_on"]) - int(time.time()), |
536 | 571 | "resource": payload.get("resource"), |
537 | 572 | "token_type": payload.get("token_type", "Bearer"), |
538 | 573 | } |
@@ -566,7 +601,7 @@ def _obtain_token_on_machine_learning( |
566 | 601 | if payload.get("access_token") and payload.get("expires_on"): |
567 | 602 | return { # Normalizing the payload into OAuth2 format |
568 | 603 | "access_token": payload["access_token"], |
569 | | - "expires_in": int(payload["expires_on"]) - int(time.time()), |
| 604 | + "expires_in": _parse_expires_on(payload["expires_on"]) - int(time.time()), |
570 | 605 | "resource": payload.get("resource"), |
571 | 606 | "token_type": payload.get("token_type", "Bearer"), |
572 | 607 | } |
|
0 commit comments