From 3a1cec9f8827a9950d0ca277e25309d253fa8a9a Mon Sep 17 00:00:00 2001 From: David Alpert Date: Mon, 13 Apr 2020 19:05:07 -0500 Subject: [PATCH] Enable use of Basic Auth and custom HTTP Headers Sometimes the swagger specs we want to load are behind authentication requiring basic auth or some other requirements around custom HTTP headers. This commit adds three new package-level variables, similar in scope to the existing swag.LoadHTTPTimeout variable, which can be overridden in client code (e.g. go-swagger) to customize the behaviour of the swagger spec load client: - LoadHTTPBasicAuthUsername and LoadHTTPBasicAuthPassword When both are non-empty the http.Client request's SetBasicAuth(..) method is invoked to decorate the request with these credentials as a Basic Auth value in the Authorization header. - LoadHTTPCustomHeaders Before sending the load request this map of string:string key:value pairs is applied to the request.Headers collection. This can be used to configure additional custom header requirements (e.g. your swagger.json may only be available when clients include a custom header like: X-MyApp-Header: AppName Closes #44 Signed-off-by: David Alpert add test coverage --- loading.go | 18 ++++++++++++++ loading_test.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/loading.go b/loading.go index 70f4fb3..04160b8 100644 --- a/loading.go +++ b/loading.go @@ -27,6 +27,15 @@ import ( // LoadHTTPTimeout the default timeout for load requests var LoadHTTPTimeout = 30 * time.Second +// LoadHTTPBasicAuthUsername the username to use when load requests require basic auth +var LoadHTTPBasicAuthUsername = "" + +// LoadHTTPBasicAuthPassword the password to use when load requests require basic auth +var LoadHTTPBasicAuthPassword = "" + +// LoadHTTPCustomHeaders an optional collection of custom HTTP headers for load requests +var LoadHTTPCustomHeaders = map[string]string{} + // LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in func LoadFromFileOrHTTP(path string) ([]byte, error) { return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path) @@ -59,6 +68,15 @@ func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) { if err != nil { return nil, err } + + if LoadHTTPBasicAuthUsername != "" && LoadHTTPBasicAuthPassword != "" { + req.SetBasicAuth(LoadHTTPBasicAuthUsername, LoadHTTPBasicAuthPassword) + } + + for key, val := range LoadHTTPCustomHeaders { + req.Header.Set(key, val) + } + resp, err := client.Do(req) defer func() { if resp != nil { diff --git a/loading_test.go b/loading_test.go index accb1c5..de145fb 100644 --- a/loading_test.go +++ b/loading_test.go @@ -22,6 +22,14 @@ import ( "github.com/stretchr/testify/assert" ) +const ( + validUsername = "fake-user" + validPassword = "correct-password" + invalidPassword = "incorrect-password" + sharedHeaderKey = "X-Myapp" + sharedHeaderValue = "MySecretKey" +) + func TestLoadFromHTTP(t *testing.T) { _, err := LoadFromFileOrHTTP("httx://12394:abd") @@ -44,4 +52,62 @@ func TestLoadFromHTTP(t *testing.T) { d, err := LoadFromFileOrHTTP(ts2.URL) assert.NoError(t, err) assert.Equal(t, []byte("the content"), d) + + ts3 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + u, p, ok := r.BasicAuth() + if ok && u == validUsername && p == validPassword { + rw.WriteHeader(http.StatusOK) + } else { + rw.WriteHeader(http.StatusForbidden) + } + })) + defer ts3.Close() + + // no auth + _, err = LoadFromFileOrHTTP(ts3.URL) + assert.Error(t, err) + + // basic auth, invalide credentials + LoadHTTPBasicAuthUsername = validUsername + LoadHTTPBasicAuthPassword = invalidPassword + + _, err = LoadFromFileOrHTTP(ts3.URL) + assert.Error(t, err) + + // basic auth, valid credentials + LoadHTTPBasicAuthUsername = validUsername + LoadHTTPBasicAuthPassword = validPassword + + _, err = LoadFromFileOrHTTP(ts3.URL) + assert.NoError(t, err) + + ts4 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + myHeaders := r.Header[sharedHeaderKey] + ok := false + for _, v := range myHeaders { + if v == sharedHeaderValue { + ok = true + break + } + } + if ok { + rw.WriteHeader(http.StatusOK) + } else { + rw.WriteHeader(http.StatusForbidden) + } + })) + defer ts4.Close() + + _, err = LoadFromFileOrHTTP(ts4.URL) + assert.Error(t, err) + + LoadHTTPCustomHeaders[sharedHeaderKey] = sharedHeaderValue + + _, err = LoadFromFileOrHTTP(ts4.URL) + assert.NoError(t, err) + + // clean up for future tests + LoadHTTPBasicAuthUsername = "" + LoadHTTPBasicAuthPassword = "" + LoadHTTPCustomHeaders = map[string]string{} }