Skip to content

Support ISO8601-ish formats for expires_on in Managed Identity#900

Open
Copilot wants to merge 2 commits intodevfrom
copilot/fix-expires-on-parsing
Open

Support ISO8601-ish formats for expires_on in Managed Identity#900
Copilot wants to merge 2 commits intodevfrom
copilot/fix-expires-on-parsing

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 8, 2026

Managed Identity endpoints (particularly App Service and Machine Learning) can return expires_on in non-epoch formats (e.g., 2024-10-18T19:51:37.0000000+00:00 as seen in the dotnet MSAL issue tracker), causing int() parsing to fail. This recreates PR #804 (original author left the team, CLA blocked merge).

Changes

  • msal/managed_identity.py: Adds _parse_expires_on(raw: str) -> int with a cascading parse strategy:

    1. Epoch integer (existing behavior)
    2. ISO 8601 via datetime.fromisoformat(), with regex pre-processing to strip 7-digit microseconds on Python < 3.11 and normalize Z suffix
    3. Azure SDK strftime formats: %m/%d/%Y %H:%M:%S %z and %m/%d/%Y %I:%M:%S %p %z
    4. Raises ManagedIdentityError if all attempts fail

    Replaces bare int(payload["expires_on"]) calls in _obtain_token_on_app_service() and _obtain_token_on_machine_learning() with _parse_expires_on().

  • tests/test_mi.py: Adds ExpiresOnTestCase covering all supported formats:

"1234567890"1234567890  # epoch
"2024-10-18T19:51:37.0000000+00:00"1729281097  # 7-digit microseconds (dotnet issue #4963)
"2025-01-01T00:00:00Z"1735689600  # Zulu suffix
"06/20/2019 02:57:58 +00:00"1560999478  # Azure SDK format
"1/16/2020 5:24:12 AM +00:00"1579152252  # Azure SDK AM/PM format
Original prompt

Recreate PR #804 which was blocked due to CLA issues with the original author.

This PR fixes #760 by supporting expires_on with format 2024-10-18T19:51:37.0000000+00:00 observed in microsoft-authentication-library-for-dotnet issue #4963, plus extra formats implemented in Azure SDK for Python.

Changes to implement:

  1. msal/managed_identity.py

    • Add imports: calendar, datetime, re, socket
    • Add new function _parse_expires_on(raw: str) -> int: that:
      • First tries to parse as int (epoch time)
      • Then tries ISO8601 format with microsecond handling for Python < 3.11
      • Then tries multiple strftime formats from Azure SDK for Python
      • Raises ManagedIdentityError if all parsing attempts fail
    • Update _obtain_token_on_app_service() to use _parse_expires_on() instead of int()
    • Update _obtain_token_on_machine_learning() to use _parse_expires_on() instead of int()
  2. tests/test_mi.py

    • Import _parse_expires_on
    • Add ExpiresOnTestCase test class with test_expires_on_parsing() method that tests various formats:
      • Epoch time: "1234567890" -> 1234567890
      • ISO8601 with microseconds: "1970-01-01T00:00:12.0000000+00:00" -> 12
      • ISO8601 observed in dotnet issue: "2024-10-18T19:51:37.0000000+00:00" -> 1729281097
      • Z/Zulu suffix: "2025-01-01T00:00:00Z" -> 1735689600
      • ISO8601 no fractional seconds: "2025-01-01T00:00:00+00:00" -> 1735689600
      • Strftime formats from Azure SDK:
        • "01/01/1970 00:00:12 +00:00" -> 12
        • "06/20/2019 02:57:58 +00:00" -> 1560999478
        • "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

Note: This is a recreation of PR #804 (original PR by @rayluo) to address CLA blocking issue.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: the guy left the team - can we recreate the PR as it is stuck in CLA - @AzureAD/microsoft-authentication-library-for-python/pull/804

Assistant: I'll help you understand the current state of this PR and work toward recreating it. Let me gather some information first.

Assistant: I can help you recreate this PR. The original PR #804 is stuck due to CLA (Contributor License Agreement) issues with the original author. Here's what I found:

