errors.go raw
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
7 package azidentity
8
9 import (
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
24 func 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.
37 type AuthenticationFailedError struct {
38 // RawResponse is the HTTP response motivating the error, if available.
39 RawResponse *http.Response
40
41 credType, message string
42 omitResponse bool
43 }
44
45 func newAuthenticationFailedError(credType, message string, resp *http.Response) error {
46 return &AuthenticationFailedError{credType: credType, message: message, RawResponse: resp}
47 }
48
49 // newAuthenticationFailedErrorFromMSAL creates an AuthenticationFailedError from an MSAL error.
50 // If the error is an MSAL CallErr, the new error includes an HTTP response and not the MSAL error
51 // message, because that message is redundant given the response. If the original error isn't a
52 // CallErr, the returned error incorporates its message.
53 func newAuthenticationFailedErrorFromMSAL(credType string, err error) error {
54 msg := ""
55 res := getResponseFromError(err)
56 if res == nil {
57 msg = err.Error()
58 }
59 return newAuthenticationFailedError(credType, msg, res)
60 }
61
62 // Error implements the error interface. Note that the message contents are not contractual and can change over time.
63 func (e *AuthenticationFailedError) Error() string {
64 if e.RawResponse == nil || e.omitResponse {
65 return e.credType + ": " + e.message
66 }
67 msg := &bytes.Buffer{}
68 fmt.Fprintf(msg, "%s authentication failed. %s\n", e.credType, e.message)
69 if e.RawResponse.Request != nil {
70 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)
71 } else {
72 // this happens when the response is created from a custom HTTP transporter,
73 // which doesn't guarantee to bind the original request to the response
74 fmt.Fprintln(msg, "Request information not available")
75 }
76 fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
77 fmt.Fprintf(msg, "RESPONSE %d: %s\n", e.RawResponse.StatusCode, e.RawResponse.Status)
78 fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
79 body, err := runtime.Payload(e.RawResponse)
80 switch {
81 case err != nil:
82 fmt.Fprintf(msg, "Error reading response body: %v", err)
83 case len(body) > 0:
84 if err := json.Indent(msg, body, "", " "); err != nil {
85 // failed to pretty-print so just dump it verbatim
86 fmt.Fprint(msg, string(body))
87 }
88 default:
89 fmt.Fprint(msg, "Response contained no body")
90 }
91 fmt.Fprintln(msg, "\n--------------------------------------------------------------------------------")
92 var anchor string
93 switch e.credType {
94 case credNameAzureCLI:
95 anchor = "azure-cli"
96 case credNameAzureDeveloperCLI:
97 anchor = "azd"
98 case credNameAzurePipelines:
99 anchor = "apc"
100 case credNameCert:
101 anchor = "client-cert"
102 case credNameAzurePowerShell:
103 anchor = "azure-pwsh"
104 case credNameSecret:
105 anchor = "client-secret"
106 case credNameManagedIdentity:
107 anchor = "managed-id"
108 case credNameWorkloadIdentity:
109 anchor = "workload"
110 }
111 if anchor != "" {
112 fmt.Fprintf(msg, "To troubleshoot, visit https://aka.ms/azsdk/go/identity/troubleshoot#%s", anchor)
113 }
114 return msg.String()
115 }
116
117 // NonRetriable indicates the request which provoked this error shouldn't be retried.
118 func (*AuthenticationFailedError) NonRetriable() {
119 // marker method
120 }
121
122 var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil)
123
124 // AuthenticationRequiredError indicates a credential's Authenticate method must be called to acquire a token
125 // because the credential requires user interaction and is configured not to request it automatically.
126 type AuthenticationRequiredError struct {
127 credentialUnavailableError
128
129 // TokenRequestOptions for the required token. Pass this to the credential's Authenticate method.
130 TokenRequestOptions policy.TokenRequestOptions
131 }
132
133 func newAuthenticationRequiredError(credType string, tro policy.TokenRequestOptions) error {
134 return &AuthenticationRequiredError{
135 credentialUnavailableError: credentialUnavailableError{
136 credType + " can't acquire a token without user interaction. Call Authenticate to authenticate a user interactively",
137 },
138 TokenRequestOptions: tro,
139 }
140 }
141
142 var (
143 _ credentialUnavailable = (*AuthenticationRequiredError)(nil)
144 _ errorinfo.NonRetriable = (*AuthenticationRequiredError)(nil)
145 )
146
147 type credentialUnavailable interface {
148 error
149 credentialUnavailable()
150 }
151
152 type credentialUnavailableError struct {
153 message string
154 }
155
156 // newCredentialUnavailableError is an internal helper that ensures consistent error message formatting
157 func newCredentialUnavailableError(credType, message string) error {
158 msg := fmt.Sprintf("%s: %s", credType, message)
159 return &credentialUnavailableError{msg}
160 }
161
162 // NewCredentialUnavailableError constructs an error indicating a credential can't attempt authentication
163 // because it lacks required data or state. When [ChainedTokenCredential] receives this error it will try
164 // its next credential, if any.
165 func NewCredentialUnavailableError(message string) error {
166 return &credentialUnavailableError{message}
167 }
168
169 // Error implements the error interface. Note that the message contents are not contractual and can change over time.
170 func (e *credentialUnavailableError) Error() string {
171 return e.message
172 }
173
174 // NonRetriable is a marker method indicating this error should not be retried. It has no implementation.
175 func (*credentialUnavailableError) NonRetriable() {}
176
177 func (*credentialUnavailableError) credentialUnavailable() {}
178
179 var (
180 _ credentialUnavailable = (*credentialUnavailableError)(nil)
181 _ errorinfo.NonRetriable = (*credentialUnavailableError)(nil)
182 )
183