ste/destReauthPolicy.go (93 lines of code) (raw):

package ste import ( "context" "errors" "fmt" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "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/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" "github.com/Azure/azure-storage-azcopy/v10/common" "net/http" "sync" "time" ) /* RESPONSE Status: 401 Server failed to authenticate the request. Please refer to the information in the www-authenticate header. Www-Authenticate: Bearer authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com X-Ms-Error-Code: InvalidAuthenticationInfo <?xml version="1.0" encoding="utf-8"?> <Error><Code>InvalidAuthenticationInfo</Code><Message>Server failed to authenticate the request. Please refer to the information in the www-authenticate header. </Message><AuthenticationErrorDetail>Lifetime validation failed. The token is expired.</AuthenticationErrorDetail> */ type destReauthPolicy struct { cred *common.ScopedAuthenticator } var reauthLock *sync.Cond = sync.NewCond(&sync.Mutex{}) func NewDestReauthPolicy(cred *common.ScopedAuthenticator) policy.Policy { return &destReauthPolicy{cred} } type destReauthDebug string var ( destReauthDebugExecuted destReauthDebug = "executed" destReauthDebugNoPrompt destReauthDebug = "destReauthNoPrompt" destReauthDebugCause destReauthDebug = "destReauthCause" destReauthDebugCauseAuthenticationRequired destReauthDebug = "AuthenticationRequiredError" destReauthDebugCauseInvalidAuthenticationInfo destReauthDebug = "InvalidAuthenticationInfoError" ) func (d *destReauthPolicy) Do(req *policy.Request) (*http.Response, error) { retry: ctx := req.Raw().Context() debugCtx := context.WithValue(ctx, destReauthDebugExecuted, true) clone := req.Clone(ctx) resp, err := clone.Next() // Initially attempt the request. if err != nil || resp.StatusCode != http.StatusOK { // But, if we get back an error... var authReq *azidentity.AuthenticationRequiredError var respErr = &azcore.ResponseError{} reauth := false switch { // Is it an error we can resolve by re-authing? case errors.As(err, &authReq): reauth = true debugCtx = context.WithValue(debugCtx, destReauthDebugCause, destReauthDebugCauseAuthenticationRequired) case resp.StatusCode == http.StatusUnauthorized: errors.As(runtime.NewResponseError(resp), &respErr) reauth = err == nil && bloberror.HasCode(respErr, bloberror.InvalidAuthenticationInfo) && len(respErr.RawResponse.Header.Values("WWW-Authenticate")) != 0 if reauth { debugCtx = context.WithValue(debugCtx, destReauthDebugCause, destReauthDebugCauseInvalidAuthenticationInfo) } } if reauth { // If it is, pull the lock if we can, reauth m := reauthLock.L.(*sync.Mutex) if m.TryLock() { // Fetch the lock and try until we get auth. for { if ctx.Value(destReauthDebugNoPrompt) == nil { _ = common.GetLifecycleMgr().Prompt("Authentication is required to continue the job. Reauthorize and continue?", common.PromptDetails{ PromptType: common.EPromptType.Reauth(), ResponseOptions: []common.ResponseOption{ common.EResponseOption.Yes(), }, }) } _, err = d.cred.Authenticate(debugCtx, &policy.TokenRequestOptions{ Scopes: []string{}, }) // I (Adele Reed) was initially worried about every case // Thinking about it further, the worst case is that the job ends automatically, or when the user asks it to end. // To avoid having to handle every error, we'll catch the cancel case as a way to exit the routine, but otherwise // we will let it happen, and just retry. if err == nil { break } else { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { select { case <-ctx.Done(): // If it was us, exit like asked. return nil, err // If it was us, that's legitimately important. default: // If it was them, we don't care. } } else { common.GetLifecycleMgr().Info(fmt.Sprintf("Authentication failed, awaiting input to continue: %s", err)) } time.Sleep(time.Second * 5) } } m.Unlock() reauthLock.Broadcast() } else { // Otherwise, wait for a signal that we can try again. reauthLock.Wait() } // Try the request once more goto retry } // If it wasn't, we won't retry, and we'll simply return the error. } return resp, err }