PR Details:

  • Title: Support ISO8601-ish format on expires_on in MI
  • Original Author: @rayluo (who has left the team)
  • Status: Open, but blocked (mergeable_state: "blocked")
  • Changes: 2 files modified
    • msal/managed_identity.py (55 additions, 4 deletions)
    • tests/test_mi.py (17 additions, 1 deletion)

Summary of Changes:
The PR adds support for parsing expires_on in multiple ISO8601-ish formats in the Managed Identity module, fixing issue #760. It implements a _parse_expires_on() function that handles:

  • Epoch time format (e.g., "1234567890")
  • ISO8601 format with 7-digit microseconds (e.g., "2024-10-18T19:51:37.0000000+00:00")
  • Z/Zulu suffix format
  • Multiple strftime formats from Azure SDK for Python

Would you like me to create a new PR with these same changes? If so, please confirm:

  1. Should I base it on the same branch (dev)?
  2. Should I attribute this work or include any notes about it being a recreation of PR Support ISO8601-ish format on expires_on in MI #804?
  3. Any other modifications you'd like to make to the code?

User: Should I base it on the same branch (dev)? YES
Should I attribute this work or include any notes about it being a recreation of PR #804? YES - referecnc it
Any other modifications you'd like to make to the code? noi - keep same linmes etc

Assistant: I'm waiting for you to review and approve the pull request that's being created before I can proceed with opening it.

This pull request was created from Copilot chat.

Copilot AI requested review from Copilot and removed request for Copilot April 8, 2026 15:26
Copilot AI requested review from Copilot and removed request for Copilot April 8, 2026 15:30
Copilot AI changed the title [WIP] Fix supports expires_on with multiple formats Support ISO8601-ish formats for expires_on in Managed Identity Apr 8, 2026
Copilot AI requested a review from gladjohn April 8, 2026 15:32
@gladjohn gladjohn marked this pull request as ready for review April 11, 2026 19:39
@gladjohn gladjohn requested a review from a team as a code owner April 11, 2026 19:39
Copilot AI review requested due to automatic review settings April 11, 2026 19:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Managed Identity token handling by making expires_on parsing resilient to non-epoch formats returned by some MI endpoints (e.g., App Service, Azure ML), preventing failures when int(expires_on) would previously raise.

Changes:

  • Added _parse_expires_on(raw: str) -> int with a cascading parsing strategy (epoch → ISO8601-ish → Azure SDK-style strptime formats).
  • Updated App Service and Azure ML MI flows to compute expires_in using _parse_expires_on(...).
  • Added unit tests covering the supported expires_on formats.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
msal/managed_identity.py Adds _parse_expires_on() and switches App Service/Azure ML to use it when normalizing expires_in.
tests/test_mi.py Adds coverage for multiple expires_on string formats and expected epoch conversions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import logging
import os
import re
import socket
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

socket is imported but not used anywhere in this module (only referenced in a comment). Please remove the unused import to avoid lint/maintenance noise.

Suggested change
import socket

Copilot uses AI. Check for mistakes.
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))
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop variable name format shadows Python’s built-in format() function. Consider renaming it (e.g., fmt) to avoid shadowing and improve readability.

Suggested change
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 uses AI. Check for mistakes.
# 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))
Copy link

Copilot AI Apr 11, 2026

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.

Suggested change
return calendar.timegm(time.strptime(raw, format))
return int(datetime.datetime.strptime(raw, format).timestamp())

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +61
for input, epoch in {
"1234567890": 1234567890,
"1970-01-01T00:00:12.0000000+00:00": 12,
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name input shadows Python’s built-in input() function. Rename it (e.g., raw/expires_on) to avoid shadowing and make the test intent clearer.

Copilot uses AI. Check for mistakes.
return calendar.timegm(time.strptime(raw, format))
except ValueError:
pass
raise ManagedIdentityError(f"Cannot parse expires_on: {raw}")
Copy link

Copilot AI Apr 11, 2026

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Fix Date-Time Parsing in Token Expiration Calculation for Multiple Formats

3 participants