Skip to content

Commit

Permalink
Merge pull request #4289 from cloudfoundry/cf-push-token-expiry
Browse files Browse the repository at this point in the history
CF Push: A better fix for the token expiry issue
  • Loading branch information
richard-cox authored May 13, 2020
2 parents 57be9d5 + e31bcb6 commit cdf0f53
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 18 deletions.
53 changes: 53 additions & 0 deletions src/jetstream/plugins/cfapppush/connection_wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cfapppush

import (
"time"

"code.cloudfoundry.org/cli/api/cloudcontroller"
"code.cloudfoundry.org/cli/command"

"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
)

// PushConnectionWrapper can wrap a given connection allowing the wrapper to modify
// all requests going in and out of the given connection.
type PushConnectionWrapper struct {
inner cloudcontroller.Connection
portalProxy interfaces.PortalProxy
config *CFPushAppConfig
cmdConfig command.Config
}

// Wrap an existing connection
func (cw PushConnectionWrapper) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection {
cw.inner = innerconnection
return cw
}

// Make makes an HTTP request
func (cw PushConnectionWrapper) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error {
// Check to see if the token is about to expire, if it is, refresh it first
token, found := cw.portalProxy.GetCNSITokenRecord(cw.config.EndpointID, cw.config.UserID)
if found {
// Aways update the access token, in case someone else refreshed it
cw.config.AuthToken = token.AuthToken

// Check if this is about to expire in the next 30 seconds
expiry := token.TokenExpiry - 30
expTime := time.Unix(expiry, 0)
if expTime.Before(time.Now()) {
cnsiRecord, err := cw.portalProxy.GetCNSIRecord(cw.config.EndpointID)
if err == nil {
// Refresh token first - makes sure it will be valid when we do the push
refreshedTokenRec, err := cw.portalProxy.RefreshOAuthToken(cnsiRecord.SkipSSLValidation, cnsiRecord.GUID, cw.config.UserID, cnsiRecord.ClientId, cnsiRecord.ClientSecret, cnsiRecord.TokenEndpoint)
if err == nil {
cw.config.AuthToken = refreshedTokenRec.AuthToken
}
}
}
}

cw.cmdConfig.SetAccessToken("bearer " + cw.config.AuthToken)

return cw.inner.Make(request, passedResponse)
}
24 changes: 10 additions & 14 deletions src/jetstream/plugins/cfapppush/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ func (cfAppPush *CFAppPush) deploy(echoContext echo.Context) error {
// App ID is this is a redeploy
appID := echoContext.QueryParam("app")

log.Debug("UpgradeToWebSocket")
clientWebSocket, pingTicker, err := interfaces.UpgradeToWebSocket(echoContext)
log.Debug("UpgradeToWebSocket done")
if err != nil {
log.Errorf("Upgrade to websocket failed due to: %+v", err)
return err
Expand Down Expand Up @@ -197,9 +199,9 @@ func (cfAppPush *CFAppPush) deploy(echoContext echo.Context) error {
pushConfig.DialTimeout = dialTimeout

// Initialise Push Command
cfAppPush.cfPush = Constructor(pushConfig, cfAppPush.portalProxy)
cfPush := Constructor(pushConfig, cfAppPush.portalProxy)

err = cfAppPush.cfPush.Init(appDir, manifestFile, overrides)
err = cfPush.Init(appDir, manifestFile, overrides)
if err != nil {
log.Warnf("Failed to parse due to: %+v", err)
sendErrorMessage(clientWebSocket, err, CLOSE_FAILURE)
Expand All @@ -208,7 +210,7 @@ func (cfAppPush *CFAppPush) deploy(echoContext echo.Context) error {

sendEvent(clientWebSocket, EVENT_PUSH_STARTED)

err = cfAppPush.cfPush.Run(cfAppPush, clientWebSocket)
err = cfPush.Run(cfAppPush, clientWebSocket)
if err != nil {
log.Warnf("Failed to execute due to: %+v", err)
sendErrorMessage(clientWebSocket, err, CLOSE_PUSH_ERROR)
Expand Down Expand Up @@ -479,34 +481,28 @@ func (cfAppPush *CFAppPush) getConfigData(echoContext echo.Context, cnsiGUID str
sendErrorMessage(clientWebSocket, err, CLOSE_NO_SESSION)
return nil, err
}
_, found := cfAppPush.portalProxy.GetCNSITokenRecord(cnsiGUID, userID)

token, found := cfAppPush.portalProxy.GetCNSITokenRecord(cnsiGUID, userID)
if !found {
log.Warnf("Failed to retrieve record for CNSI %s", cnsiGUID)
sendErrorMessage(clientWebSocket, err, CLOSE_NO_CNSI_USERTOKEN)
return nil, errors.New("Failed to find token record")
}

// Refresh token first - makes sure it will be valid when we do the push
refreshedTokenRec, err := cfAppPush.portalProxy.RefreshOAuthToken(cnsiRecord.SkipSSLValidation, cnsiRecord.GUID, userID, cnsiRecord.ClientId, cnsiRecord.ClientSecret, cnsiRecord.TokenEndpoint)
if err != nil {
log.Warnf("Couldn't get refresh token for endpoint with GUID %s", cnsiRecord.GUID)
sendErrorMessage(clientWebSocket, err, CLOSE_NO_CNSI_USERTOKEN)
return nil, fmt.Errorf("Couldn't get refresh token for endpoint with GUID %s", cnsiRecord.GUID)
}

config := &CFPushAppConfig{
AuthorizationEndpoint: cnsiRecord.AuthorizationEndpoint,
CFClient: cnsiRecord.ClientId,
CFClientSecret: cnsiRecord.ClientSecret,
APIEndpointURL: cnsiRecord.APIEndpoint.String(),
DopplerLoggingEndpoint: cnsiRecord.DopplerLoggingEndpoint,
SkipSSLValidation: cnsiRecord.SkipSSLValidation,
AuthToken: refreshedTokenRec.AuthToken,
RefreshToken: refreshedTokenRec.RefreshToken,
AuthToken: token.AuthToken,
OrgGUID: orgGUID,
OrgName: orgName,
SpaceGUID: spaceGUID,
SpaceName: spaceName,
EndpointID: cnsiGUID,
UserID: userID,
}

return config, nil
Expand Down
1 change: 1 addition & 0 deletions src/jetstream/plugins/cfapppush/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func (c *CFPushApp) setEndpointInfo(config *configv3.Config) error {
// Got the info we need - update the config with it
config.SetTargetInformation(apiEndpoint, info.APIVersion, info.AuthorizationEndpoint, info.MinCLIVersion, info.DopplerLoggingEndpoint, info.RoutingEndpoint, skipSSLValidation)
config.SetAccessToken("bearer " + c.config.AuthToken)
// Note: We do not give the refresh token to the CLI code as we do NOT want it to refresh the token
} else {
return errors.New("Did not get a CF /v2/info response")
}
Expand Down
1 change: 0 additions & 1 deletion src/jetstream/plugins/cfapppush/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
// CFAppPush is a plugin to allow applications to be pushed to Cloud Foundry from Stratos
type CFAppPush struct {
portalProxy interfaces.PortalProxy
cfPush CFPush
}

// Init creates a new CFAppPush
Expand Down
16 changes: 13 additions & 3 deletions src/jetstream/plugins/cfapppush/pushapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ type CFPushAppConfig struct {
DopplerLoggingEndpoint string
SkipSSLValidation bool
AuthToken string
RefreshToken string
OrgGUID string
OrgName string
SpaceGUID string
SpaceName string
OutputWriter io.Writer
DialTimeout string
EndpointID string
UserID string
}

// CFPushAppOverrides represents the document that can be sent from the client with the app overrrides for the push
Expand Down Expand Up @@ -308,8 +309,8 @@ func (c *CFPushApp) Run(msgSender DeployAppMessageSender, clientWebsocket *webso
return nil
}

func (push *CFPushApp) setup(config command.Config, ui command.UI, msgSender DeployAppMessageSender, clientWebsocket *websocket.Conn) error {
cmd := push.pushCommand
func (c *CFPushApp) setup(config command.Config, ui command.UI, msgSender DeployAppMessageSender, clientWebsocket *websocket.Conn) error {
cmd := c.pushCommand
cmd.UI = ui
cmd.Config = config
sharedActor := sharedaction.NewActor(config)
Expand All @@ -320,6 +321,15 @@ func (push *CFPushApp) setup(config command.Config, ui command.UI, msgSender Dep
return err
}

// Initialize connection wrapper that will refresh the auto token if needed
pushConnectionWrapper := PushConnectionWrapper{
portalProxy: c.portalProxy,
config: c.config,
cmdConfig: config,
}

ccClient.WrapConnection(pushConnectionWrapper)

ccClientV3, _, err := sharedV3.NewV3BasedClients(config, ui, true)
if err != nil {
return err
Expand Down

0 comments on commit cdf0f53

Please sign in to comment.