| 268 | } |
| 269 | |
| 270 | func TestHTTPClientRedirectAuthenticationHeaderHandling(t *testing.T) { |
| 271 | // Two servers stand in for two different hosts. A dial map lets the test |
| 272 | // address them by hostname, so the auth layer compares real hostnames rather |
| 273 | // than the ephemeral ports of localhost servers, which it no longer treats as |
| 274 | // part of the host. |
| 275 | var serverRequest *http.Request |
| 276 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 277 | serverRequest = r |
| 278 | w.WriteHeader(http.StatusNoContent) |
| 279 | })) |
| 280 | defer server.Close() |
| 281 | |
| 282 | var redirectRequest *http.Request |
| 283 | redirectServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 284 | redirectRequest = r |
| 285 | http.Redirect(w, r, "http://canonical.example/", http.StatusFound) |
| 286 | })) |
| 287 | defer redirectServer.Close() |
| 288 | |
| 289 | hostToAddr := map[string]string{ |
| 290 | "other.example": redirectServer.Listener.Addr().String(), |
| 291 | "canonical.example": server.Listener.Addr().String(), |
| 292 | } |
| 293 | dialer := &net.Dialer{} |
| 294 | transport := &http.Transport{ |
| 295 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { |
| 296 | if host, _, err := net.SplitHostPort(addr); err == nil { |
| 297 | if mapped, ok := hostToAddr[host]; ok { |
| 298 | addr = mapped |
| 299 | } |
| 300 | } |
| 301 | return dialer.DialContext(ctx, network, addr) |
| 302 | }, |
| 303 | } |
| 304 | config := tinyConfig{ |
| 305 | "other.example:oauth_token": "OTHER-TOKEN", |
| 306 | "canonical.example:oauth_token": "CANONICAL-TOKEN", |
| 307 | } |
| 308 | client := &http.Client{Transport: AddAuthTokenHeader(transport, config)} |
| 309 | |
| 310 | req, err := http.NewRequest("GET", "http://other.example/", nil) |
| 311 | require.NoError(t, err) |
| 312 | |
| 313 | res, err := client.Do(req) |
| 314 | require.NoError(t, err) |
| 315 | |
| 316 | // The initial request is authenticated as its own host. |
| 317 | assert.Equal(t, "token OTHER-TOKEN", redirectRequest.Header.Get(authorization)) |
| 318 | // Following the redirect crosses to a different host, so no token is attached, |
| 319 | // even though one is configured for that host. |
| 320 | assert.Equal(t, "", serverRequest.Header.Get(authorization)) |
| 321 | assert.Equal(t, 204, res.StatusCode) |
| 322 | } |
| 323 | |
| 324 | // serverHostname returns the hostname of a test server URL, so config can be |
| 325 | // keyed by host, as production config is, rather than host:port. |