From e31bcb6505cfde20ec044bdda54b1fe8e03c6428 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 13 May 2020 12:21:02 +0100 Subject: [PATCH] CF Push: A better fix for the token expiry issue --- .../plugins/cfapppush/connection_wrapper.go | 53 +++++++++++++++++++ src/jetstream/plugins/cfapppush/deploy.go | 24 ++++----- src/jetstream/plugins/cfapppush/info.go | 1 + src/jetstream/plugins/cfapppush/main.go | 1 - src/jetstream/plugins/cfapppush/pushapp.go | 16 ++++-- 5 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 src/jetstream/plugins/cfapppush/connection_wrapper.go diff --git a/src/jetstream/plugins/cfapppush/connection_wrapper.go b/src/jetstream/plugins/cfapppush/connection_wrapper.go new file mode 100644 index 0000000000..4d72c4425f --- /dev/null +++ b/src/jetstream/plugins/cfapppush/connection_wrapper.go @@ -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) +} diff --git a/src/jetstream/plugins/cfapppush/deploy.go b/src/jetstream/plugins/cfapppush/deploy.go index a6f1ad7a47..efe17e714f 100644 --- a/src/jetstream/plugins/cfapppush/deploy.go +++ b/src/jetstream/plugins/cfapppush/deploy.go @@ -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 @@ -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) @@ -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) @@ -479,21 +481,14 @@ 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, @@ -501,12 +496,13 @@ func (cfAppPush *CFAppPush) getConfigData(echoContext echo.Context, cnsiGUID str 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 diff --git a/src/jetstream/plugins/cfapppush/info.go b/src/jetstream/plugins/cfapppush/info.go index c32b34cc2c..e273385dce 100644 --- a/src/jetstream/plugins/cfapppush/info.go +++ b/src/jetstream/plugins/cfapppush/info.go @@ -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") } diff --git a/src/jetstream/plugins/cfapppush/main.go b/src/jetstream/plugins/cfapppush/main.go index 25a3596697..6fb15d46a4 100644 --- a/src/jetstream/plugins/cfapppush/main.go +++ b/src/jetstream/plugins/cfapppush/main.go @@ -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 diff --git a/src/jetstream/plugins/cfapppush/pushapp.go b/src/jetstream/plugins/cfapppush/pushapp.go index 11a1c00976..0b033e0247 100644 --- a/src/jetstream/plugins/cfapppush/pushapp.go +++ b/src/jetstream/plugins/cfapppush/pushapp.go @@ -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 @@ -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) @@ -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