github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/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 go run github.com/maxbrunsfeld/counterfeiter/v6 . 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 go run github.com/maxbrunsfeld/counterfeiter/v6 . 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 request.Header.Get("Authorization") == "" && (t.cache.AccessToken() != "" || t.cache.RefreshToken() != "") {
    54  		// assert a valid access token for authenticated requests
    55  		err := t.refreshTokenIfNecessary(t.cache.AccessToken())
    56  		if nil != err {
    57  			return err
    58  		}
    59  
    60  		request.Header.Set("Authorization", t.cache.AccessToken())
    61  	}
    62  
    63  	err := t.connection.Make(request, passedResponse)
    64  	return err
    65  }
    66  
    67  // SetClient sets the UAA client that the wrapper will use.
    68  func (t *UAAAuthentication) SetClient(client UAAClient) {
    69  	t.client = client
    70  }
    71  
    72  // Wrap sets the connection on the UAAAuthentication and returns itself
    73  func (t *UAAAuthentication) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection {
    74  	t.connection = innerconnection
    75  	return t
    76  }
    77  
    78  // refreshToken refreshes the JWT access token if it is expired or about to expire.
    79  // If the access token is not yet expired, no action is performed.
    80  func (t *UAAAuthentication) refreshTokenIfNecessary(accessToken string) error {
    81  	var expiresIn time.Duration
    82  
    83  	tokenStr := strings.TrimPrefix(accessToken, "bearer ")
    84  	token, err := jws.ParseJWT([]byte(tokenStr))
    85  
    86  	if err == nil {
    87  		expiration, ok := token.Claims().Expiration()
    88  		if ok {
    89  			expiresIn = time.Until(expiration)
    90  		}
    91  	}
    92  
    93  	if err != nil || expiresIn < accessTokenExpirationMargin {
    94  		tokens, err := t.client.RefreshAccessToken(t.cache.RefreshToken())
    95  		if err != nil {
    96  			return err
    97  		}
    98  		t.cache.SetAccessToken(tokens.AuthorizationToken())
    99  		t.cache.SetRefreshToken(tokens.RefreshToken)
   100  	}
   101  	return nil
   102  }