From 5210814ee9f60844d42249552c76ab7a5dcacde9 Mon Sep 17 00:00:00 2001 From: Charles Lowell <10964656+chlowell@users.noreply.github.com> Date: Fri, 6 Jan 2023 08:53:29 -0800 Subject: [PATCH] AzureCLICredential reports token expiration time in UTC (#19761) --- sdk/azidentity/CHANGELOG.md | 1 + sdk/azidentity/azure_cli_credential.go | 23 +++--------------- sdk/azidentity/azure_cli_credential_test.go | 27 ++++++++++++--------- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/sdk/azidentity/CHANGELOG.md b/sdk/azidentity/CHANGELOG.md index 95ffa1ee144d..68147b108f8d 100644 --- a/sdk/azidentity/CHANGELOG.md +++ b/sdk/azidentity/CHANGELOG.md @@ -9,6 +9,7 @@ ### Breaking Changes ### Bugs Fixed +* `AzureCLICredential` reports token expiration in local time (should be UTC) ### Other Changes * `AzureCLICredential` imposes its default timeout only when the `Context` diff --git a/sdk/azidentity/azure_cli_credential.go b/sdk/azidentity/azure_cli_credential.go index b76246085068..3b0083a9b1da 100644 --- a/sdk/azidentity/azure_cli_credential.go +++ b/sdk/azidentity/azure_cli_credential.go @@ -162,32 +162,17 @@ func (c *AzureCLICredential) createAccessToken(tk []byte) (azcore.AccessToken, e return azcore.AccessToken{}, err } - tokenExpirationDate, err := parseExpirationDate(t.ExpiresOn) + // the Azure CLI's "expiresOn" is local time + exp, err := time.ParseInLocation("2006-01-02 15:04:05.999999", t.ExpiresOn, time.Local) if err != nil { - return azcore.AccessToken{}, fmt.Errorf("Error parsing Token Expiration Date %q: %+v", t.ExpiresOn, err) + return azcore.AccessToken{}, fmt.Errorf("Error parsing token expiration time %q: %v", t.ExpiresOn, err) } converted := azcore.AccessToken{ Token: t.AccessToken, - ExpiresOn: *tokenExpirationDate, + ExpiresOn: exp.UTC(), } return converted, nil } -// parseExpirationDate parses either a Azure CLI or CloudShell date into a time object -func parseExpirationDate(input string) (*time.Time, error) { - // CloudShell (and potentially the Azure CLI in future) - expirationDate, cloudShellErr := time.Parse(time.RFC3339, input) - if cloudShellErr != nil { - // Azure CLI (Python) e.g. 2017-08-31 19:48:57.998857 (plus the local timezone) - const cliFormat = "2006-01-02 15:04:05.999999" - expirationDate, cliErr := time.ParseInLocation(cliFormat, input, time.Local) - if cliErr != nil { - return nil, fmt.Errorf("Error parsing expiration date %q.\n\nCloudShell Error: \n%+v\n\nCLI Error:\n%+v", input, cloudShellErr, cliErr) - } - return &expirationDate, nil - } - return &expirationDate, nil -} - var _ azcore.TokenCredential = (*AzureCLICredential)(nil) diff --git a/sdk/azidentity/azure_cli_credential_test.go b/sdk/azidentity/azure_cli_credential_test.go index bd7a5e83c8aa..17f42fc87c37 100644 --- a/sdk/azidentity/azure_cli_credential_test.go +++ b/sdk/azidentity/azure_cli_credential_test.go @@ -10,17 +10,21 @@ import ( "context" "errors" "testing" + "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" ) var ( mockCLITokenProviderSuccess = func(ctx context.Context, resource string, tenantID string) ([]byte, error) { - return []byte(" {\"accessToken\":\"mocktoken\" , " + - "\"expiresOn\": \"2007-01-01 01:01:01.079627\"," + - "\"subscription\": \"mocksub\"," + - "\"tenant\": \"mocktenant\"," + - "\"tokenType\": \"mocktype\"}"), nil + return []byte(`{ + "accessToken": "mocktoken", + "expiresOn": "2001-02-03 04:05:06.000007", + "subscription": "mocksub", + "tenant": "mocktenant", + "tokenType": "Bearer" +} +`), nil } mockCLITokenProviderFailure = func(ctx context.Context, resource string, tenantID string) ([]byte, error) { return nil, errors.New("provider failure message") @@ -32,17 +36,18 @@ func TestAzureCLICredential_GetTokenSuccess(t *testing.T) { options.tokenProvider = mockCLITokenProviderSuccess cred, err := NewAzureCLICredential(&options) if err != nil { - t.Fatalf("Unable to create credential. Received: %v", err) + t.Fatal(err) } at, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{Scopes: []string{liveTestScope}}) if err != nil { - t.Fatalf("Expected an empty error but received: %v", err) - } - if len(at.Token) == 0 { - t.Fatalf(("Did not receive a token")) + t.Fatal(err) } if at.Token != "mocktoken" { - t.Fatalf(("Did not receive the correct access token")) + t.Fatalf("unexpected access token %q", at.Token) + } + expected := time.Date(2001, 2, 3, 4, 5, 6, 7000, time.Local).UTC() + if actual := at.ExpiresOn; !actual.Equal(expected) || actual.Location() != time.UTC { + t.Fatalf("expected %q, got %q", expected, actual) } }