@@ -38,6 +38,7 @@ import (
38
38
const (
39
39
headerNameUserAgent = "User-Agent"
40
40
sdkName = "ibm-go-sdk-core"
41
+ maxRedirects = 10
41
42
)
42
43
43
44
// ServiceOptions is a struct of configuration values for a service.
@@ -117,7 +118,7 @@ func (service *BaseService) Clone() *BaseService {
117
118
// First, copy the service options struct.
118
119
serviceOptions := * service .Options
119
120
120
- // Next, make a copy the service struct, then use the copy of the service options.
121
+ // Next, make a copy of the service struct, then use the copy of the service options.
121
122
// Note, we'll re-use the "Client" instance from the original BaseService instance.
122
123
clone := * service
123
124
clone .Options = & serviceOptions
@@ -234,7 +235,7 @@ func (service *BaseService) SetDefaultHeaders(headers http.Header) {
234
235
// the retryable client; otherwise "client" will be stored
235
236
// directly on "service".
236
237
func (service * BaseService ) SetHTTPClient (client * http.Client ) {
237
- setMinimumTLSVersion (client )
238
+ setupHTTPClient (client )
238
239
239
240
if isRetryableClient (service .Client ) {
240
241
// If "service" is currently holding a retryable client,
@@ -298,15 +299,74 @@ func (service *BaseService) IsSSLDisabled() bool {
298
299
return false
299
300
}
300
301
301
- // setMinimumTLSVersion sets the minimum TLS version required by the client to TLS v1.2
302
- func setMinimumTLSVersion (client * http.Client ) {
303
- if tr , ok := client .Transport .(* http.Transport ); tr != nil && ok {
304
- if tr .TLSClientConfig == nil {
305
- tr .TLSClientConfig = & tls.Config {} // #nosec G402
302
+ // setupHTTPClient will configure "client" for use with the BaseService object.
303
+ func setupHTTPClient (client * http.Client ) {
304
+ // Set our "CheckRedirect" function to allow safe headers to be included
305
+ // in redirected requests under certain conditions.
306
+ if client .CheckRedirect == nil {
307
+ client .CheckRedirect = checkRedirect
308
+ }
309
+ }
310
+
311
+ // checkRedirect is used as an override for the default "CheckRedirect" function supplied
312
+ // by the net/http package and implements some additional logic required by IBM SDKs.
313
+ func checkRedirect (req * http.Request , via []* http.Request ) error {
314
+
315
+ // The net/http module is implemented such that it will only include "safe" headers
316
+ // ("Authorization", "WWW-Authenticate", "Cookie", "Cookie2") when redirecting a request
317
+ // if the redirected host is the same host or a sub-domain of the original request's host.
318
+ // Example: foo.com redirected to foo.com or bar.foo.com would work, but bar.com would not.
319
+ // This "CheckRedirect" implementation will propagate "safe" headers in a redirected request
320
+ // only in situations where the hosts associated with the original and redirected request URLs
321
+ // are both located within the ".cloud.ibm.com" domain.
322
+
323
+ // First, perform the check that is done by the default CheckRedirect function
324
+ // to ensure we don't exhaust our max redirect limit.
325
+ if len (via ) >= maxRedirects {
326
+ GetLogger ().Debug ("Exceeded max redirects: %d" , maxRedirects )
327
+ return fmt .Errorf ("stopped after %d redirects" , maxRedirects )
328
+ }
329
+
330
+ if len (via ) > 0 {
331
+ GetLogger ().Debug ("Detected %d prior request(s)" , len (via ))
332
+ originalReq := via [0 ]
333
+ redirectedReq := req
334
+ GetLogger ().Debug ("Redirecting request from %s to %s" , originalReq .URL .String (), redirectedReq .URL .String ())
335
+ redirectedHeader := req .Header
336
+ originalHeader := via [0 ].Header
337
+
338
+ originalHost := originalReq .URL .Hostname ()
339
+ redirectedHost := redirectedReq .URL .Hostname ()
340
+
341
+ if shouldCopySafeHeadersOnRedirect (originalHost , redirectedHost ) {
342
+
343
+ // We're only concerned with "safe" headers since these are the ones that are not
344
+ // propagated automatically by net/http for a "cross-site" redirect.
345
+ for _ , headerKey := range []string {"Authorization" , "WWW-Authenticate" , "Cookie" , "Cookie2" } {
346
+ // If the original request contains a value for "headerKey"
347
+ // *and* this header is not already present in the redirected request,
348
+ // then copy the value from the original request to the redirected request.
349
+ if v , inOriginalRequest := originalHeader [headerKey ]; inOriginalRequest {
350
+ if _ , inRedirectedRequest := redirectedHeader [headerKey ]; ! inRedirectedRequest {
351
+ redirectedHeader [headerKey ] = v
352
+ GetLogger ().Debug ("Propagating header '%s' in redirected request" , headerKey )
353
+ }
354
+ }
355
+ }
356
+ } else {
357
+ GetLogger ().Debug ("Redirected request is not within the trusted domain." )
306
358
}
307
-
308
- tr . TLSClientConfig . MinVersion = tls . VersionTLS12
359
+ } else {
360
+ GetLogger (). Debug ( "Detected no prior requests!" )
309
361
}
362
+ return nil
363
+ }
364
+
365
+ // shouldCopySafeHeadersOnRedirect returns true iff safe headers should be copied
366
+ // to a redirected request.
367
+ func shouldCopySafeHeadersOnRedirect (fromHost , toHost string ) bool {
368
+ GetLogger ().Debug ("hosts: %s %s" , fromHost , toHost )
369
+ return strings .HasSuffix (fromHost , ".cloud.ibm.com" ) && strings .HasSuffix (toHost , ".cloud.ibm.com" )
310
370
}
311
371
312
372
// SetEnableGzipCompression sets the service's EnableGzipCompression field
@@ -693,7 +753,7 @@ func (service *BaseService) DisableRetries() {
693
753
// DefaultHTTPClient returns a non-retryable http client with default configuration.
694
754
func DefaultHTTPClient () * http.Client {
695
755
client := cleanhttp .DefaultPooledClient ()
696
- setMinimumTLSVersion (client )
756
+ setupHTTPClient (client )
697
757
return client
698
758
}
699
759
@@ -731,7 +791,7 @@ func NewRetryableClientWithHTTPClient(httpClient *http.Client) *retryablehttp.Cl
731
791
// as our embedded client used to invoke individual requests.
732
792
client .HTTPClient = httpClient
733
793
} else {
734
- // Otherwise, we'll use construct a default HTTP client and use that
794
+ // Otherwise, we'll construct a default HTTP client and use that
735
795
client .HTTPClient = DefaultHTTPClient ()
736
796
}
737
797
0 commit comments