From 308f923e48e8d0e14ffd05016fb7599c9bf347f4 Mon Sep 17 00:00:00 2001 From: Yu Cao Date: Thu, 1 Nov 2018 17:51:24 -0400 Subject: [PATCH] use github.com/goji/httpauth instead --- prog/app.go | 16 +- prog/app_auth.go | 66 ------- vendor/github.com/goji/httpauth/LICENSE | 20 ++ vendor/github.com/goji/httpauth/basic_auth.go | 185 ++++++++++++++++++ vendor/manifest | 8 + 5 files changed, 218 insertions(+), 77 deletions(-) delete mode 100644 prog/app_auth.go create mode 100644 vendor/github.com/goji/httpauth/LICENSE create mode 100644 vendor/github.com/goji/httpauth/basic_auth.go diff --git a/prog/app.go b/prog/app.go index 3139fac6b..7a36cbccc 100644 --- a/prog/app.go +++ b/prog/app.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/goji/httpauth" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" @@ -207,12 +208,6 @@ func appMain(flags appFlags) { setLogFormatter(flags.logPrefix) runtime.SetBlockProfileRate(flags.blockProfileRate) - if flags.basicAuth { - log.Infof("Basic authentication enabled") - } else { - log.Infof("Basic authentication disabled") - } - traceCloser := tracing.NewFromEnv(fmt.Sprintf("scope-%s", flags.serviceName)) defer traceCloser.Close() @@ -306,11 +301,10 @@ func appMain(flags appFlags) { } if flags.basicAuth { - handler = BasicAuthentication{ - Realm: "Restricted", - User: flags.username, - Password: flags.password, - }.Wrap(handler) + log.Infof("Basic authentication enabled") + handler = httpauth.SimpleBasicAuth(flags.username, flags.password)(handler) + } else { + log.Infof("Basic authentication disabled") } server := &graceful.Server{ diff --git a/prog/app_auth.go b/prog/app_auth.go deleted file mode 100644 index 75562c5a4..000000000 --- a/prog/app_auth.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "bytes" - "crypto/sha256" - "crypto/subtle" - "encoding/base64" - "fmt" - "net/http" - "strings" -) - -// BasicAuthentication middleware authenticate http request -type BasicAuthentication struct { - Realm string - User string - Password string -} - -// Wrap implements Middleware -func (b BasicAuthentication) Wrap(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authenticated := b.authenticate(r) - if !authenticated { - b.requestAuth(w, r) - } else { - next.ServeHTTP(w, r) - } - }) -} -func (b *BasicAuthentication) authenticate(r *http.Request) bool { - const basicScheme string = "Basic " - // Confirm the request is sending Basic Authentication credentials. - auth := r.Header.Get("Authorization") - if !strings.HasPrefix(auth, basicScheme) { - return false - } - str, err := base64.StdEncoding.DecodeString(auth[len(basicScheme):]) - if err != nil { - return false - } - - creds := bytes.SplitN(str, []byte(":"), 2) - - if len(creds) != 2 { - return false - } - - givenUser := sha256.Sum256([]byte(string(creds[0]))) - givenPass := sha256.Sum256([]byte(string(creds[1]))) - requiredUser := sha256.Sum256([]byte(b.User)) - requiredPass := sha256.Sum256([]byte(b.Password)) - // Compare the supplied credentials to those set in our options - if subtle.ConstantTimeCompare(givenUser[:], requiredUser[:]) == 1 && - subtle.ConstantTimeCompare(givenPass[:], requiredPass[:]) == 1 { - return true - } - - return false -} - -func (b *BasicAuthentication) requestAuth(w http.ResponseWriter, r *http.Request) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, b.Realm)) - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return -} diff --git a/vendor/github.com/goji/httpauth/LICENSE b/vendor/github.com/goji/httpauth/LICENSE new file mode 100644 index 000000000..316bced77 --- /dev/null +++ b/vendor/github.com/goji/httpauth/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014 Carl Jackson (carl@avtok.com), Matt Silverlock (matt@eatsleeprepeat.net) + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/goji/httpauth/basic_auth.go b/vendor/github.com/goji/httpauth/basic_auth.go new file mode 100644 index 000000000..a14c27ab1 --- /dev/null +++ b/vendor/github.com/goji/httpauth/basic_auth.go @@ -0,0 +1,185 @@ +package httpauth + +import ( + "bytes" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "fmt" + "net/http" + "strings" +) + +type basicAuth struct { + h http.Handler + opts AuthOptions +} + +// AuthOptions stores the configuration for HTTP Basic Authentication. +// +// A http.Handler may also be passed to UnauthorizedHandler to override the +// default error handler if you wish to serve a custom template/response. +type AuthOptions struct { + Realm string + User string + Password string + AuthFunc func(string, string, *http.Request) bool + UnauthorizedHandler http.Handler +} + +// Satisfies the http.Handler interface for basicAuth. +func (b basicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Check if we have a user-provided error handler, else set a default + if b.opts.UnauthorizedHandler == nil { + b.opts.UnauthorizedHandler = http.HandlerFunc(defaultUnauthorizedHandler) + } + + // Check that the provided details match + if b.authenticate(r) == false { + b.requestAuth(w, r) + return + } + + // Call the next handler on success. + b.h.ServeHTTP(w, r) +} + +// authenticate retrieves and then validates the user:password combination provided in +// the request header. Returns 'false' if the user has not successfully authenticated. +func (b *basicAuth) authenticate(r *http.Request) bool { + const basicScheme string = "Basic " + + if r == nil { + return false + } + + // In simple mode, prevent authentication with empty credentials if User is + // not set. Allow empty passwords to support non-password use-cases. + if b.opts.AuthFunc == nil && b.opts.User == "" { + return false + } + + // Confirm the request is sending Basic Authentication credentials. + auth := r.Header.Get("Authorization") + if !strings.HasPrefix(auth, basicScheme) { + return false + } + + // Get the plain-text username and password from the request. + // The first six characters are skipped - e.g. "Basic ". + str, err := base64.StdEncoding.DecodeString(auth[len(basicScheme):]) + if err != nil { + return false + } + + // Split on the first ":" character only, with any subsequent colons assumed to be part + // of the password. Note that the RFC2617 standard does not place any limitations on + // allowable characters in the password. + creds := bytes.SplitN(str, []byte(":"), 2) + + if len(creds) != 2 { + return false + } + + givenUser := string(creds[0]) + givenPass := string(creds[1]) + + // Default to Simple mode if no AuthFunc is defined. + if b.opts.AuthFunc == nil { + b.opts.AuthFunc = b.simpleBasicAuthFunc + } + + return b.opts.AuthFunc(givenUser, givenPass, r) +} + +// simpleBasicAuthFunc authenticates the supplied username and password against +// the User and Password set in the Options struct. +func (b *basicAuth) simpleBasicAuthFunc(user, pass string, r *http.Request) bool { + // Equalize lengths of supplied and required credentials + // by hashing them + givenUser := sha256.Sum256([]byte(user)) + givenPass := sha256.Sum256([]byte(pass)) + requiredUser := sha256.Sum256([]byte(b.opts.User)) + requiredPass := sha256.Sum256([]byte(b.opts.Password)) + + // Compare the supplied credentials to those set in our options + if subtle.ConstantTimeCompare(givenUser[:], requiredUser[:]) == 1 && + subtle.ConstantTimeCompare(givenPass[:], requiredPass[:]) == 1 { + return true + } + + return false +} + +// Require authentication, and serve our error handler otherwise. +func (b *basicAuth) requestAuth(w http.ResponseWriter, r *http.Request) { + w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, b.opts.Realm)) + b.opts.UnauthorizedHandler.ServeHTTP(w, r) +} + +// defaultUnauthorizedHandler provides a default HTTP 401 Unauthorized response. +func defaultUnauthorizedHandler(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) +} + +// BasicAuth provides HTTP middleware for protecting URIs with HTTP Basic Authentication +// as per RFC 2617. The server authenticates a user:password combination provided in the +// "Authorization" HTTP header. +// +// Example: +// +// package main +// +// import( +// "net/http" +// "github.com/zenazn/goji" +// "github.com/goji/httpauth" +// ) +// +// func main() { +// basicOpts := httpauth.AuthOptions{ +// Realm: "Restricted", +// User: "Dave", +// Password: "ClearText", +// } +// +// goji.Use(httpauth.BasicAuth(basicOpts), SomeOtherMiddleware) +// goji.Get("/thing", myHandler) +// } +// +// Note: HTTP Basic Authentication credentials are sent in plain text, and therefore it does +// not make for a wholly secure authentication mechanism. You should serve your content over +// HTTPS to mitigate this, noting that "Basic Authentication" is meant to be just that: basic! +func BasicAuth(o AuthOptions) func(http.Handler) http.Handler { + fn := func(h http.Handler) http.Handler { + return basicAuth{h, o} + } + return fn +} + +// SimpleBasicAuth is a convenience wrapper around BasicAuth. It takes a user and password, and +// returns a pre-configured BasicAuth handler using the "Restricted" realm and a default 401 handler. +// +// Example: +// +// package main +// +// import( +// "net/http" +// "github.com/zenazn/goji/web/httpauth" +// ) +// +// func main() { +// +// goji.Use(httpauth.SimpleBasicAuth("dave", "somepassword"), SomeOtherMiddleware) +// goji.Get("/thing", myHandler) +// } +// +func SimpleBasicAuth(user, password string) func(http.Handler) http.Handler { + opts := AuthOptions{ + Realm: "Restricted", + User: user, + Password: password, + } + return BasicAuth(opts) +} diff --git a/vendor/manifest b/vendor/manifest index a100a25a8..5578e4b98 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -871,6 +871,14 @@ "path": "types", "notests": true }, + { + "importpath": "github.com/goji/httpauth", + "repository": "https://github.com/goji/httpauth", + "vcs": "git", + "revision": "2da839ab0f4df05a6db5eb277995589dadbd4fb9", + "branch": "master", + "notests": true + }, { "importpath": "github.com/golang/glog", "repository": "https://github.com/golang/glog",