diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 0ba112105..a93608d32 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -27,6 +27,11 @@ "Comment": "v0.1-142-g8659df7", "Rev": "8659df7a51aebe6c6120268cd5a8b4c34fa8441a" }, + { + "ImportPath": "github.com/codegangsta/martini-contrib/auth", + "Comment": "v0.1-159-g8ce6181", + "Rev": "8ce6181c2609699e4c7cd30994b76a850a9cdadc" + }, { "ImportPath": "github.com/golang/groupcache/lru", "Rev": "d781998583680cda80cf61e0b37dd0cd8da2eb52" diff --git a/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/README.md b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/README.md new file mode 100644 index 000000000..d952e37d8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/README.md @@ -0,0 +1,25 @@ +# auth +Martini middleware/handler for http basic authentication. + +[API Reference](http://godoc.org/github.com/codegangsta/martini-contrib/auth) + +## Usage + +~~~ go +import ( + "github.com/codegangsta/martini" + "github.com/codegangsta/martini-contrib/auth" +) + +func main() { + m := martini.Classic() + // authenticate every request + m.Use(auth.Basic("username", "secretpassword")) + m.Run() +} + +~~~ + +## Authors +* [Jeremy Saenz](http://github.com/codegangsta) +* [Brendon Murphy](http://github.com/bemurphy) diff --git a/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/basic.go b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/basic.go new file mode 100644 index 000000000..afe04b60f --- /dev/null +++ b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/basic.go @@ -0,0 +1,19 @@ +package auth + +import ( + "encoding/base64" + "net/http" +) + +// Basic returns a Handler that authenticates via Basic Auth. Writes a http.StatusUnauthorized +// if authentication fails +func Basic(username string, password string) http.HandlerFunc { + var siteAuth = base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + return func(res http.ResponseWriter, req *http.Request) { + auth := req.Header.Get("Authorization") + if !SecureCompare(auth, "Basic "+siteAuth) { + res.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"") + http.Error(res, "Not Authorized", http.StatusUnauthorized) + } + } +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/basic_test.go b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/basic_test.go new file mode 100644 index 000000000..8ee76ec17 --- /dev/null +++ b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/basic_test.go @@ -0,0 +1,45 @@ +package auth + +import ( + "encoding/base64" + "github.com/codegangsta/martini" + "net/http" + "net/http/httptest" + "testing" +) + +func Test_BasicAuth(t *testing.T) { + recorder := httptest.NewRecorder() + + auth := "Basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar")) + + m := martini.New() + m.Use(Basic("foo", "bar")) + m.Use(func(res http.ResponseWriter, req *http.Request) { + res.Write([]byte("hello")) + }) + + r, _ := http.NewRequest("GET", "foo", nil) + + m.ServeHTTP(recorder, r) + + if recorder.Code != 401 { + t.Error("Response not 401") + } + + if recorder.Body.String() == "hello" { + t.Error("Auth block failed") + } + + recorder = httptest.NewRecorder() + r.Header.Set("Authorization", auth) + m.ServeHTTP(recorder, r) + + if recorder.Code == 401 { + t.Error("Response is 401") + } + + if recorder.Body.String() != "hello" { + t.Error("Auth failed, got: ", recorder.Body.String()) + } +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/util.go b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/util.go new file mode 100644 index 000000000..b95b4578b --- /dev/null +++ b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/util.go @@ -0,0 +1,15 @@ +package auth + +import ( + "crypto/subtle" +) + +// SecureCompare performs a constant time compare of two strings to limit timing attacks. +func SecureCompare(given string, actual string) bool { + if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { + return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 + } else { + /* Securely compare actual to itself to keep constant time, but always return false */ + return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false + } +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/util_test.go b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/util_test.go new file mode 100644 index 000000000..3ea891d6c --- /dev/null +++ b/Godeps/_workspace/src/github.com/codegangsta/martini-contrib/auth/util_test.go @@ -0,0 +1,26 @@ +package auth + +import ( + "testing" +) + +var comparetests = []struct { + a string + b string + val bool +}{ + {"foo", "foo", true}, + {"bar", "bar", true}, + {"password", "password", true}, + {"Foo", "foo", false}, + {"foo", "foobar", false}, + {"password", "pass", false}, +} + +func Test_SecureCompare(t *testing.T) { + for _, tt := range comparetests { + if SecureCompare(tt.a, tt.b) != tt.val { + t.Errorf("Expected SecureCompare(%v, %v) to return %v but did not", tt.a, tt.b, tt.val) + } + } +}