Skip to content
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

Align authentication errors with azcore.ResponseError #16777

Merged
merged 8 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion sdk/azidentity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Release History

## 0.12.1 (Unreleased)
## 0.13.0 (2022-01-11)

### Features Added

### Breaking Changes
* Replaced `AuthenticationFailedError.RawResponse()` with a field having the same name
* Unexported `CredentialUnavailableError`

### Bugs Fixed

Expand Down
8 changes: 4 additions & 4 deletions sdk/azidentity/chained_token_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@ func NewChainedTokenCredential(sources []azcore.TokenCredential, options *Chaine
// ctx: Context controlling the request lifetime.
// opts: Options for the token request, in particular the desired scope of the access token.
func (c *ChainedTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (token *azcore.AccessToken, err error) {
var errList []CredentialUnavailableError
var errList []credentialUnavailableError
for _, cred := range c.sources {
token, err = cred.GetToken(ctx, opts)
var credErr CredentialUnavailableError
var credErr credentialUnavailableError
if errors.As(err, &credErr) {
errList = append(errList, credErr)
} else if err != nil {
var authFailed AuthenticationFailedError
if errors.As(err, &authFailed) {
err = fmt.Errorf("Authentication failed:\n%s\n%s"+createChainedErrorMessage(errList), err)
authErr := newAuthenticationFailedError(err, authFailed.RawResponse())
authErr := newAuthenticationFailedError(err, authFailed.RawResponse)
return nil, authErr
}
return nil, err
Expand All @@ -69,7 +69,7 @@ func (c *ChainedTokenCredential) GetToken(ctx context.Context, opts policy.Token
return nil, credErr
}

func createChainedErrorMessage(errList []CredentialUnavailableError) string {
func createChainedErrorMessage(errList []credentialUnavailableError) string {
msg := ""
for _, err := range errList {
msg += err.Error()
Expand Down
4 changes: 2 additions & 2 deletions sdk/azidentity/client_certificate_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func TestClientCertificateCredential_InvalidCertLive(t *testing.T) {
if !errors.As(err, &e) {
t.Fatal("expected AuthenticationFailedError")
}
if e.RawResponse() == nil {
t.Fatal("expected RawResponse() to return a non-nil *http.Response")
if e.RawResponse == nil {
t.Fatal("expected a non-nil RawResponse")
}
}
4 changes: 2 additions & 2 deletions sdk/azidentity/client_secret_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestClientSecretCredential_InvalidSecretLive(t *testing.T) {
if !errors.As(err, &e) {
t.Fatal("expected AuthenticationFailedError")
}
if e.RawResponse() == nil {
t.Fatal("expected RawResponse() to return a non-nil *http.Response")
if e.RawResponse == nil {
t.Fatal("expected a non-nil RawResponse")
}
}
8 changes: 4 additions & 4 deletions sdk/azidentity/environment_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ func TestEnvironmentCredential_InvalidClientSecretLive(t *testing.T) {
if !errors.As(err, &e) {
t.Fatal("expected AuthenticationFailedError")
}
if e.RawResponse() == nil {
t.Fatal("expected RawResponse() to return a non-nil *http.Response")
if e.RawResponse == nil {
t.Fatal("expected a non-nil RawResponse")
}
}

Expand Down Expand Up @@ -254,7 +254,7 @@ func TestEnvironmentCredential_InvalidPasswordLive(t *testing.T) {
if !errors.As(err, &e) {
t.Fatal("expected AuthenticationFailedError")
}
if e.RawResponse() == nil {
t.Fatal("expected RawResponse() to return a non-nil *http.Response")
if e.RawResponse == nil {
t.Fatal("expected a non-nil RawResponse")
}
}
78 changes: 42 additions & 36 deletions sdk/azidentity/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,78 @@
package azidentity

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"

"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
)

// AuthenticationFailedError indicates an authentication request has failed.
type AuthenticationFailedError interface {
errorinfo.NonRetriable
RawResponse() *http.Response
authenticationFailed()
}
type AuthenticationFailedError struct {
err error

type authenticationFailedError struct {
error
resp *http.Response
// RawResponse is the HTTP response motivating the error, if available.
RawResponse *http.Response
}

func newAuthenticationFailedError(err error, resp *http.Response) AuthenticationFailedError {
if resp == nil {
var e msal.CallErr
if errors.As(err, &e) {
return authenticationFailedError{err, e.Resp}
return AuthenticationFailedError{err: e, RawResponse: e.Resp}
}
}
return authenticationFailedError{err, resp}
return AuthenticationFailedError{err: err, RawResponse: resp}
}

// NonRetriable indicates that this error should not be retried.
func (authenticationFailedError) NonRetriable() {
// marker method
// Error implements the error interface for type ResponseError.
// Note that the message contents are not contractual and can change over time.
func (e AuthenticationFailedError) Error() string {
if e.RawResponse == nil {
return e.err.Error()
}
msg := &bytes.Buffer{}
fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path)
fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
fmt.Fprintf(msg, "RESPONSE %s\n", e.RawResponse.Status)
fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
body, err := io.ReadAll(e.RawResponse.Body)
e.RawResponse.Body.Close()
if err != nil {
fmt.Fprintf(msg, "Error reading response body: %v", err)
} else if len(body) > 0 {
e.RawResponse.Body = io.NopCloser(bytes.NewReader(body))
if err := json.Indent(msg, body, "", " "); err != nil {
// failed to pretty-print so just dump it verbatim
fmt.Fprint(msg, string(body))
}
} else {
fmt.Fprint(msg, "Response contained no body")
}
fmt.Fprintln(msg, "\n--------------------------------------------------------------------------------")
return msg.String()
}

// AuthenticationFailed indicates that an authentication attempt failed
func (authenticationFailedError) authenticationFailed() {
// NonRetriable indicates that this error should not be retried.
func (AuthenticationFailedError) NonRetriable() {
// marker method
}

// RawResponse returns the HTTP response motivating the error, if available.
func (e authenticationFailedError) RawResponse() *http.Response {
return e.resp
}

var _ AuthenticationFailedError = (*authenticationFailedError)(nil)
var _ errorinfo.NonRetriable = (*authenticationFailedError)(nil)

// CredentialUnavailableError indicates a credential can't attempt authenticate
// because it lacks required data or state.
type CredentialUnavailableError interface {
errorinfo.NonRetriable
credentialUnavailable()
}
var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil)

// credentialUnavailableError indicates a credential can't attempt
// authentication because it lacks required data or state.
type credentialUnavailableError struct {
credType string
message string
}

func newCredentialUnavailableError(credType, message string) CredentialUnavailableError {
func newCredentialUnavailableError(credType, message string) credentialUnavailableError {
return credentialUnavailableError{credType: credType, message: message}
}

Expand All @@ -76,10 +88,4 @@ func (e credentialUnavailableError) NonRetriable() {
// marker method
}

// CredentialUnavailable indicates that the credential didn't attempt to authenticate
func (e credentialUnavailableError) credentialUnavailable() {
// marker method
}

var _ CredentialUnavailableError = (*credentialUnavailableError)(nil)
var _ errorinfo.NonRetriable = (*credentialUnavailableError)(nil)
4 changes: 2 additions & 2 deletions sdk/azidentity/managed_identity_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,8 @@ func TestManagedIdentityCredential_GetTokenIMDS400(t *testing.T) {
if err != nil {
t.Fatal(err)
}
// cred should return CredentialUnavailableError when IMDS responds 400 to a token request
var expected CredentialUnavailableError
// cred should return credentialUnavailableError when IMDS responds 400 to a token request
var expected credentialUnavailableError
for i := 0; i < 3; i++ {
_, err = cred.GetToken(context.Background(), policy.TokenRequestOptions{Scopes: []string{liveTestScope}})
if !errors.As(err, &expected) {
Expand Down
4 changes: 2 additions & 2 deletions sdk/azidentity/username_password_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestUsernamePasswordCredential_InvalidPasswordLive(t *testing.T) {
if !errors.As(err, &e) {
t.Fatal("expected AuthenticationFailedError")
}
if e.RawResponse() == nil {
t.Fatal("expected RawResponse() to return a non-nil *http.Response")
if e.RawResponse == nil {
t.Fatal("expected a non-nil RawResponse")
}
}
2 changes: 1 addition & 1 deletion sdk/azidentity/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ const (
component = "azidentity"

// Version is the semantic version (see http://semver.org) of this module.
version = "v0.12.1"
version = "v0.13.0"
)