-
Notifications
You must be signed in to change notification settings - Fork 212
Support ISO8601-ish formats for expires_on in Managed Identity
#900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,10 +2,14 @@ | |||||||||||||||||||||||||||||||||||||
| # All rights reserved. | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # This code is licensed under the MIT License. | ||||||||||||||||||||||||||||||||||||||
| import hashlib | ||||||||||||||||||||||||||||||||||||||
| import calendar | ||||||||||||||||||||||||||||||||||||||
| import datetime | ||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||
| import socket | ||||||||||||||||||||||||||||||||||||||
| import hashlib | ||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||||||||||
| from urllib.parse import urlparse # Python 3+ | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -460,6 +464,37 @@ def _obtain_token( | |||||||||||||||||||||||||||||||||||||
| return _obtain_token_on_azure_vm(http_client, managed_identity, resource) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def _parse_expires_on(raw: str) -> int: | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| return int(raw) # It is typically an epoch time | ||||||||||||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| # '2024-10-18T19:51:37.0000000+00:00' was observed in | ||||||||||||||||||||||||||||||||||||||
| # https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4963 | ||||||||||||||||||||||||||||||||||||||
| if sys.version_info < (3, 11): # Does not support 7-digit microseconds | ||||||||||||||||||||||||||||||||||||||
| raw = re.sub( # Strip microseconds portion using regex | ||||||||||||||||||||||||||||||||||||||
| r'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d+)([+-]\d{2}:\d{2})', | ||||||||||||||||||||||||||||||||||||||
| r'\1\3', | ||||||||||||||||||||||||||||||||||||||
| raw) | ||||||||||||||||||||||||||||||||||||||
| if raw.endswith("Z"): # fromisoformat() doesn't support Z before 3.11 | ||||||||||||||||||||||||||||||||||||||
| raw = raw[:-1] + "+00:00" | ||||||||||||||||||||||||||||||||||||||
| return int(datetime.datetime.fromisoformat(raw).timestamp()) | ||||||||||||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||||||||
| for format in ( | ||||||||||||||||||||||||||||||||||||||
| "%m/%d/%Y %H:%M:%S %z", # Support "06/20/2019 02:57:58 +00:00" | ||||||||||||||||||||||||||||||||||||||
| # 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 | ||||||||||||||||||||||||||||||||||||||
| "%m/%d/%Y %I:%M:%S %p %z", # Support "1/16/2020 12:0:12 AM +00:00" | ||||||||||||||||||||||||||||||||||||||
| # 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 | ||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| return calendar.timegm(time.strptime(raw, format)) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+485
to
+492
|
||||||||||||||||||||||||||||||||||||||
| for format in ( | |
| "%m/%d/%Y %H:%M:%S %z", # Support "06/20/2019 02:57:58 +00:00" | |
| # 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 | |
| "%m/%d/%Y %I:%M:%S %p %z", # Support "1/16/2020 12:0:12 AM +00:00" | |
| # 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 | |
| ): | |
| try: | |
| return calendar.timegm(time.strptime(raw, format)) | |
| for fmt in ( | |
| "%m/%d/%Y %H:%M:%S %z", # Support "06/20/2019 02:57:58 +00:00" | |
| # 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 | |
| "%m/%d/%Y %I:%M:%S %p %z", # Support "1/16/2020 12:0:12 AM +00:00" | |
| # 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 | |
| ): | |
| try: | |
| return calendar.timegm(time.strptime(raw, fmt)) |
Copilot
AI
Apr 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calendar.timegm(time.strptime(...)) ignores the parsed %z offset (time tuples don’t apply the timezone offset when converted this way). If expires_on ever includes a non-+00:00 offset, this will compute the wrong epoch and thus wrong expires_in. Prefer parsing into a timezone-aware datetime (e.g., datetime.datetime.strptime(...).timestamp()) so offsets are honored.
| return calendar.timegm(time.strptime(raw, format)) | |
| return int(datetime.datetime.strptime(raw, format).timestamp()) |
Copilot
AI
Apr 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raw is mutated during ISO preprocessing, and the final exception reports the possibly-normalized value rather than the original input. Preserve the original string (e.g., original_raw = raw) and include that in the ManagedIdentityError message to make troubleshooting easier.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ | |
| MACHINE_LEARNING, | ||
| SERVICE_FABRIC, | ||
| DEFAULT_TO_VM, | ||
| _parse_expires_on, | ||
| ) | ||
| from msal.token_cache import is_subdict_of | ||
|
|
||
|
|
@@ -53,6 +54,22 @@ def test_helper_class_should_be_interchangable_with_dict_which_could_be_loaded_f | |
| {"ManagedIdentityIdType": "SystemAssigned", "Id": None}) | ||
|
|
||
|
|
||
| class ExpiresOnTestCase(unittest.TestCase): | ||
| def test_expires_on_parsing(self): | ||
| for input, epoch in { | ||
| "1234567890": 1234567890, | ||
| "1970-01-01T00:00:12.0000000+00:00": 12, | ||
|
Comment on lines
+59
to
+61
|
||
| "2024-10-18T19:51:37.0000000+00:00": 1729281097, # Copied from https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4963 | ||
| "2025-01-01T00:00:00Z": 1735689600, # Z/Zulu suffix | ||
| "2025-01-01T00:00:00+00:00": 1735689600, # No fractional seconds | ||
| "01/01/1970 00:00:12 +00:00": 12, | ||
| "06/20/2019 02:57:58 +00:00": 1560999478, # 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 | ||
| "1/1/1970 12:0:12 AM +00:00": 12, | ||
| "1/1/1970 12:0:12 PM +00:00": 43212, | ||
| "1/16/2020 5:24:12 AM +00:00": 1579152252, # 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 | ||
| }.items(): | ||
| self.assertEqual(_parse_expires_on(input), epoch, f'Should parse "{input}" to {epoch}') | ||
|
|
||
| class ThrottledHttpClientTestCase(ThrottledHttpClientBaseTestCase): | ||
| def test_throttled_http_client_should_not_alter_original_http_client(self): | ||
| self.assertNotAlteringOriginalHttpClient(_ThrottledHttpClient) | ||
|
|
@@ -83,7 +100,6 @@ def test_throttled_http_client_should_cache_unsuccessful_http_response(self): | |
| self.assertNotEqual({}, http_cache, "Should cache unsuccessful http response") | ||
| self.assertCleanPickle(http_cache) | ||
|
|
||
|
|
||
| class ClientTestCase(unittest.TestCase): | ||
| maxDiff = None | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
socketis imported but not used anywhere in this module (only referenced in a comment). Please remove the unused import to avoid lint/maintenance noise.