From 99a1aca69cc9bf7af7ede36c897a36a3b75dea6f Mon Sep 17 00:00:00 2001 From: clayton-gonsalves Date: Tue, 28 Jun 2022 16:26:49 +0530 Subject: [PATCH 1/4] add User-Agent header to oauth2 requests Signed-off-by: clayton-gonsalves --- config/http_config.go | 28 +++++++++++++++++++ config/http_config_test.go | 19 +++++++++++-- .../http.conf.oauth2-user-agent.good.yml | 5 ++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 config/testdata/http.conf.oauth2-user-agent.good.yml diff --git a/config/http_config.go b/config/http_config.go index 063edde0..803f6a1c 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -224,6 +224,8 @@ type OAuth2 struct { ProxyURL URL `yaml:"proxy_url,omitempty" json:"proxy_url,omitempty"` // TLSConfig is used to connect to the token URL. TLSConfig TLSConfig `yaml:"tls_config,omitempty"` + // UserAgent is used to set a custom User-Agent http header while making the oauth request. + UserAgent string `yaml:"user_agent,omitempty" json:"user_agent,omitempty"` } // SetDirectory joins any relative file paths with dir. @@ -681,6 +683,10 @@ func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, erro } } + if rt.config.UserAgent != "" { + t = NewUserAgentRoundTripper(rt.config.UserAgent, t) + } + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{Transport: t}) tokenSource := config.TokenSource(ctx) @@ -911,6 +917,28 @@ func (t *tlsRoundTripper) CloseIdleConnections() { } } +type userAgentRoundTripper struct { + userAgent string + rt http.RoundTripper +} + +// NewUserAgentRoundTripper adds the user agent every request header. +func NewUserAgentRoundTripper(userAgent string, rt http.RoundTripper) http.RoundTripper { + return &userAgentRoundTripper{userAgent, rt} +} + +func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + req = cloneRequest(req) + req.Header.Set("User-Agent", rt.userAgent) + return rt.rt.RoundTrip(req) +} + +func (rt *userAgentRoundTripper) CloseIdleConnections() { + if ci, ok := rt.rt.(closeIdler); ok { + ci.CloseIdleConnections() + } +} + func (c HTTPClientConfig) String() string { b, err := yaml.Marshal(c) if err != nil { diff --git a/config/http_config_test.go b/config/http_config_test.go index 06eb6d04..9c9c6237 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -1183,6 +1183,12 @@ type oauth2TestServerResponse struct { func TestOAuth2(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/token" { + if r.Header.Get("User-Agent") != "myuseragent" { + t.Fatalf("Expected User-Agent header in oauth request to be 'myuseragent', got '%s'", r.Header.Get("User-Agent")) + } + } + res, _ := json.Marshal(oauth2TestServerResponse{ AccessToken: "12345", TokenType: "Bearer", @@ -1198,7 +1204,8 @@ client_secret: 2 scopes: - A - B -token_url: %s +token_url: %s/token +user_agent: myuseragent endpoint_params: hi: hello `, ts.URL) @@ -1207,7 +1214,8 @@ endpoint_params: ClientSecret: "2", Scopes: []string{"A", "B"}, EndpointParams: map[string]string{"hi": "hello"}, - TokenURL: ts.URL, + TokenURL: fmt.Sprintf("%s/token", ts.URL), + UserAgent: "myuseragent", } var unmarshalledConfig OAuth2 @@ -1488,3 +1496,10 @@ func TestOAuth2Proxy(t *testing.T) { t.Errorf("Error loading OAuth2 client config: %v", err) } } + +func TestOAuth2UserAgent(t *testing.T) { + _, _, err := LoadHTTPConfigFile("testdata/http.conf.oauth2-user-agent.good.yml") + if err != nil { + t.Errorf("Error loading OAuth2 client config: %v", err) + } +} diff --git a/config/testdata/http.conf.oauth2-user-agent.good.yml b/config/testdata/http.conf.oauth2-user-agent.good.yml new file mode 100644 index 00000000..a0a407f2 --- /dev/null +++ b/config/testdata/http.conf.oauth2-user-agent.good.yml @@ -0,0 +1,5 @@ +oauth2: + client_id: "myclient" + client_secret: "mysecret" + token_url: "http://auth" + user_agent: "myuseragent" From 316097ce731d915502acf7640147cb5512f31ec5 Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Thu, 30 Jun 2022 15:57:28 +0200 Subject: [PATCH 2/4] Use WithUserAgent Signed-off-by: Julien Pivotto --- config/http_config.go | 24 +++++-- config/http_config_test.go | 63 ++++++++++++++----- .../http.conf.oauth2-user-agent.good.yml | 5 -- 3 files changed, 64 insertions(+), 28 deletions(-) delete mode 100644 config/testdata/http.conf.oauth2-user-agent.good.yml diff --git a/config/http_config.go b/config/http_config.go index 803f6a1c..d3f5be4d 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -224,8 +224,6 @@ type OAuth2 struct { ProxyURL URL `yaml:"proxy_url,omitempty" json:"proxy_url,omitempty"` // TLSConfig is used to connect to the token URL. TLSConfig TLSConfig `yaml:"tls_config,omitempty"` - // UserAgent is used to set a custom User-Agent http header while making the oauth request. - UserAgent string `yaml:"user_agent,omitempty" json:"user_agent,omitempty"` } // SetDirectory joins any relative file paths with dir. @@ -374,6 +372,7 @@ type httpClientOptions struct { keepAlivesEnabled bool http2Enabled bool idleConnTimeout time.Duration + userAgent string } // HTTPClientOption defines an option that can be applied to the HTTP client. @@ -407,6 +406,13 @@ func WithIdleConnTimeout(timeout time.Duration) HTTPClientOption { } } +// WithIdleConnTimeout allows setting the user agent. +func WithUserAgent(ua string) HTTPClientOption { + return func(opts *httpClientOptions) { + opts.userAgent = ua + } +} + // NewClient returns a http.Client using the specified http.RoundTripper. func newClient(rt http.RoundTripper) *http.Client { return &http.Client{Transport: rt} @@ -499,8 +505,12 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, cfg.BasicAuth.PasswordFile, rt) } + if opts.userAgent != "" { + rt = NewUserAgentRoundTripper(opts.userAgent, rt) + } + if cfg.OAuth2 != nil { - rt = NewOAuth2RoundTripper(cfg.OAuth2, rt) + rt = NewOAuth2RoundTripper(cfg.OAuth2, rt, &opts) } // Return a new configured RoundTripper. return rt, nil @@ -621,12 +631,14 @@ type oauth2RoundTripper struct { next http.RoundTripper secret string mtx sync.RWMutex + opts *httpClientOptions } -func NewOAuth2RoundTripper(config *OAuth2, next http.RoundTripper) http.RoundTripper { +func NewOAuth2RoundTripper(config *OAuth2, next http.RoundTripper, opts *httpClientOptions) http.RoundTripper { return &oauth2RoundTripper{ config: config, next: next, + opts: opts, } } @@ -683,8 +695,8 @@ func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, erro } } - if rt.config.UserAgent != "" { - t = NewUserAgentRoundTripper(rt.config.UserAgent, t) + if rt.opts.userAgent != "" { + t = NewUserAgentRoundTripper(rt.opts.userAgent, t) } ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{Transport: t}) diff --git a/config/http_config_test.go b/config/http_config_test.go index 9c9c6237..eb8208a4 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -1183,12 +1183,6 @@ type oauth2TestServerResponse struct { func TestOAuth2(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/token" { - if r.Header.Get("User-Agent") != "myuseragent" { - t.Fatalf("Expected User-Agent header in oauth request to be 'myuseragent', got '%s'", r.Header.Get("User-Agent")) - } - } - res, _ := json.Marshal(oauth2TestServerResponse{ AccessToken: "12345", TokenType: "Bearer", @@ -1205,7 +1199,6 @@ scopes: - A - B token_url: %s/token -user_agent: myuseragent endpoint_params: hi: hello `, ts.URL) @@ -1215,7 +1208,6 @@ endpoint_params: Scopes: []string{"A", "B"}, EndpointParams: map[string]string{"hi": "hello"}, TokenURL: fmt.Sprintf("%s/token", ts.URL), - UserAgent: "myuseragent", } var unmarshalledConfig OAuth2 @@ -1227,7 +1219,7 @@ endpoint_params: t.Fatalf("Got unmarshalled config %v, expected %v", unmarshalledConfig, expectedConfig) } - rt := NewOAuth2RoundTripper(&expectedConfig, http.DefaultTransport) + rt := NewOAuth2RoundTripper(&expectedConfig, http.DefaultTransport, &defaultHTTPClientOptions) client := http.Client{ Transport: rt, @@ -1240,6 +1232,50 @@ endpoint_params: } } +func TestOAuth2UserAgent(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + if r.Header.Get("User-Agent") != "myuseragent" { + t.Fatalf("Expected User-Agent header in oauth request to be 'myuseragent', got '%s'", r.Header.Get("User-Agent")) + } + } + + res, _ := json.Marshal(oauth2TestServerResponse{ + AccessToken: "12345", + TokenType: "Bearer", + }) + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(res) + })) + defer ts.Close() + + config := &OAuth2{ + ClientID: "1", + ClientSecret: "2", + Scopes: []string{"A", "B"}, + EndpointParams: map[string]string{"hi": "hello"}, + TokenURL: fmt.Sprintf("%s/token", ts.URL), + } + + opts := defaultHTTPClientOptions + WithUserAgent("myuseragent")(&opts) + + rt := NewOAuth2RoundTripper(config, http.DefaultTransport, &opts) + + client := http.Client{ + Transport: rt, + } + resp, err := client.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + + authorization := resp.Request.Header.Get("Authorization") + if authorization != "Bearer 12345" { + t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization) + } +} + func TestOAuth2WithFile(t *testing.T) { var expectedAuth *string var previousAuth string @@ -1302,7 +1338,7 @@ endpoint_params: t.Fatalf("Got unmarshalled config %v, expected %v", unmarshalledConfig, expectedConfig) } - rt := NewOAuth2RoundTripper(&expectedConfig, http.DefaultTransport) + rt := NewOAuth2RoundTripper(&expectedConfig, http.DefaultTransport, &defaultHTTPClientOptions) client := http.Client{ Transport: rt, @@ -1496,10 +1532,3 @@ func TestOAuth2Proxy(t *testing.T) { t.Errorf("Error loading OAuth2 client config: %v", err) } } - -func TestOAuth2UserAgent(t *testing.T) { - _, _, err := LoadHTTPConfigFile("testdata/http.conf.oauth2-user-agent.good.yml") - if err != nil { - t.Errorf("Error loading OAuth2 client config: %v", err) - } -} diff --git a/config/testdata/http.conf.oauth2-user-agent.good.yml b/config/testdata/http.conf.oauth2-user-agent.good.yml deleted file mode 100644 index a0a407f2..00000000 --- a/config/testdata/http.conf.oauth2-user-agent.good.yml +++ /dev/null @@ -1,5 +0,0 @@ -oauth2: - client_id: "myclient" - client_secret: "mysecret" - token_url: "http://auth" - user_agent: "myuseragent" From 2d0de856fca11926b0589316bbcbf36370ac9fae Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Thu, 30 Jun 2022 16:05:02 +0200 Subject: [PATCH 3/4] Use full roundtripper Signed-off-by: Julien Pivotto --- config/http_config_test.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/config/http_config_test.go b/config/http_config_test.go index eb8208a4..3c4c3192 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -1234,10 +1234,8 @@ endpoint_params: func TestOAuth2UserAgent(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - if r.Header.Get("User-Agent") != "myuseragent" { - t.Fatalf("Expected User-Agent header in oauth request to be 'myuseragent', got '%s'", r.Header.Get("User-Agent")) - } + if r.Header.Get("User-Agent") != "myuseragent" { + t.Fatalf("Expected User-Agent header in oauth request to be 'myuseragent', got '%s'", r.Header.Get("User-Agent")) } res, _ := json.Marshal(oauth2TestServerResponse{ @@ -1249,7 +1247,8 @@ func TestOAuth2UserAgent(t *testing.T) { })) defer ts.Close() - config := &OAuth2{ + config := DefaultHTTPClientConfig + config.OAuth2 = &OAuth2{ ClientID: "1", ClientSecret: "2", Scopes: []string{"A", "B"}, @@ -1257,10 +1256,10 @@ func TestOAuth2UserAgent(t *testing.T) { TokenURL: fmt.Sprintf("%s/token", ts.URL), } - opts := defaultHTTPClientOptions - WithUserAgent("myuseragent")(&opts) - - rt := NewOAuth2RoundTripper(config, http.DefaultTransport, &opts) + rt, err := NewRoundTripperFromConfig(config, "test_oauth2", WithUserAgent("myuseragent")) + if err != nil { + t.Fatal(err) + } client := http.Client{ Transport: rt, From db0284d115d31b8954fe69b726fbf9bab87142a5 Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Fri, 8 Jul 2022 12:03:25 +0200 Subject: [PATCH 4/4] Fix comment Signed-off-by: Julien Pivotto --- config/http_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/http_config.go b/config/http_config.go index d3f5be4d..2ce312f6 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -406,7 +406,7 @@ func WithIdleConnTimeout(timeout time.Duration) HTTPClientOption { } } -// WithIdleConnTimeout allows setting the user agent. +// WithUserAgent allows setting the user agent. func WithUserAgent(ua string) HTTPClientOption { return func(opts *httpClientOptions) { opts.userAgent = ua