errors.go

  1//go:build go1.18
  2// +build go1.18
  3
  4// Copyright (c) Microsoft Corporation. All rights reserved.
  5// Licensed under the MIT License.
  6
  7package azidentity
  8
  9import (
 10	"bytes"
 11	"encoding/json"
 12	"errors"
 13	"fmt"
 14	"net/http"
 15
 16	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 17	"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
 18	"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
 19	msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
 20)
 21
 22// getResponseFromError retrieves the response carried by
 23// an AuthenticationFailedError or MSAL CallErr, if any
 24func getResponseFromError(err error) *http.Response {
 25	var a *AuthenticationFailedError
 26	var c msal.CallErr
 27	var res *http.Response
 28	if errors.As(err, &c) {
 29		res = c.Resp
 30	} else if errors.As(err, &a) {
 31		res = a.RawResponse
 32	}
 33	return res
 34}
 35
 36// AuthenticationFailedError indicates an authentication request has failed.
 37type AuthenticationFailedError struct {
 38	// RawResponse is the HTTP response motivating the error, if available.
 39	RawResponse *http.Response
 40
 41	credType string
 42	message  string
 43	err      error
 44}
 45
 46func newAuthenticationFailedError(credType string, message string, resp *http.Response, err error) error {
 47	return &AuthenticationFailedError{credType: credType, message: message, RawResponse: resp, err: err}
 48}
 49
 50// Error implements the error interface. Note that the message contents are not contractual and can change over time.
 51func (e *AuthenticationFailedError) Error() string {
 52	if e.RawResponse == nil {
 53		return e.credType + ": " + e.message
 54	}
 55	msg := &bytes.Buffer{}
 56	fmt.Fprintf(msg, "%s authentication failed. %s\n", e.credType, e.message)
 57	if e.RawResponse.Request != nil {
 58		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)
 59	} else {
 60		// this happens when the response is created from a custom HTTP transporter,
 61		// which doesn't guarantee to bind the original request to the response
 62		fmt.Fprintln(msg, "Request information not available")
 63	}
 64	fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
 65	fmt.Fprintf(msg, "RESPONSE %s\n", e.RawResponse.Status)
 66	fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
 67	body, err := runtime.Payload(e.RawResponse)
 68	switch {
 69	case err != nil:
 70		fmt.Fprintf(msg, "Error reading response body: %v", err)
 71	case len(body) > 0:
 72		if err := json.Indent(msg, body, "", "  "); err != nil {
 73			// failed to pretty-print so just dump it verbatim
 74			fmt.Fprint(msg, string(body))
 75		}
 76	default:
 77		fmt.Fprint(msg, "Response contained no body")
 78	}
 79	fmt.Fprintln(msg, "\n--------------------------------------------------------------------------------")
 80	var anchor string
 81	switch e.credType {
 82	case credNameAzureCLI:
 83		anchor = "azure-cli"
 84	case credNameAzureDeveloperCLI:
 85		anchor = "azd"
 86	case credNameAzurePipelines:
 87		anchor = "apc"
 88	case credNameCert:
 89		anchor = "client-cert"
 90	case credNameSecret:
 91		anchor = "client-secret"
 92	case credNameManagedIdentity:
 93		anchor = "managed-id"
 94	case credNameUserPassword:
 95		anchor = "username-password"
 96	case credNameWorkloadIdentity:
 97		anchor = "workload"
 98	}
 99	if anchor != "" {
100		fmt.Fprintf(msg, "To troubleshoot, visit https://aka.ms/azsdk/go/identity/troubleshoot#%s", anchor)
101	}
102	return msg.String()
103}
104
105// NonRetriable indicates the request which provoked this error shouldn't be retried.
106func (*AuthenticationFailedError) NonRetriable() {
107	// marker method
108}
109
110var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil)
111
112// authenticationRequiredError indicates a credential's Authenticate method must be called to acquire a token
113// because the credential requires user interaction and is configured not to request it automatically.
114type authenticationRequiredError struct {
115	credentialUnavailableError
116
117	// TokenRequestOptions for the required token. Pass this to the credential's Authenticate method.
118	TokenRequestOptions policy.TokenRequestOptions
119}
120
121func newauthenticationRequiredError(credType string, tro policy.TokenRequestOptions) error {
122	return &authenticationRequiredError{
123		credentialUnavailableError: credentialUnavailableError{
124			credType + " can't acquire a token without user interaction. Call Authenticate to authenticate a user interactively",
125		},
126		TokenRequestOptions: tro,
127	}
128}
129
130var (
131	_ credentialUnavailable  = (*authenticationRequiredError)(nil)
132	_ errorinfo.NonRetriable = (*authenticationRequiredError)(nil)
133)
134
135type credentialUnavailable interface {
136	error
137	credentialUnavailable()
138}
139
140type credentialUnavailableError struct {
141	message string
142}
143
144// newCredentialUnavailableError is an internal helper that ensures consistent error message formatting
145func newCredentialUnavailableError(credType, message string) error {
146	msg := fmt.Sprintf("%s: %s", credType, message)
147	return &credentialUnavailableError{msg}
148}
149
150// NewCredentialUnavailableError constructs an error indicating a credential can't attempt authentication
151// because it lacks required data or state. When [ChainedTokenCredential] receives this error it will try
152// its next credential, if any.
153func NewCredentialUnavailableError(message string) error {
154	return &credentialUnavailableError{message}
155}
156
157// Error implements the error interface. Note that the message contents are not contractual and can change over time.
158func (e *credentialUnavailableError) Error() string {
159	return e.message
160}
161
162// NonRetriable is a marker method indicating this error should not be retried. It has no implementation.
163func (*credentialUnavailableError) NonRetriable() {}
164
165func (*credentialUnavailableError) credentialUnavailable() {}
166
167var (
168	_ credentialUnavailable  = (*credentialUnavailableError)(nil)
169	_ errorinfo.NonRetriable = (*credentialUnavailableError)(nil)
170)