diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py b/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py index a94e001852ec..87aab2420843 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py @@ -2,7 +2,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from datetime import datetime +from datetime import datetime, timedelta import json import os import platform @@ -91,7 +91,10 @@ def parse_token(output): """ try: token = json.loads(output) - dt = datetime.strptime(token["expiresOn"], "%Y-%m-%d %H:%M:%S.%f") + expires_on_in_token = token["expiresOn"] + if not expires_on_in_token: + expires_on_in_token = (datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S.%f") + dt = datetime.strptime(expires_on_in_token, "%Y-%m-%d %H:%M:%S.%f") if hasattr(dt, "timestamp"): # Python >= 3.3 expires_on = dt.timestamp() diff --git a/sdk/identity/azure-identity/tests/test_cli_credential.py b/sdk/identity/azure-identity/tests/test_cli_credential.py index 6eb71b97e722..766635e19a5c 100644 --- a/sdk/identity/azure-identity/tests/test_cli_credential.py +++ b/sdk/identity/azure-identity/tests/test_cli_credential.py @@ -2,7 +2,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from datetime import datetime +from datetime import datetime, timedelta import json import re import sys @@ -214,3 +214,25 @@ def fake_check_output(command_line, **_): ): token = credential.get_token("scope", tenant_id="un" + expected_tenant) assert token.token == expected_token + +def test_no_expireson_token(): + """The credential should parse the CLI's output to an AccessToken even expiresOn is None""" + + access_token = "access token" + expected_expires_on = int(datetime.timestamp(datetime.now()+timedelta(days=1))) + no_expireon_output = json.dumps( + { + "expiresOn": None, + "accessToken": access_token, + "subscription": "some-guid", + "tenant": "some-guid", + "tokenType": "Bearer", + } + ) + + with mock.patch(CHECK_OUTPUT, mock.Mock(return_value=no_expireon_output)): + token = AzureCliCredential().get_token("scope") + + assert token.token == access_token + assert type(token.expires_on) == int + assert token.expires_on == expected_expires_on diff --git a/sdk/identity/azure-identity/tests/test_cli_credential_async.py b/sdk/identity/azure-identity/tests/test_cli_credential_async.py index 2276a1bff3e5..ed4b2cb80f73 100644 --- a/sdk/identity/azure-identity/tests/test_cli_credential_async.py +++ b/sdk/identity/azure-identity/tests/test_cli_credential_async.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. # ------------------------------------ import asyncio -from datetime import datetime +from datetime import datetime, timedelta import json import re import sys @@ -247,3 +247,27 @@ async def fake_exec(*args, **_): with mock.patch.dict("os.environ", {EnvironmentVariables.AZURE_IDENTITY_DISABLE_MULTITENANTAUTH: "true"}): token = await credential.get_token("scope", tenant_id="un" + expected_tenant) assert token.token == expected_token + + +async def test_no_expireson_token(): + """The credential should parse the CLI's output to an AccessToken even expiresOn is None""" + + access_token = "access token" + expected_expires_on = int(datetime.timestamp(datetime.now()+timedelta(days=1))) + successful_output = json.dumps( + { + "expiresOn": None, + "accessToken": access_token, + "subscription": "some-guid", + "tenant": "some-guid", + "tokenType": "Bearer", + } + ) + + with mock.patch(SUBPROCESS_EXEC, mock_exec(successful_output)): + credential = AzureCliCredential() + token = await credential.get_token("scope") + + assert token.token == access_token + assert type(token.expires_on) == int + assert token.expires_on == expected_expires_on \ No newline at end of file