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 }