github.com/sleungcy/cli@v7.1.0+incompatible/api/cloudcontroller/wrapper/uaa_authentication.go (about)

     1  package wrapper
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	"github.com/SermoDigital/jose/jws"
     8  
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    10  	"code.cloudfoundry.org/cli/api/uaa"
    11  )
    12  
    13  //go:generate counterfeiter . UAAClient
    14  
    15  const accessTokenExpirationMargin = time.Minute
    16  
    17  // UAAClient is the interface for getting a valid access token
    18  type UAAClient interface {
    19  	RefreshAccessToken(refreshToken string) (uaa.RefreshedTokens, error)
    20  }
    21  
    22  //go:generate counterfeiter . TokenCache
    23  
    24  // TokenCache is where the UAA token information is stored.
    25  type TokenCache interface {
    26  	AccessToken() string
    27  	RefreshToken() string
    28  	SetAccessToken(token string)
    29  	SetRefreshToken(token string)
    30  }
    31  
    32  // UAAAuthentication wraps connections and adds authentication headers to all
    33  // requests
    34  type UAAAuthentication struct {
    35  	connection cloudcontroller.Connection
    36  	client     UAAClient
    37  	cache      TokenCache
    38  }
    39  
    40  // NewUAAAuthentication returns a pointer to a UAAAuthentication wrapper with
    41  // the client and a token cache.
    42  func NewUAAAuthentication(client UAAClient, cache TokenCache) *UAAAuthentication {
    43  	return &UAAAuthentication{
    44  		client: client,
    45  		cache:  cache,
    46  	}
    47  }
    48  
    49  // Make adds authentication headers to the passed in request and then calls the
    50  // wrapped connection's Make. If the client is not set on the wrapper, it will
    51  // not add any header or handle any authentication errors.
    52  func (t *UAAAuthentication) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error {
    53  	if t.client == nil {
    54  		return t.connection.Make(request, passedResponse)
    55  	}
    56  
    57  	if t.cache.AccessToken() != "" || t.cache.RefreshToken() != "" {
    58  		// assert a valid access token for authenticated requests
    59  		err := t.refreshToken()
    60  		if nil != err {
    61  			return err
    62  		}
    63  		request.Header.Set("Authorization", t.cache.AccessToken())
    64  	}
    65  
    66  	err := t.connection.Make(request, passedResponse)
    67  	return err
    68  }
    69  
    70  // SetClient sets the UAA client that the wrapper will use.
    71  func (t *UAAAuthentication) SetClient(client UAAClient) {
    72  	t.client = client
    73  }
    74  
    75  // Wrap sets the connection on the UAAAuthentication and returns itself
    76  func (t *UAAAuthentication) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection {
    77  	t.connection = innerconnection
    78  	return t
    79  }
    80  
    81  // refreshToken refreshes the JWT access token if it is expired or about to expire.
    82  // If the access token is not yet expired, no action is performed.
    83  func (t *UAAAuthentication) refreshToken() error {
    84  	var expiresIn time.Duration
    85  
    86  	tokenStr := strings.TrimPrefix(t.cache.AccessToken(), "bearer ")
    87  	token, err := jws.ParseJWT([]byte(tokenStr))
    88  
    89  	if err == nil {
    90  		expiration, ok := token.Claims().Expiration()
    91  		if ok {
    92  			expiresIn = time.Until(expiration)
    93  		}
    94  	}
    95  
    96  	if err != nil || expiresIn < accessTokenExpirationMargin {
    97  		tokens, err := t.client.RefreshAccessToken(t.cache.RefreshToken())
    98  		if err != nil {
    99  			return err
   100  		}
   101  		t.cache.SetAccessToken(tokens.AuthorizationToken())
   102  		t.cache.SetRefreshToken(tokens.RefreshToken)
   103  	}
   104  	return nil
   105  }