From 73dc9adf10058f69d892436847f454476296bc0e Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 18 Mar 2017 23:43:04 +0200 Subject: [PATCH] Add an example for sessions + securecookie. Relative: http://support.iris-go.com/d/29-mark-cookie-for-session-as-secure Former-commit-id: 10c30aabdf6b8fa59457ed8296b3e87108d3861c --- HISTORY.md | 9 +- _examples/examples/sessions/main.go | 3 +- adaptors/sessions/_examples/database/main.go | 73 +++++++++++++++ .../sessions/_examples/securecookie/main.go | 79 ++++++++++++++++ .../standalone}/main.go | 13 +-- adaptors/sessions/config.go | 12 --- adaptors/sessions/cookie.go | 12 --- adaptors/sessions/sessions.go | 89 +++++++++---------- policy_sessions_test.go | 4 +- 9 files changed, 213 insertions(+), 81 deletions(-) create mode 100644 adaptors/sessions/_examples/database/main.go create mode 100644 adaptors/sessions/_examples/securecookie/main.go rename adaptors/sessions/{_example => _examples/standalone}/main.go (86%) diff --git a/HISTORY.md b/HISTORY.md index 32fb6460..c5bdcf37 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,6 +8,10 @@ ## 6.1.4 -> 6.2.0 (√Νεxτ) +_Update: 18 March 2017_ + +- **Sessions**: Enchance the community feature request about custom encode and decode methods for the cookie value(sessionid) as requested [here](http://support.iris-go.com/d/29-mark-cookie-for-session-as-secure). + _Update: 12 March 2017_ - Enhance Custom http errors with gzip and static files handler, as requested/reported [here](http://support.iris-go.com/d/17-fallback-handler-for-non-matched-routes/9). @@ -934,7 +938,10 @@ to adapt a package as a session manager. So `iris.UseDatabase` has been removed > Don't worry about forgetting to adapt any feature that you use inside Iris, Iris will print you a how-to-fix message at iris.DevMode log level. -**[Example](https://github.com/kataras/iris/tree/v6/adaptors/sessions/_example) code:** + +**[Examples folder](https://github.com/kataras/iris/tree/v6/adaptors/sessions/_examples)** + + ```go package main diff --git a/_examples/examples/sessions/main.go b/_examples/examples/sessions/main.go index f0c470aa..cdf4c770 100644 --- a/_examples/examples/sessions/main.go +++ b/_examples/examples/sessions/main.go @@ -42,7 +42,8 @@ func logout(ctx *iris.Context) { func main() { app := iris.New() app.Adapt(httprouter.New()) - + // Look https://github.com/kataras/iris/tree/v6/adaptors/sessions/_examples for more features, + // i.e encode/decode and lifetime. sess := sessions.New(sessions.Config{Cookie: key}) app.Adapt(sess) diff --git a/adaptors/sessions/_examples/database/main.go b/adaptors/sessions/_examples/database/main.go new file mode 100644 index 00000000..70946ce9 --- /dev/null +++ b/adaptors/sessions/_examples/database/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "gopkg.in/kataras/iris.v6" + "gopkg.in/kataras/iris.v6/adaptors/httprouter" + "gopkg.in/kataras/iris.v6/adaptors/sessions" + "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis" + "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis/service" +) + +func main() { + // replace with your running redis' server settings: + db := redis.New(service.Config{Network: service.DefaultRedisNetwork, + Addr: service.DefaultRedisAddr, + Password: "", + Database: "", + MaxIdle: 0, + MaxActive: 0, + IdleTimeout: service.DefaultRedisIdleTimeout, + Prefix: "", + MaxAgeSeconds: service.DefaultRedisMaxAgeSeconds}) // optionally configure the bridge between your redis server + + mySessions := sessions.New(sessions.Config{Cookie: "mysessionid"}) + + // + // IMPORTANT: + // + mySessions.UseDatabase(db) + + // the rest of the code stays the same. + app := iris.New() + app.Adapt(iris.DevLogger()) // enable all (error) logs + app.Adapt(httprouter.New()) // select the httprouter as the servemux + + // Adapt the session manager we just created + app.Adapt(mySessions) + + app.Get("/", func(ctx *iris.Context) { + ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead") + }) + app.Get("/set", func(ctx *iris.Context) { + + //set session values + ctx.Session().Set("name", "iris") + + //test if setted here + ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name")) + }) + + app.Get("/get", func(ctx *iris.Context) { + // get a specific key, as string, if no found returns just an empty string + name := ctx.Session().GetString("name") + + ctx.Writef("The name on the /set was: %s", name) + }) + + app.Get("/delete", func(ctx *iris.Context) { + // delete a specific key + ctx.Session().Delete("name") + }) + + app.Get("/clear", func(ctx *iris.Context) { + // removes all entries + ctx.Session().Clear() + }) + + app.Get("/destroy", func(ctx *iris.Context) { + //destroy, removes the entire session data and cookie + ctx.SessionDestroy() + }) + + app.Listen(":8080") +} diff --git a/adaptors/sessions/_examples/securecookie/main.go b/adaptors/sessions/_examples/securecookie/main.go new file mode 100644 index 00000000..a678c5f6 --- /dev/null +++ b/adaptors/sessions/_examples/securecookie/main.go @@ -0,0 +1,79 @@ +package main + +import ( + // developers can use any library to add a custom cookie encoder/decoder. + // At this example we use the gorilla's securecookie library: + "github.com/gorilla/securecookie" + + "gopkg.in/kataras/iris.v6" + "gopkg.in/kataras/iris.v6/adaptors/httprouter" + "gopkg.in/kataras/iris.v6/adaptors/sessions" +) + +func main() { + app := iris.New() + app.Adapt(iris.DevLogger()) // enable all (error) logs + app.Adapt(httprouter.New()) // select the httprouter as the servemux + + cookieName := "mycustomsessionid" + // AES only supports key sizes of 16, 24 or 32 bytes. + // You either need to provide exactly that amount or you derive the key from what you type in. + hashKey := []byte("the-big-and-secret-fash-key-here") + blockKey := []byte("lot-secret-of-characters-big-too") + secureCookie := securecookie.New(hashKey, blockKey) + + mySessions := sessions.New(sessions.Config{ + Cookie: cookieName, + Encode: secureCookie.Encode, + Decode: secureCookie.Decode, + }) + + app.Adapt(mySessions) + + // OPTIONALLY: + // import "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis" + // or import "github.com/kataras/go-sessions/sessiondb/$any_available_community_database" + // mySessions.UseDatabase(redis.New(...)) + + app.Adapt(mySessions) // Adapt the session manager we just created. + + app.Get("/", func(ctx *iris.Context) { + ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead") + }) + app.Get("/set", func(ctx *iris.Context) { + + //set session values + ctx.Session().Set("name", "iris") + + //test if setted here + ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name")) + }) + + app.Get("/get", func(ctx *iris.Context) { + // get a specific key, as string, if no found returns just an empty string + name := ctx.Session().GetString("name") + + ctx.Writef("The name on the /set was: %s", name) + }) + + app.Get("/delete", func(ctx *iris.Context) { + // delete a specific key + ctx.Session().Delete("name") + }) + + app.Get("/clear", func(ctx *iris.Context) { + // removes all entries + ctx.Session().Clear() + }) + + app.Get("/destroy", func(ctx *iris.Context) { + //destroy, removes the entire session data and cookie + ctx.SessionDestroy() + }) // Note about destroy: + // + // You can destroy a session outside of a handler too, using the: + // mySessions.DestroyByID + // mySessions.DestroyAll + + app.Listen(":8080") +} diff --git a/adaptors/sessions/_example/main.go b/adaptors/sessions/_examples/standalone/main.go similarity index 86% rename from adaptors/sessions/_example/main.go rename to adaptors/sessions/_examples/standalone/main.go index 09cd67a0..87032940 100644 --- a/adaptors/sessions/_example/main.go +++ b/adaptors/sessions/_examples/standalone/main.go @@ -18,9 +18,6 @@ func main() { // // Defaults to "irissessionid" Cookie: "mysessionid", - // base64 urlencoding, - // if you have strange name cookie name enable this - DecodeCookie: false, // it's time.Duration, from the time cookie is created, how long it can be alive? // 0 means no expire. // -1 means expire when browser closes @@ -31,6 +28,7 @@ func main() { // if you want to invalid cookies on different subdomains // of the same host, then enable it DisableSubdomainPersistence: false, + // want to be crazy safe? Take a look at the "securecookie" example folder. }) // OPTIONALLY: @@ -71,13 +69,10 @@ func main() { app.Get("/destroy", func(ctx *iris.Context) { - //destroy, removes the entire session and cookie + //destroy, removes the entire session data and cookie ctx.SessionDestroy() - msg := "You have to refresh the page to completely remove the session (browsers works this way, it's not iris-specific.)" - - ctx.Writef(msg) - ctx.Log(iris.DevMode, msg) - }) // Note about destroy: + }) + // Note about Destroy: // // You can destroy a session outside of a handler too, using the: // mySessions.DestroyByID diff --git a/adaptors/sessions/config.go b/adaptors/sessions/config.go index b6911ee5..4cb35b78 100644 --- a/adaptors/sessions/config.go +++ b/adaptors/sessions/config.go @@ -1,7 +1,6 @@ package sessions import ( - "encoding/base64" "time" ) @@ -26,11 +25,6 @@ type ( // Defaults to "irissessionid" Cookie string - // DecodeCookie set it to true to decode the cookie key with base64 URLEncoding - // - // Defaults to false - DecodeCookie bool - // Encode the cookie value if not nil. // Should accept as first argument the cookie name (config.Name) // as second argument the server's generated session id. @@ -83,12 +77,6 @@ func (c Config) Validate() Config { c.Cookie = DefaultCookieName } - if c.DecodeCookie { - c.Cookie = base64.URLEncoding.EncodeToString([]byte(c.Cookie)) // change the cookie's name/key to a more safe(?) - // get the real value for your tests by: - //sessIdKey := url.QueryEscape(base64.URLEncoding.EncodeToString([]byte(Sessions.Cookie))) - } - if c.CookieLength <= 0 { c.CookieLength = DefaultCookieLength } diff --git a/adaptors/sessions/cookie.go b/adaptors/sessions/cookie.go index a6521e27..87181c62 100644 --- a/adaptors/sessions/cookie.go +++ b/adaptors/sessions/cookie.go @@ -81,18 +81,6 @@ func IsValidCookieDomain(domain string) bool { return true } -func encodeCookieValue(value string) string { - return base64.URLEncoding.EncodeToString([]byte(value)) -} - -func decodeCookieValue(value string) (string, error) { - v, err := base64.URLEncoding.DecodeString(value) - if err != nil { - return "", err - } - return string(v), nil -} - // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- // ----------------------------------Strings & Serialization---------------------------- diff --git a/adaptors/sessions/sessions.go b/adaptors/sessions/sessions.go index 1ab08623..9e48ce60 100644 --- a/adaptors/sessions/sessions.go +++ b/adaptors/sessions/sessions.go @@ -1,5 +1,5 @@ // Package sessions as originally written by me at https://github.com/kataras/go-sessions -// Based on kataras/go-sessions v1.0.0. +// Based on kataras/go-sessions v1.0.1. // // Edited for Iris v6 (or iris vNext) and removed all fasthttp things in order to reduce the // compiled and go getable size. The 'file' and 'leveldb' databases are missing @@ -28,13 +28,13 @@ type ( // UseDatabase ,optionally, adds a session database to the manager's provider, // a session db doesn't have write access - // see https://github.com/kataras/go-sessions/tree/master/sessiondb + // see https://github.com/kataras/go-sessions/tree/master/sessiondb for its usage. UseDatabase(Database) // Start starts the session for the particular net/http request Start(http.ResponseWriter, *http.Request) iris.Session - // Destroy kills the net/http session and remove the associated cookie + // Destroy deletes all session data and remove the associated cookie. Destroy(http.ResponseWriter, *http.Request) // DestroyByID removes the session entry @@ -42,6 +42,9 @@ type ( // Client's session cookie will still exist but it will be reseted on the next request. // // It's safe to use it even if you are not sure if a session with that id exists. + // + // Note: the sid should be the original one (i.e: fetched by a store ) + // it's not decoded. DestroyByID(string) // DestroyAll removes all sessions // from the server-side memory (and database if registered). @@ -146,62 +149,30 @@ func (s *sessions) Start(res http.ResponseWriter, req *http.Request) iris.Sessio cookie.MaxAge = int(cookie.Expires.Sub(time.Now()).Seconds()) } - { - // encode the session id cookie client value right before send it. - if encode := s.config.Encode; encode != nil { - newVal, err := encode(s.config.Cookie, cookie.Value) - if err == nil { - cookie.Value = newVal - } else { - cookie.Value = "" - } - } - } + // encode the session id cookie client value right before send it. + cookie.Value = s.encodeCookieValue(cookie.Value) AddCookie(cookie, res) } else { - { - // decode the cookie value from the client's cookie right before read the session data. - var cookieValueDecoded *string - if decode := s.config.Decode; decode != nil { - err := decode(s.config.Cookie, cookieValue, &cookieValueDecoded) - if err == nil { - cookieValue = *cookieValueDecoded - } else { - cookieValue = "" - } - } - } + cookieValue = s.decodeCookieValue(cookieValue) sess = s.provider.Read(cookieValue, s.config.Expires) } return sess } -// Destroy kills the net/http session and remove the associated cookie +// Destroy remove the session data and remove the associated cookie. func (s *sessions) Destroy(res http.ResponseWriter, req *http.Request) { cookieValue := GetCookie(s.config.Cookie, req) + // decode the client's cookie value in order to find the server's session id + // to destroy the session data. + cookieValue = s.decodeCookieValue(cookieValue) if cookieValue == "" { // nothing to destroy return } RemoveCookie(s.config.Cookie, res, req) - { - // decode the client's cookie value in order to find the server's session id - // to destroy the session data. - var cookieValueDecoded *string - if decode := s.config.Decode; decode != nil { - err := decode(s.config.Cookie, cookieValue, &cookieValueDecoded) - if err == nil { - cookieValue = *cookieValueDecoded - } else { - cookieValue = "" - } - } - - } - s.provider.Destroy(cookieValue) } @@ -210,7 +181,9 @@ func (s *sessions) Destroy(res http.ResponseWriter, req *http.Request) { // Client's session cookie will still exist but it will be reseted on the next request. // // It's safe to use it even if you are not sure if a session with that id exists. -// Works for both net/http +// +// Note: the sid should be the original one (i.e: fetched by a store ) +// it's not decoded. func (s *sessions) DestroyByID(sid string) { s.provider.Destroy(sid) } @@ -218,13 +191,39 @@ func (s *sessions) DestroyByID(sid string) { // DestroyAll removes all sessions // from the server-side memory (and database if registered). // Client's session cookie will still exist but it will be reseted on the next request. -// Works for both net/http func (s *sessions) DestroyAll() { s.provider.DestroyAll() } // SessionIDGenerator returns a random string, used to set the session id -// you are able to override this to use your own method for generate session ids +// you are able to override this to use your own method for generate session ids. var SessionIDGenerator = func(strLength int) string { return base64.URLEncoding.EncodeToString(random(strLength)) } + +// let's keep these funcs simple, we can do it with two lines but we may add more things in the future. +func (s *sessions) decodeCookieValue(cookieValue string) string { + var cookieValueDecoded *string + if decode := s.config.Decode; decode != nil { + err := decode(s.config.Cookie, cookieValue, &cookieValueDecoded) + if err == nil { + cookieValue = *cookieValueDecoded + } else { + cookieValue = "" + } + } + return cookieValue +} + +func (s *sessions) encodeCookieValue(cookieValue string) string { + if encode := s.config.Encode; encode != nil { + newVal, err := encode(s.config.Cookie, cookieValue) + if err == nil { + cookieValue = newVal + } else { + cookieValue = "" + } + } + + return cookieValue +} diff --git a/policy_sessions_test.go b/policy_sessions_test.go index 2916017b..31b8d363 100644 --- a/policy_sessions_test.go +++ b/policy_sessions_test.go @@ -2,8 +2,10 @@ package iris_test import ( "testing" + // developers can use any library to add a custom cookie encoder/decoder. + // At this test code we use the gorilla's securecookie library: + "github.com/gorilla/securecookie" - "github.com/gorilla/securecookie" // optional, to set sessions'' Encode and Decode "gopkg.in/kataras/iris.v6" "gopkg.in/kataras/iris.v6/adaptors/httprouter" "gopkg.in/kataras/iris.v6/adaptors/sessions"