diff --git a/sdk/azidentity/CHANGELOG.md b/sdk/azidentity/CHANGELOG.md index e960660d69a8..7aa88b7ae89d 100644 --- a/sdk/azidentity/CHANGELOG.md +++ b/sdk/azidentity/CHANGELOG.md @@ -5,6 +5,10 @@ ### Features Added ### Breaking Changes +> These changes affect only code written against a beta version such as v1.6.0-beta.1 +* Replaced `ErrAuthenticationRequired` with `AuthenticationRequiredError`, a struct + type that carries the `TokenRequestOptions` passed to the `GetToken` call which + returned the error. ### Bugs Fixed * Fixed more cases in which credential chains like `DefaultAzureCredential` diff --git a/sdk/azidentity/azidentity_test.go b/sdk/azidentity/azidentity_test.go index 7239d0aadf48..eae8af424eb6 100644 --- a/sdk/azidentity/azidentity_test.go +++ b/sdk/azidentity/azidentity_test.go @@ -21,6 +21,7 @@ import ( "runtime" "strings" "testing" + "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" @@ -114,6 +115,7 @@ func TestUserAuthentication(t *testing.T) { name: credNameBrowser, new: func(tcpo *TokenCachePersistenceOptions, co azcore.ClientOptions, ar AuthenticationRecord, disableAutoAuth bool) (authenticater, error) { return NewInteractiveBrowserCredential(&InteractiveBrowserCredentialOptions{ + AdditionallyAllowedTenants: []string{"*"}, AuthenticationRecord: ar, ClientOptions: co, DisableAutomaticAuthentication: disableAutoAuth, @@ -126,6 +128,7 @@ func TestUserAuthentication(t *testing.T) { name: credNameDeviceCode, new: func(tcpo *TokenCachePersistenceOptions, co azcore.ClientOptions, ar AuthenticationRecord, disableAutoAuth bool) (authenticater, error) { o := DeviceCodeCredentialOptions{ + AdditionallyAllowedTenants: []string{"*"}, AuthenticationRecord: ar, ClientOptions: co, DisableAutomaticAuthentication: disableAutoAuth, @@ -143,6 +146,7 @@ func TestUserAuthentication(t *testing.T) { name: credNameUserPassword, new: func(tcpo *TokenCachePersistenceOptions, co azcore.ClientOptions, ar AuthenticationRecord, disableAutoAuth bool) (authenticater, error) { opts := UsernamePasswordCredentialOptions{ + AdditionallyAllowedTenants: []string{"*"}, AuthenticationRecord: ar, ClientOptions: co, TokenCachePersistenceOptions: tcpo, @@ -264,8 +268,19 @@ func TestUserAuthentication(t *testing.T) { t.Run("DisableAutomaticAuthentication/"+credential.name, func(t *testing.T) { cred, err := credential.new(nil, policy.ClientOptions{Transport: &mockSTS{}}, AuthenticationRecord{}, true) require.NoError(t, err) - _, err = cred.GetToken(context.Background(), testTRO) - require.ErrorIs(t, err, ErrAuthenticationRequired) + expected := policy.TokenRequestOptions{ + Claims: "claims", + EnableCAE: true, + Scopes: []string{"scope"}, + TenantID: "tenant", + } + _, err = cred.GetToken(context.Background(), expected) + require.Contains(t, err.Error(), credential.name) + require.Contains(t, err.Error(), "Call Authenticate") + var actual *AuthenticationRequiredError + require.ErrorAs(t, err, &actual) + require.Equal(t, expected, actual.TokenRequestOptions) + if credential.name != credNameBrowser || runManualTests { _, err = cred.Authenticate(context.Background(), &testTRO) require.NoError(t, err) @@ -274,6 +289,20 @@ func TestUserAuthentication(t *testing.T) { require.NoError(t, err) } }) + t.Run("DisableAutomaticAuthentication/ChainedTokenCredential/"+credential.name, func(t *testing.T) { + cred, err := credential.new(nil, policy.ClientOptions{}, AuthenticationRecord{}, true) + require.NoError(t, err) + expected := azcore.AccessToken{ExpiresOn: time.Now().UTC(), Token: tokenValue} + fake := NewFakeCredential() + fake.SetResponse(expected, nil) + chain, err := NewChainedTokenCredential([]azcore.TokenCredential{cred, fake}, nil) + require.NoError(t, err) + // ChainedTokenCredential should continue iterating when a credential returns + // AuthenticationRequiredError i.e., it should call fake.GetToken() and return the expected token + actual, err := chain.GetToken(context.Background(), testTRO) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) } } } @@ -635,7 +664,8 @@ func TestAdditionallyAllowedTenants(t *testing.T) { // tenant resolution should have succeeded because the specified tenant is allowed, // however the credential should have returned a different error because automatic // authentication is disabled - require.ErrorIs(t, ErrAuthenticationRequired, err) + var e *AuthenticationRequiredError + require.ErrorAs(t, err, &e) } }) diff --git a/sdk/azidentity/azure_cli_credential_test.go b/sdk/azidentity/azure_cli_credential_test.go index 82f09735246c..7f026a9b678a 100644 --- a/sdk/azidentity/azure_cli_credential_test.go +++ b/sdk/azidentity/azure_cli_credential_test.go @@ -53,9 +53,9 @@ func TestAzureCLICredential_DefaultChainError(t *testing.T) { t.Fatal(err) } _, err = cred.GetToken(context.Background(), testTRO) - var ue *credentialUnavailableError - if !errors.As(err, &ue) { - t.Fatalf("expected credentialUnavailableError, got %T: %q", err, err) + var cu credentialUnavailable + if !errors.As(err, &cu) { + t.Fatalf("expected %T, got %T: %q", cu, err, err) } } diff --git a/sdk/azidentity/azure_developer_cli_credential_test.go b/sdk/azidentity/azure_developer_cli_credential_test.go index 9aeff5529523..f452ccfed55c 100644 --- a/sdk/azidentity/azure_developer_cli_credential_test.go +++ b/sdk/azidentity/azure_developer_cli_credential_test.go @@ -35,9 +35,9 @@ func TestAzureDeveloperCLICredential_DefaultChainError(t *testing.T) { t.Fatal(err) } _, err = cred.GetToken(context.Background(), testTRO) - var ue *credentialUnavailableError - if !errors.As(err, &ue) { - t.Fatalf("expected credentialUnavailableError, got %T: %q", err, err) + var cu credentialUnavailable + if !errors.As(err, &cu) { + t.Fatalf("expected %T, got %T: %q", cu, err, err) } } diff --git a/sdk/azidentity/chained_token_credential.go b/sdk/azidentity/chained_token_credential.go index dc855edf7868..6c35a941b976 100644 --- a/sdk/azidentity/chained_token_credential.go +++ b/sdk/azidentity/chained_token_credential.go @@ -86,7 +86,7 @@ func (c *ChainedTokenCredential) GetToken(ctx context.Context, opts policy.Token errs []error successfulCredential azcore.TokenCredential token azcore.AccessToken - unavailableErr *credentialUnavailableError + unavailableErr credentialUnavailable ) for _, cred := range c.sources { token, err = cred.GetToken(ctx, opts) diff --git a/sdk/azidentity/chained_token_credential_test.go b/sdk/azidentity/chained_token_credential_test.go index c05c21bcc41a..04cb1eb74ad2 100644 --- a/sdk/azidentity/chained_token_credential_test.go +++ b/sdk/azidentity/chained_token_credential_test.go @@ -139,8 +139,8 @@ func TestChainedTokenCredential_MultipleCredentialsGetTokenUnavailable(t *testin t.Fatal(err) } _, err = cred.GetToken(context.Background(), testTRO) - if _, ok := err.(*credentialUnavailableError); !ok { - t.Fatalf("expected credentialUnavailableError, received %T", err) + if _, ok := err.(credentialUnavailable); !ok { + t.Fatalf("expected credentialUnavailable, received %T", err) } expectedError := `ChainedTokenCredential: failed to acquire a token. Attempted credentials: @@ -186,8 +186,8 @@ func TestChainedTokenCredential_MultipleCredentialsGetTokenCustomName(t *testing } cred.name = "CustomNameCredential" _, err = cred.GetToken(context.Background(), testTRO) - if _, ok := err.(*credentialUnavailableError); !ok { - t.Fatalf("expected credentialUnavailableError, received %T", err) + if _, ok := err.(credentialUnavailable); !ok { + t.Fatalf("expected credentialUnavailable, received %T", err) } expectedError := `CustomNameCredential: failed to acquire a token. Attempted credentials: diff --git a/sdk/azidentity/confidential_client.go b/sdk/azidentity/confidential_client.go index 5a88e740fee2..01446a7242a4 100644 --- a/sdk/azidentity/confidential_client.go +++ b/sdk/azidentity/confidential_client.go @@ -109,7 +109,7 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque if err != nil { // We could get a credentialUnavailableError from managed identity authentication because in that case the error comes from our code. // We return it directly because it affects the behavior of credential chains. Otherwise, we return AuthenticationFailedError. - var unavailableErr *credentialUnavailableError + var unavailableErr credentialUnavailable if !errors.As(err, &unavailableErr) { res := getResponseFromError(err) err = newAuthenticationFailedError(c.name, err.Error(), res, err) diff --git a/sdk/azidentity/default_azure_credential.go b/sdk/azidentity/default_azure_credential.go index 35aeef867478..2385c986f9cf 100644 --- a/sdk/azidentity/default_azure_credential.go +++ b/sdk/azidentity/default_azure_credential.go @@ -158,7 +158,7 @@ type defaultCredentialErrorReporter struct { } func (d *defaultCredentialErrorReporter) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { - if _, ok := d.err.(*credentialUnavailableError); ok { + if _, ok := d.err.(credentialUnavailable); ok { return azcore.AccessToken{}, d.err } return azcore.AccessToken{}, newCredentialUnavailableError(d.credType, d.err.Error()) diff --git a/sdk/azidentity/default_azure_credential_test.go b/sdk/azidentity/default_azure_credential_test.go index f9bbb5f80ff8..7f6001b2076a 100644 --- a/sdk/azidentity/default_azure_credential_test.go +++ b/sdk/azidentity/default_azure_credential_test.go @@ -277,8 +277,8 @@ func TestDefaultAzureCredential_timeoutWrapper(t *testing.T) { for i := 0; i < 2; i++ { // expecting credentialUnavailableError because delay exceeds the wrapper's timeout _, err = chain.GetToken(context.Background(), testTRO) - if _, ok := err.(*credentialUnavailableError); !ok { - t.Fatalf("expected credentialUnavailableError, got %T: %v", err, err) + if _, ok := err.(credentialUnavailable); !ok { + t.Fatalf("expected credentialUnavailable, got %T: %v", err, err) } } diff --git a/sdk/azidentity/developer_credential_util.go b/sdk/azidentity/developer_credential_util.go index d8b952f532ee..be963d3a2af0 100644 --- a/sdk/azidentity/developer_credential_util.go +++ b/sdk/azidentity/developer_credential_util.go @@ -19,7 +19,7 @@ const cliTimeout = 10 * time.Second // the next credential in its chain (another developer credential). func unavailableIfInChain(err error, inDefaultChain bool) error { if err != nil && inDefaultChain { - var unavailableErr *credentialUnavailableError + var unavailableErr credentialUnavailable if !errors.As(err, &unavailableErr) { err = newCredentialUnavailableError(credNameAzureDeveloperCLI, err.Error()) } diff --git a/sdk/azidentity/device_code_credential.go b/sdk/azidentity/device_code_credential.go index 65390b9492bb..29a73e96e842 100644 --- a/sdk/azidentity/device_code_credential.go +++ b/sdk/azidentity/device_code_credential.go @@ -34,8 +34,8 @@ type DeviceCodeCredentialOptions struct { ClientID string // DisableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate. - // When this option is true, [DeviceCodeCredential.GetToken] will return [ErrAuthenticationRequired] when user - // interaction is necessary to acquire a token. + // When this option is true, GetToken will return AuthenticationRequiredError when user interaction is necessary + // to acquire a token. DisableAutomaticAuthentication bool // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or diff --git a/sdk/azidentity/errors.go b/sdk/azidentity/errors.go index 9cc4531ee156..19db7ffa6317 100644 --- a/sdk/azidentity/errors.go +++ b/sdk/azidentity/errors.go @@ -13,15 +13,12 @@ import ( "fmt" "net/http" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo" msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors" ) -// ErrAuthenticationRequired indicates a credential's Authenticate method must be called to acquire a token -// because user interaction is required and the credential is configured not to automatically prompt the user. -var ErrAuthenticationRequired error = &credentialUnavailableError{"can't acquire a token without user interaction. Call Authenticate to interactively authenticate a user"} - // getResponseFromError retrieves the response carried by // an AuthenticationFailedError or MSAL CallErr, if any func getResponseFromError(err error) *http.Response { @@ -110,8 +107,34 @@ func (*AuthenticationFailedError) NonRetriable() { var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil) -// credentialUnavailableError indicates a credential can't attempt authentication because it lacks required -// data or state +// AuthenticationRequiredError indicates a credential's Authenticate method must be called to acquire a token +// because the credential requires user interaction and is configured not to request it automatically. +type AuthenticationRequiredError struct { + credentialUnavailableError + + // TokenRequestOptions for the required token. Pass this to the credential's Authenticate method. + TokenRequestOptions policy.TokenRequestOptions +} + +func newAuthenticationRequiredError(credType string, tro policy.TokenRequestOptions) error { + return &AuthenticationRequiredError{ + credentialUnavailableError: credentialUnavailableError{ + credType + " can't acquire a token without user interaction. Call Authenticate to authenticate a user interactively", + }, + TokenRequestOptions: tro, + } +} + +var ( + _ credentialUnavailable = (*AuthenticationRequiredError)(nil) + _ errorinfo.NonRetriable = (*AuthenticationRequiredError)(nil) +) + +type credentialUnavailable interface { + error + credentialUnavailable() +} + type credentialUnavailableError struct { message string } @@ -135,6 +158,11 @@ func (e *credentialUnavailableError) Error() string { } // NonRetriable is a marker method indicating this error should not be retried. It has no implementation. -func (e *credentialUnavailableError) NonRetriable() {} +func (*credentialUnavailableError) NonRetriable() {} + +func (*credentialUnavailableError) credentialUnavailable() {} -var _ errorinfo.NonRetriable = (*credentialUnavailableError)(nil) +var ( + _ credentialUnavailable = (*credentialUnavailableError)(nil) + _ errorinfo.NonRetriable = (*credentialUnavailableError)(nil) +) diff --git a/sdk/azidentity/example_cache_test.go b/sdk/azidentity/example_cache_test.go deleted file mode 100644 index 4108aace412d..000000000000 --- a/sdk/azidentity/example_cache_test.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build go1.18 -// +build go1.18 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azidentity_test - -import ( - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - - // importing the cache module registers the cache implementation for the current platform - _ "github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache" -) - -// Credentials, excepting those that authenticate via external tools like [AzureCLICredential], -// cache authentication data in memory by default. Most of these credentials also support optional -// persistent caching. This example shows how to enable and configure that for a credential. It -// shows only [InteractiveBrowserCredential], however all credentials that support persistent caching have -// the same [TokenCachePersistenceOptions] API. -func Example_persistentCache() { - cred, err := azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{ - // Non-nil TokenCachePersistenceOptions enables persistent caching with default options. - // See TokenCachePersistenceOptions documentation for details of the supported options. - TokenCachePersistenceOptions: &azidentity.TokenCachePersistenceOptions{}, - }) - if err != nil { - // TODO: handle error - } - // TODO: use credential - _ = cred -} diff --git a/sdk/azidentity/example_shared_test.go b/sdk/azidentity/example_shared_test.go index c876d5fbf2fe..e81bf98fc7c6 100644 --- a/sdk/azidentity/example_shared_test.go +++ b/sdk/azidentity/example_shared_test.go @@ -12,12 +12,13 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" ) -// Helpers and variables to keep the examples tidy +// Helpers, variables, fakes to keep the examples tidy const ( - certPath = "testdata/certificate.pem" - clientID = "fake-client-id" - tenantID = "fake-tenant" + authRecordPath = "fake/path" + certPath = "testdata/certificate.pem" + clientID = "fake-client-id" + tenantID = "fake-tenant" ) func handleError(err error) { @@ -28,3 +29,13 @@ func handleError(err error) { var cred azcore.TokenCredential var err error + +type exampleServiceClient struct{} + +func newServiceClient(azcore.TokenCredential) (exampleServiceClient, error) { + return exampleServiceClient{}, nil +} + +func (exampleServiceClient) Method() error { + return nil +} diff --git a/sdk/azidentity/example_test.go b/sdk/azidentity/example_test.go index 8e941493e4cf..878876928b6e 100644 --- a/sdk/azidentity/example_test.go +++ b/sdk/azidentity/example_test.go @@ -7,11 +7,49 @@ package azidentity_test import ( + "context" + "errors" "os" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" ) +// Credentials that require user interaction such as [InteractiveBrowserCredential] and [DeviceCodeCredential] +// can optionally return this error instead of automatically prompting for user interaction. This allows applications +// to decide when to request user interaction. This example shows how to handle the error and authenticate a user +// interactively. It shows [InteractiveBrowserCredential] but the same pattern applies to [DeviceCodeCredential]. +func ExampleAuthenticationRequiredError() { + cred, err := azidentity.NewInteractiveBrowserCredential( + &azidentity.InteractiveBrowserCredentialOptions{ + // This option is useful only for applications that need to control when to prompt users to + // authenticate. If the timing of user interaction isn't important, don't set this option. + DisableAutomaticAuthentication: true, + }, + ) + if err != nil { + // TODO: handle error + } + // this could be any client that authenticates with an azidentity credential + client, err := newServiceClient(cred) + if err != nil { + // TODO: handle error + } + err = client.Method() + if err != nil { + var are *azidentity.AuthenticationRequiredError + if errors.As(err, &are) { + // The client requested a token and the credential requires user interaction. Whenever it's convenient + // for the application, call Authenticate to prompt the user. Pass the error's TokenRequestOptions to + // request a token with the parameters the client specified. + _, err = cred.Authenticate(context.TODO(), &are.TokenRequestOptions) + if err != nil { + // TODO: handle error + } + // TODO: retry the client method; it should succeed because the credential now has the required token + } + } +} + func ExampleNewOnBehalfOfCredentialWithCertificate() { data, err := os.ReadFile(certPath) if err != nil { diff --git a/sdk/azidentity/example_user_auth_test.go b/sdk/azidentity/example_user_auth_test.go index 26c7086ee7eb..4f29e3c62583 100644 --- a/sdk/azidentity/example_user_auth_test.go +++ b/sdk/azidentity/example_user_auth_test.go @@ -9,65 +9,64 @@ package azidentity_test import ( "context" "encoding/json" + "os" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" -) - -// This example shows how to authenticate a user with [InteractiveBrowserCredential], enabling persistent -// caching so that the user doesn't need to authenticate interactively the next time the application runs. -func Example_userAuthentication() { - cred, err := azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{ - // By default, credentials begin interactive authentication whenever necessary. To instead control when - // a credential prompts for user interaction, set this option true. The credential will then return - // azidentity.ErrAuthenticationRequired instead of prompting for authentication. The application - // can then call the credential's Authenticate method when it's convenient to prompt the user. - DisableAutomaticAuthentication: true, - // By default, credentials cache in memory. Set TokenCachePersistenceOptions to enable persistent caching. - TokenCachePersistenceOptions: &azidentity.TokenCachePersistenceOptions{ - // optionally set Name to isolate this credential's cache from other applications - Name: "myapp", - }, - }) - if err != nil { - // TODO: handle error - } + // importing the cache module registers the cache implementation for the current platform + _ "github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache" +) - // The Authenticate method begins interactive authentication. Call it whenever it's convenient for - // your application to authenticate a user. If Authenticate succeeds, the credential is ready for - // use with a client. - record, err := cred.Authenticate(context.TODO(), nil) - if err != nil { - // TODO: handle error +// this example shows file storage but any form of byte storage would work +func retrieveRecord() (azidentity.AuthenticationRecord, error) { + record := azidentity.AuthenticationRecord{} + b, err := os.ReadFile(authRecordPath) + if err == nil { + err = json.Unmarshal(b, &record) } + return record, err +} - // The record contains no authentication secrets. You can marshal it for storage. +func storeRecord(record azidentity.AuthenticationRecord) error { b, err := json.Marshal(record) - if err != nil { - // TODO: handle error + if err == nil { + err = os.WriteFile(authRecordPath, b, 0600) } - // TODO: store bytes - _ = b + return err +} - // An authentication record stored by your application enables other credentials to access data from - // past authentications. If the cache contains sufficient data, your application won't need to prompt - // for authentication. - var unmarshaled azidentity.AuthenticationRecord - err = json.Unmarshal(b, &unmarshaled) +// This example shows how to cache authentication data persistently so a user doesn't need to authenticate +// interactively every time the application runs. The example uses [InteractiveBrowserCredential], however +// [DeviceCodeCredential] has the same API. The key steps are: +// +// 1. Enable persistent caching by importing "github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache" and +// setting [TokenCachePersistenceOptions] +// 2. Call Authenticate to acquire an [AuthenticationRecord] and store that for future use. An [AuthenticationRecord] +// enables credentials to access data in the persistent cache. The record contains no authentication secrets. +// 3. Add the [AuthenticationRecord] to the credential's options +func Example_persistentUserAuthentication() { + record, err := retrieveRecord() if err != nil { // TODO: handle error } - - // this credential will be able to access authentication data cached by cred above, even in another process - newCred, err := azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{ - AuthenticationRecord: unmarshaled, - DisableAutomaticAuthentication: true, - TokenCachePersistenceOptions: &azidentity.TokenCachePersistenceOptions{ - Name: "myapp", - }, + cred, err := azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{ + AuthenticationRecord: record, + // Credentials cache in memory by default. Set TokenCachePersistenceOptions to enable persistent caching. + TokenCachePersistenceOptions: &azidentity.TokenCachePersistenceOptions{}, }) if err != nil { // TODO: handle error } - _ = newCred + + if record == (azidentity.AuthenticationRecord{}) { + // No stored record; call Authenticate to acquire one + record, err = cred.Authenticate(context.TODO(), nil) + if err != nil { + // TODO: handle error + } + err = storeRecord(record) + if err != nil { + // TODO: handle error + } + } } diff --git a/sdk/azidentity/interactive_browser_credential.go b/sdk/azidentity/interactive_browser_credential.go index 4e04ecdab097..ad6bdaf69189 100644 --- a/sdk/azidentity/interactive_browser_credential.go +++ b/sdk/azidentity/interactive_browser_credential.go @@ -33,8 +33,8 @@ type InteractiveBrowserCredentialOptions struct { ClientID string // DisableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate. - // When this option is true, [InteractiveBrowserCredential.GetToken] will return [ErrAuthenticationRequired] when - // user interaction is necessary to acquire a token. + // When this option is true, GetToken will return AuthenticationRequiredError when user interaction is necessary + // to acquire a token. DisableAutomaticAuthentication bool // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or diff --git a/sdk/azidentity/managed_identity_client_test.go b/sdk/azidentity/managed_identity_client_test.go index 56ef2340941f..78564a261cfe 100644 --- a/sdk/azidentity/managed_identity_client_test.go +++ b/sdk/azidentity/managed_identity_client_test.go @@ -116,7 +116,7 @@ func TestManagedIdentityClient_IMDSErrors(t *testing.T) { if actual := err.Error(); !strings.Contains(actual, test.body) { t.Fatalf("expected response body in error, got %q", actual) } - var unavailableErr *credentialUnavailableError + var unavailableErr credentialUnavailable if !errors.As(err, &unavailableErr) { t.Fatalf("expected %T, got %T", unavailableErr, err) } diff --git a/sdk/azidentity/managed_identity_credential_test.go b/sdk/azidentity/managed_identity_credential_test.go index c22aaafccf17..6390775b800c 100644 --- a/sdk/azidentity/managed_identity_credential_test.go +++ b/sdk/azidentity/managed_identity_credential_test.go @@ -300,8 +300,8 @@ func TestManagedIdentityCredential_GetTokenIMDS400(t *testing.T) { // cred should return credentialUnavailableError when IMDS responds 400 to a token request for i := 0; i < 3; i++ { _, err = cred.GetToken(context.Background(), testTRO) - if _, ok := err.(*credentialUnavailableError); !ok { - t.Fatalf("expected credentialUnavailableError, received %T", err) + if _, ok := err.(credentialUnavailable); !ok { + t.Fatalf("expected credentialUnavailable, received %T", err) } } } diff --git a/sdk/azidentity/public_client.go b/sdk/azidentity/public_client.go index 81d8632fa96e..e76cb3bab4e6 100644 --- a/sdk/azidentity/public_client.go +++ b/sdk/azidentity/public_client.go @@ -152,7 +152,7 @@ func (p *publicClient) GetToken(ctx context.Context, tro policy.TokenRequestOpti return p.token(ar, err) } if p.opts.DisableAutomaticAuthentication { - return azcore.AccessToken{}, ErrAuthenticationRequired + return azcore.AccessToken{}, newAuthenticationRequiredError(p.name, tro) } at, err := p.reqToken(ctx, client, tro) if err == nil {