From 18eb08d37d947b7a5b7e000ecb036123987988e6 Mon Sep 17 00:00:00 2001 From: "hkantare@in.ibm.com" Date: Mon, 12 Aug 2019 12:08:45 +0530 Subject: [PATCH] Add support for refresh token for softlayer-sdk --- ibm/config.go | 4 +- .../softlayer/softlayer-go/session/rest.go | 92 +++++++++++++++++-- .../softlayer/softlayer-go/session/session.go | 3 + 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/ibm/config.go b/ibm/config.go index efc6e18c31..b6c9ef31f8 100644 --- a/ibm/config.go +++ b/ibm/config.go @@ -344,6 +344,7 @@ func (c *Config) ClientSession() (interface{}, error) { session.bmxUserDetails = userConfig if sess.SoftLayerSession != nil && sess.SoftLayerSession.IAMToken != "" { sess.SoftLayerSession.IAMToken = sess.BluemixSession.Config.IAMAccessToken + sess.SoftLayerSession.IAMRefreshToken = sess.BluemixSession.Config.IAMRefreshToken } } @@ -459,9 +460,10 @@ func newSession(c *Config) (*Session, error) { RetryWait: c.RetryDelay, } - if c.IAMToken != "" { + if c.IAMToken != "" && c.IAMRefreshToken != "" { log.Println("Configuring SoftLayer Session with token") softlayerSession.IAMToken = c.IAMToken + softlayerSession.IAMRefreshToken = c.IAMRefreshToken } if c.SoftLayerAPIKey != "" && c.SoftLayerUserName != "" { log.Println("Configuring SoftLayer Session with API key") diff --git a/vendor/github.com/softlayer/softlayer-go/session/rest.go b/vendor/github.com/softlayer/softlayer-go/session/rest.go index 1d7e4b8064..011bb77db1 100644 --- a/vendor/github.com/softlayer/softlayer-go/session/rest.go +++ b/vendor/github.com/softlayer/softlayer-go/session/rest.go @@ -18,6 +18,7 @@ package session import ( "bytes" + "encoding/base64" "encoding/json" "fmt" "io/ioutil" @@ -35,6 +36,21 @@ import ( type RestTransport struct{} +const IBMCLOUDIAMENDPOINT = "https://iam.cloud.ibm.com/identity/token" + +//IAMTokenResponse ... +type IAMTokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` +} + +// IAMErrorMessage - +type IAMErrorMessage struct { + ErrorMessage string `json:"errormessage"` + ErrorCode string `json:"errorcode"` +} + // DoRequest - Implementation of the TransportHandler interface for handling // calls to the REST endpoint. func (r *RestTransport) DoRequest(sess *Session, service string, method string, args []interface{}, options *sl.Options, pResult interface{}) error { @@ -56,7 +72,7 @@ func (r *RestTransport) DoRequest(sess *Session, service string, method string, sess, path, restMethod, - bytes.NewBuffer(parameters), + parameters, options) if err != nil { @@ -167,11 +183,11 @@ func encodeQuery(opts *sl.Options) string { func sendHTTPRequest( sess *Session, path string, requestType string, - requestBody *bytes.Buffer, options *sl.Options) ([]byte, int, error) { + requestBody []byte, options *sl.Options) ([]byte, int, error) { retries := sess.Retries if retries < 2 { - return makeHTTPRequest(sess, path, requestType, requestBody, options) + return makeHTTPRequest(sess, path, requestType, bytes.NewBuffer(requestBody), options) } wait := sess.RetryWait @@ -184,11 +200,27 @@ func sendHTTPRequest( func tryHTTPRequest( retries int, wait time.Duration, sess *Session, - path string, requestType string, requestBody *bytes.Buffer, + path string, requestType string, requestBody []byte, options *sl.Options) ([]byte, int, error) { - - resp, code, err := makeHTTPRequest(sess, path, requestType, requestBody, options) + resp, code, err := makeHTTPRequest(sess, path, requestType, bytes.NewBuffer(requestBody), options) if err != nil { + if code == 500 && (sess.IAMToken != "" && sess.IAMRefreshToken != "") { + authErr := refreshToken(sess) + if authErr == nil { + if retries--; retries > 0 { + jitter := time.Duration(rand.Int63n(int64(wait))) + wait = wait + jitter/2 + time.Sleep(wait) + return tryHTTPRequest( + retries, wait, sess, path, requestType, requestBody, options) + } + } + if authErr != nil { + return resp, code, fmt.Errorf("Unable to refresh auth token: {{%v}}", authErr) + } + + } + if !isRetryable(err) { return resp, code, err } @@ -320,3 +352,51 @@ func findResponseError(code int, resp []byte) error { } return nil } + +func refreshToken(sess *Session) error { + + client := http.DefaultClient + reqPayload := url.Values{} + reqPayload.Add("grant_type", "refresh_token") + reqPayload.Add("refresh_token", sess.IAMRefreshToken) + + req, err := http.NewRequest("POST", IBMCLOUDIAMENDPOINT, strings.NewReader(reqPayload.Encode())) + if err != nil { + return err + } + req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("bx:bx"))) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Accept", "application/json") + var token IAMTokenResponse + var eresp IAMErrorMessage + + resp, err := client.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + responseBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp != nil && resp.StatusCode != 200 { + err = json.Unmarshal(responseBody, &eresp) + if err != nil { + return err + } + if eresp.ErrorCode != "" { + return sl.Error{Exception: eresp.ErrorCode, Message: eresp.ErrorMessage} + } + } + + err = json.Unmarshal(responseBody, &token) + if err != nil { + return err + } + sess.IAMToken = fmt.Sprintf("%s %s", token.TokenType, token.AccessToken) + sess.IAMRefreshToken = token.RefreshToken + return nil +} diff --git a/vendor/github.com/softlayer/softlayer-go/session/session.go b/vendor/github.com/softlayer/softlayer-go/session/session.go index fc4c0cc87f..9384920561 100644 --- a/vendor/github.com/softlayer/softlayer-go/session/session.go +++ b/vendor/github.com/softlayer/softlayer-go/session/session.go @@ -104,6 +104,9 @@ type Session struct { //IAMToken is the IAM token secret that included IMS account for token-based authentication IAMToken string + //IAMRefreshToken is the IAM refresh token secret that required to refresh IAM Token + IAMRefreshToken string + // AuthToken is the token secret for token-based authentication AuthToken string