Blame vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_token.go

Packit Service 4d2de5
package azblob
Packit Service 4d2de5
Packit Service 4d2de5
import (
Packit Service 4d2de5
	"context"
Packit Service 4d2de5
	"errors"
Packit Service 4d2de5
	"sync/atomic"
Packit Service 4d2de5
Packit Service 4d2de5
	"runtime"
Packit Service 4d2de5
	"sync"
Packit Service 4d2de5
	"time"
Packit Service 4d2de5
Packit Service 4d2de5
	"github.com/Azure/azure-pipeline-go/pipeline"
Packit Service 4d2de5
)
Packit Service 4d2de5
Packit Service 4d2de5
// TokenRefresher represents a callback method that you write; this method is called periodically
Packit Service 4d2de5
// so you can refresh the token credential's value.
Packit Service 4d2de5
type TokenRefresher func(credential TokenCredential) time.Duration
Packit Service 4d2de5
Packit Service 4d2de5
// TokenCredential represents a token credential (which is also a pipeline.Factory).
Packit Service 4d2de5
type TokenCredential interface {
Packit Service 4d2de5
	Credential
Packit Service 4d2de5
	Token() string
Packit Service 4d2de5
	SetToken(newToken string)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// NewTokenCredential creates a token credential for use with role-based access control (RBAC) access to Azure Storage
Packit Service 4d2de5
// resources. You initialize the TokenCredential with an initial token value. If you pass a non-nil value for
Packit Service 4d2de5
// tokenRefresher, then the function you pass will be called immediately so it can refresh and change the
Packit Service 4d2de5
// TokenCredential's token value by calling SetToken. Your tokenRefresher function must return a time.Duration
Packit Service 4d2de5
// indicating how long the TokenCredential object should wait before calling your tokenRefresher function again.
Packit Service 4d2de5
// If your tokenRefresher callback fails to refresh the token, you can return a duration of 0 to stop your
Packit Service 4d2de5
// TokenCredential object from ever invoking tokenRefresher again. Also, oen way to deal with failing to refresh a
Packit Service 4d2de5
// token is to cancel a context.Context object used by requests that have the TokenCredential object in their pipeline.
Packit Service 4d2de5
func NewTokenCredential(initialToken string, tokenRefresher TokenRefresher) TokenCredential {
Packit Service 4d2de5
	tc := &tokenCredential{}
Packit Service 4d2de5
	tc.SetToken(initialToken) // We don't set it above to guarantee atomicity
Packit Service 4d2de5
	if tokenRefresher == nil {
Packit Service 4d2de5
		return tc // If no callback specified, return the simple tokenCredential
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	tcwr := &tokenCredentialWithRefresh{token: tc}
Packit Service 4d2de5
	tcwr.token.startRefresh(tokenRefresher)
Packit Service 4d2de5
	runtime.SetFinalizer(tcwr, func(deadTC *tokenCredentialWithRefresh) {
Packit Service 4d2de5
		deadTC.token.stopRefresh()
Packit Service 4d2de5
		deadTC.token = nil //  Sanity (not really required)
Packit Service 4d2de5
	})
Packit Service 4d2de5
	return tcwr
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// tokenCredentialWithRefresh is a wrapper over a token credential.
Packit Service 4d2de5
// When this wrapper object gets GC'd, it stops the tokenCredential's timer
Packit Service 4d2de5
// which allows the tokenCredential object to also be GC'd.
Packit Service 4d2de5
type tokenCredentialWithRefresh struct {
Packit Service 4d2de5
	token *tokenCredential
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
Packit Service 4d2de5
func (*tokenCredentialWithRefresh) credentialMarker() {}
Packit Service 4d2de5
Packit Service 4d2de5
// Token returns the current token value
Packit Service 4d2de5
func (f *tokenCredentialWithRefresh) Token() string { return f.token.Token() }
Packit Service 4d2de5
Packit Service 4d2de5
// SetToken changes the current token value
Packit Service 4d2de5
func (f *tokenCredentialWithRefresh) SetToken(token string) { f.token.SetToken(token) }
Packit Service 4d2de5
Packit Service 4d2de5
// New satisfies pipeline.Factory's New method creating a pipeline policy object.
Packit Service 4d2de5
func (f *tokenCredentialWithRefresh) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
Packit Service 4d2de5
	return f.token.New(next, po)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
///////////////////////////////////////////////////////////////////////////////
Packit Service 4d2de5
Packit Service 4d2de5
// tokenCredential is a pipeline.Factory is the credential's policy factory.
Packit Service 4d2de5
type tokenCredential struct {
Packit Service 4d2de5
	token atomic.Value
Packit Service 4d2de5
Packit Service 4d2de5
	// The members below are only used if the user specified a tokenRefresher callback function.
Packit Service 4d2de5
	timer          *time.Timer
Packit Service 4d2de5
	tokenRefresher TokenRefresher
Packit Service 4d2de5
	lock           sync.Mutex
Packit Service 4d2de5
	stopped        bool
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
Packit Service 4d2de5
func (*tokenCredential) credentialMarker() {}
Packit Service 4d2de5
Packit Service 4d2de5
// Token returns the current token value
Packit Service 4d2de5
func (f *tokenCredential) Token() string { return f.token.Load().(string) }
Packit Service 4d2de5
Packit Service 4d2de5
// SetToken changes the current token value
Packit Service 4d2de5
func (f *tokenCredential) SetToken(token string) { f.token.Store(token) }
Packit Service 4d2de5
Packit Service 4d2de5
// startRefresh calls refresh which immediately calls tokenRefresher
Packit Service 4d2de5
// and then starts a timer to call tokenRefresher in the future.
Packit Service 4d2de5
func (f *tokenCredential) startRefresh(tokenRefresher TokenRefresher) {
Packit Service 4d2de5
	f.tokenRefresher = tokenRefresher
Packit Service 4d2de5
	f.stopped = false // In case user calls StartRefresh, StopRefresh, & then StartRefresh again
Packit Service 4d2de5
	f.refresh()
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// refresh calls the user's tokenRefresher so they can refresh the token (by
Packit Service 4d2de5
// calling SetToken) and then starts another time (based on the returned duration)
Packit Service 4d2de5
// in order to refresh the token again in the future.
Packit Service 4d2de5
func (f *tokenCredential) refresh() {
Packit Service 4d2de5
	d := f.tokenRefresher(f) // Invoke the user's refresh callback outside of the lock
Packit Service 4d2de5
	if d > 0 {               // If duration is 0 or negative, refresher wants to not be called again
Packit Service 4d2de5
		f.lock.Lock()
Packit Service 4d2de5
		if !f.stopped {
Packit Service 4d2de5
			f.timer = time.AfterFunc(d, f.refresh)
Packit Service 4d2de5
		}
Packit Service 4d2de5
		f.lock.Unlock()
Packit Service 4d2de5
	}
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// stopRefresh stops any pending timer and sets stopped field to true to prevent
Packit Service 4d2de5
// any new timer from starting.
Packit Service 4d2de5
// NOTE: Stopping the timer allows the GC to destroy the tokenCredential object.
Packit Service 4d2de5
func (f *tokenCredential) stopRefresh() {
Packit Service 4d2de5
	f.lock.Lock()
Packit Service 4d2de5
	f.stopped = true
Packit Service 4d2de5
	if f.timer != nil {
Packit Service 4d2de5
		f.timer.Stop()
Packit Service 4d2de5
	}
Packit Service 4d2de5
	f.lock.Unlock()
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// New satisfies pipeline.Factory's New method creating a pipeline policy object.
Packit Service 4d2de5
func (f *tokenCredential) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
Packit Service 4d2de5
	return pipeline.PolicyFunc(func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
Packit Service 4d2de5
		if request.URL.Scheme != "https" {
Packit Service 4d2de5
			// HTTPS must be used, otherwise the tokens are at the risk of being exposed
Packit Service 4d2de5
			return nil, errors.New("token credentials require a URL using the https protocol scheme")
Packit Service 4d2de5
		}
Packit Service 4d2de5
		request.Header[headerAuthorization] = []string{"Bearer " + f.Token()}
Packit Service 4d2de5
		return next.Do(ctx, request)
Packit Service 4d2de5
	})
Packit Service 4d2de5
}