diff --git a/HISTORY.md b/HISTORY.md index b2c93389..ce65a429 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -29,6 +29,11 @@ Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.co The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please! +# Sa, 10 June 2017 | v7.0.4 + +- Simplify and add a test for the [basicauth middleware](https://github.com/kataras/iris/tree/master/middleware/basicauth), no need to be +stored inside the Context anymore, developers can get the validated user(username and password) via `context.Request().BasicAuth()`. `basicauth.Config.ContextKey` was removed, just remove that field from your configuration, it's useless now. + # Sa, 10 June 2017 | v7.0.3 - New `context.Session().PeekFlash("key")` added, unlike `GetFlash` this will return the flash value but keep the message valid for the next requests too. diff --git a/README.md b/README.md index 00f4d6ec..567f1857 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A fast, cross-platform and efficient web framework with robust set of well-desig [![Report card](https://img.shields.io/badge/report%20card%20-a%2B-F44336.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![Support forum](https://img.shields.io/badge/support-page-ec2eb4.svg?style=flat-square)](http://support.iris-go.com) [![Examples](https://img.shields.io/badge/howto-examples-3362c2.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples#table-of-contents) -[![Godocs](https://img.shields.io/badge/7.0.3-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris) +[![Godocs](https://img.shields.io/badge/7.0.4-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris) [![Chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![Buy me a cup of coffee](https://img.shields.io/badge/support-%20open--source-F4A460.svg?logo=data:image%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIGZpbGw9InJnYigyMjAsMjIwLDIyMCkiIGQ9Ik04ODYuNiwzMDUuM2MtNDUuNywyMDMuMS0xODcsMzEwLjMtNDA5LjYsMzEwLjNoLTc0LjFsLTUxLjUsMzI2LjloLTYybC0zLjIsMjEuMWMtMi4xLDE0LDguNiwyNi40LDIyLjYsMjYuNGgxNTguNWMxOC44LDAsMzQuNy0xMy42LDM3LjctMzIuMmwxLjUtOGwyOS45LTE4OS4zbDEuOS0xMC4zYzIuOS0xOC42LDE4LjktMzIuMiwzNy43LTMyLjJoMjMuNWMxNTMuNSwwLDI3My43LTYyLjQsMzA4LjktMjQyLjdDOTIxLjYsNDA2LjgsOTE2LjcsMzQ4LjYsODg2LjYsMzA1LjN6Ii8%2BPHBhdGggZmlsbD0icmdiKDIyMCwyMjAsMjIwKSIgZD0iTTc5MS45LDgzLjlDNzQ2LjUsMzIuMiw2NjQuNCwxMCw1NTkuNSwxMEgyNTVjLTIxLjQsMC0zOS44LDE1LjUtNDMuMSwzNi44TDg1LDg1MWMtMi41LDE1LjksOS44LDMwLjIsMjUuOCwzMC4ySDI5OWw0Ny4zLTI5OS42bC0xLjUsOS40YzMuMi0yMS4zLDIxLjQtMzYuOCw0Mi45LTM2LjhINDc3YzE3NS41LDAsMzEzLTcxLjIsMzUzLjItMjc3LjVjMS4yLTYuMSwyLjMtMTIuMSwzLjEtMTcuOEM4NDUuMSwxODIuOCw4MzMuMiwxMzAuOCw3OTEuOSw4My45TDc5MS45LDgzLjl6Ii8%2BPC9zdmc%2B)](https://github.com/kataras/iris#buy-me-a-cup-of-coffee) @@ -394,7 +394,7 @@ Besides the fact that we have a [community chat][Chat] for questions or reports Version ------------ -Current: **7.0.3** +Current: **7.0.4** Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever". diff --git a/_examples/beginner/basicauth/main.go b/_examples/beginner/basicauth/main.go index fdbeae65..779fefca 100644 --- a/_examples/beginner/basicauth/main.go +++ b/_examples/beginner/basicauth/main.go @@ -12,10 +12,9 @@ func main() { app := iris.New() authConfig := basicauth.Config{ - Users: map[string]string{"myusername": "mypassword", "mySecondusername": "mySecondpassword"}, - Realm: "Authorization Required", // defaults to "Authorization Required" - ContextKey: "user", // defaults to "user" - Expires: time.Duration(30) * time.Minute, + Users: map[string]string{"myusername": "mypassword", "mySecondusername": "mySecondpassword"}, + Realm: "Authorization Required", // defaults to "Authorization Required" + Expires: time.Duration(30) * time.Minute, } authentication := basicauth.New(authConfig) @@ -23,10 +22,7 @@ func main() { // to global app.Use(authentication) (or app.UseGlobal before the .Run) // to routes /* - app.Get("/mysecret", authentication, func(ctx context.Context) { - username := ctx.Values().GetString("user") // the Contextkey from the authConfig - ctx.Writef("Hello authenticated user: %s ", username) - }) + app.Get("/mysecret", authentication, h) */ app.Get("/", func(ctx context.Context) { ctx.Redirect("/admin") }) @@ -36,23 +32,22 @@ func main() { needAuth := app.Party("/admin", authentication) { //http://localhost:8080/admin - needAuth.Get("/", func(ctx context.Context) { - username := ctx.Values().GetString("user") // the Contextkey from the authConfig - ctx.Writef("Hello authenticated user: %s from: %s", username, ctx.Path()) - }) + needAuth.Get("/", h) // http://localhost:8080/admin/profile - needAuth.Get("/profile", func(ctx context.Context) { - username := ctx.Values().GetString("user") // the Contextkey from the authConfig - ctx.Writef("Hello authenticated user: %s from: % ", username, ctx.Path()) - }) + needAuth.Get("/profile", h) // http://localhost:8080/admin/settings - needAuth.Get("/settings", func(ctx context.Context) { - username := authConfig.User(ctx) // shortcut for ctx.Values().GetString("user") - ctx.Writef("Hello authenticated user: %s from: %s", username, ctx.Path()) - }) + needAuth.Get("/settings", h) } // open http://localhost:8080/admin app.Run(iris.Addr(":8080")) } + +func h(ctx context.Context) { + username, password, _ := ctx.Request().BasicAuth() + // third parameter it will be always true because the middleware + // makes sure for that, otherwise this handler will not be executed. + + ctx.Writef("%s %s:%s", ctx.Path(), username, password) +} diff --git a/_examples/intermediate/httptest/main.go b/_examples/intermediate/httptest/main.go index c8d3ecc7..c721fdaf 100644 --- a/_examples/intermediate/httptest/main.go +++ b/_examples/intermediate/httptest/main.go @@ -6,26 +6,15 @@ import ( "github.com/kataras/iris/middleware/basicauth" ) -func buildApp() *iris.Application { +func newApp() *iris.Application { app := iris.New() authConfig := basicauth.Config{ - Users: map[string]string{"myusername": "mypassword", "mySecondusername": "mySecondpassword"}, - Realm: "Authorization Required", // defaults to "Authorization Required" - ContextKey: "user", // defaults to "user" + Users: map[string]string{"myusername": "mypassword"}, } authentication := basicauth.New(authConfig) - // to global app.Use(authentication) (or app.UseGlobal before the .Run) - // to routes - /* - app.Get("/mysecret", authentication, func(ctx context.Context) { - username := ctx.Values().GetString("user") // the Contextkey from the authConfig - ctx.Writef("Hello authenticated user: %s ", username) - }) - */ - app.Get("/", func(ctx context.Context) { ctx.Redirect("/admin") }) // to party @@ -33,27 +22,26 @@ func buildApp() *iris.Application { needAuth := app.Party("/admin", authentication) { //http://localhost:8080/admin - needAuth.Get("/", func(ctx context.Context) { - username := ctx.Values().GetString("user") // the Contextkey from the authConfig - ctx.Writef("Hello authenticated user: %s from: %s", username, ctx.Path()) - }) + needAuth.Get("/", h) // http://localhost:8080/admin/profile - needAuth.Get("/profile", func(ctx context.Context) { - username := ctx.Values().GetString("user") // the Contextkey from the authConfig - ctx.Writef("Hello authenticated user: %s from: %s", username, ctx.Path()) - }) + needAuth.Get("/profile", h) // http://localhost:8080/admin/settings - needAuth.Get("/settings", func(ctx context.Context) { - username := authConfig.User(ctx) // shortcut for ctx.Values().GetString("user") - ctx.Writef("Hello authenticated user: %s from: %s", username, ctx.Path()) - }) + needAuth.Get("/settings", h) } return app } +func h(ctx context.Context) { + username, password, _ := ctx.Request().BasicAuth() + // third parameter it will be always true because the middleware + // makes sure for that, otherwise this handler will not be executed. + + ctx.Writef("%s %s:%s", ctx.Path(), username, password) +} + func main() { - app := buildApp() + app := newApp() app.Run(iris.Addr(":8080")) } diff --git a/_examples/intermediate/httptest/main_test.go b/_examples/intermediate/httptest/main_test.go index 0f74dd97..20ea60d0 100644 --- a/_examples/intermediate/httptest/main_test.go +++ b/_examples/intermediate/httptest/main_test.go @@ -7,10 +7,10 @@ import ( "github.com/kataras/iris/httptest" ) -// $ cd _example +// $ cd $GOPATH/src/github.com/kataras/iris/_examples/intermediate/httptest // $ go test -v func TestNewApp(t *testing.T) { - app := buildApp() + app := newApp() e := httptest.New(app, t) // redirects to /admin without basic auth @@ -20,11 +20,11 @@ func TestNewApp(t *testing.T) { // with valid basic auth e.GET("/admin").WithBasicAuth("myusername", "mypassword").Expect(). - Status(iris.StatusOK).Body().Equal("Hello authenticated user: myusername from: /admin") + Status(iris.StatusOK).Body().Equal("/admin myusername:mypassword") e.GET("/admin/profile").WithBasicAuth("myusername", "mypassword").Expect(). - Status(iris.StatusOK).Body().Equal("Hello authenticated user: myusername from: /admin/profile") + Status(iris.StatusOK).Body().Equal("/admin/profile myusername:mypassword") e.GET("/admin/settings").WithBasicAuth("myusername", "mypassword").Expect(). - Status(iris.StatusOK).Body().Equal("Hello authenticated user: myusername from: /admin/settings") + Status(iris.StatusOK).Body().Equal("/admin/settings myusername:mypassword") // with invalid basic auth e.GET("/admin/settings").WithBasicAuth("invalidusername", "invalidpassword"). diff --git a/iris.go b/iris.go index bd6589c8..d51ca541 100644 --- a/iris.go +++ b/iris.go @@ -41,7 +41,7 @@ const ( // Version is the current version number of the Iris Web framework. // // Look https://github.com/kataras/iris#where-can-i-find-older-versions for older versions. - Version = "7.0.3" + Version = "7.0.4" ) const ( diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go index 0c93819f..b3c04112 100644 --- a/middleware/basicauth/basicauth.go +++ b/middleware/basicauth/basicauth.go @@ -34,13 +34,12 @@ type ( // -// New takes one parameter, the Config returns a Handler -// use: iris.Use(New(...)), iris.Get(...,New(...),...) +// New accepts basicauth.Config and returns a new Handler +// which will ask the client for basic auth (username, password), +// validate that and if valid continues to the next handler, otherwise +// throws a StatusUnauthorized http error code. func New(c Config) context.Handler { config := DefaultConfig() - if c.ContextKey != "" { - config.ContextKey = c.ContextKey - } if c.Realm != "" { config.Realm = c.Realm } @@ -51,8 +50,10 @@ func New(c Config) context.Handler { return b.Serve } -// Default takes one parameter, the users returns a Handler -// use: iris.Use(Default(...)), iris.Get(...,Default(...),...) +// Default accepts only the users and returns a new Handler +// which will ask the client for basic auth (username, password), +// validate that and if valid continues to the next handler, otherwise +// throws a StatusUnauthorized http error code. func Default(users map[string]string) context.Handler { c := DefaultConfig() c.Users = users @@ -101,26 +102,23 @@ func (b *basicAuthMiddleware) askForCredentials(ctx context.Context) { // Serve the actual middleware func (b *basicAuthMiddleware) Serve(ctx context.Context) { - if auth, found := b.findAuth(ctx.GetHeader("Authorization")); !found { + auth, found := b.findAuth(ctx.GetHeader("Authorization")) + if !found { b.askForCredentials(ctx) + return // don't continue to the next handler - } else { - // all ok set the context's value in order to be getable from the next handler - ctx.Values().Set(b.config.ContextKey, auth.Username) - if b.expireEnabled { - - if auth.logged == false { - auth.expires = time.Now().Add(b.config.Expires) - auth.logged = true - } - - if time.Now().After(auth.expires) { - b.askForCredentials(ctx) // ask for authentication again - return - } - - } - ctx.Next() // continue } + // all ok + if b.expireEnabled { + if auth.logged == false { + auth.expires = time.Now().Add(b.config.Expires) + auth.logged = true + } + if time.Now().After(auth.expires) { + b.askForCredentials(ctx) // ask for authentication again + return + } + } + ctx.Next() // continue } diff --git a/middleware/basicauth/basicauth_test.go b/middleware/basicauth/basicauth_test.go new file mode 100644 index 00000000..0809d919 --- /dev/null +++ b/middleware/basicauth/basicauth_test.go @@ -0,0 +1,67 @@ +// black-box testing +package basicauth_test + +import ( + "testing" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/httptest" + "github.com/kataras/iris/middleware/basicauth" +) + +func buildApp() *iris.Application { + app := iris.New() + + authConfig := basicauth.Config{ + Users: map[string]string{"myusername": "mypassword"}, + } + + authentication := basicauth.New(authConfig) + + app.Get("/", func(ctx context.Context) { ctx.Redirect("/admin") }) + + // to party + + needAuth := app.Party("/admin", authentication) + { + //http://localhost:8080/admin + needAuth.Get("/", h) + // http://localhost:8080/admin/profile + needAuth.Get("/profile", h) + + // http://localhost:8080/admin/settings + needAuth.Get("/settings", h) + } + + return app +} + +func h(ctx context.Context) { + username, password, _ := ctx.Request().BasicAuth() + // third parameter it will be always true because the middleware + // makes sure for that, otherwise this handler will not be executed. + + ctx.Writef("%s %s:%s", ctx.Path(), username, password) +} +func TestBasicAuth(t *testing.T) { + app := buildApp() + e := httptest.New(app, t) + + // redirects to /admin without basic auth + e.GET("/").Expect().Status(iris.StatusUnauthorized) + // without basic auth + e.GET("/admin").Expect().Status(iris.StatusUnauthorized) + + // with valid basic auth + e.GET("/admin").WithBasicAuth("myusername", "mypassword").Expect(). + Status(iris.StatusOK).Body().Equal("/admin myusername:mypassword") + e.GET("/admin/profile").WithBasicAuth("myusername", "mypassword").Expect(). + Status(iris.StatusOK).Body().Equal("/admin/profile myusername:mypassword") + e.GET("/admin/settings").WithBasicAuth("myusername", "mypassword").Expect(). + Status(iris.StatusOK).Body().Equal("/admin/settings myusername:mypassword") + + // with invalid basic auth + e.GET("/admin/settings").WithBasicAuth("invalidusername", "invalidpassword"). + Expect().Status(iris.StatusUnauthorized) +} diff --git a/middleware/basicauth/config.go b/middleware/basicauth/config.go index 93ffc83d..59473ce1 100644 --- a/middleware/basicauth/config.go +++ b/middleware/basicauth/config.go @@ -13,9 +13,6 @@ import ( const ( // DefaultBasicAuthRealm is "Authorization Required" DefaultBasicAuthRealm = "Authorization Required" - // DefaultBasicAuthContextKey is the "auth" - // this key is used to do context.Set("user", theUsernameFromBasicAuth) - DefaultBasicAuthContextKey = "user" ) // DefaultExpireTime zero time @@ -27,18 +24,16 @@ type Config struct { Users map[string]string // Realm http://tools.ietf.org/html/rfc2617#section-1.2. Default is "Authorization Required" Realm string - // ContextKey the key for ctx.GetString(...). Default is 'user' - ContextKey string // Expires expiration duration, default is 0 never expires Expires time.Duration } // DefaultConfig returns the default configs for the BasicAuth middleware func DefaultConfig() Config { - return Config{make(map[string]string), DefaultBasicAuthRealm, DefaultBasicAuthContextKey, 0} + return Config{make(map[string]string), DefaultBasicAuthRealm, 0} } -// User returns the user from context key same as 'ctx.GetString("user")' but cannot be used by the developer, this is only here in order to understand how you can get the authenticated username -func (c Config) User(ctx context.Context) string { - return ctx.Values().GetString(c.ContextKey) +// User returns the user from context key same as ctx.Request().BasicAuth(). +func (c Config) User(ctx context.Context) (string, string, bool) { + return ctx.Request().BasicAuth() }