From dbc12bc64e35294f6c98b92c474fe957f0ec004d Mon Sep 17 00:00:00 2001 From: Darko Luketic <2694548+dalu@users.noreply.github.com> Date: Fri, 27 Apr 2018 18:01:24 +0200 Subject: [PATCH] initial --- sscookie/LICENSE | 21 ++++ sscookie/cookie.go | 38 +++++++ sscookie/cookie_test.go | 30 ++++++ sscookie/sessions.go | 154 ++++++++++++++++++++++++++ sscookie/sessions_test.go | 220 ++++++++++++++++++++++++++++++++++++++ ssredis/LICENSE | 21 ++++ ssredis/redis.go | 69 ++++++++++++ ssredis/redis_test.go | 35 ++++++ ssredis/sessions.go | 154 ++++++++++++++++++++++++++ ssredis/sessions_test.go | 220 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 962 insertions(+) create mode 100644 sscookie/LICENSE create mode 100644 sscookie/cookie.go create mode 100644 sscookie/cookie_test.go create mode 100644 sscookie/sessions.go create mode 100644 sscookie/sessions_test.go create mode 100644 ssredis/LICENSE create mode 100644 ssredis/redis.go create mode 100644 ssredis/redis_test.go create mode 100644 ssredis/sessions.go create mode 100644 ssredis/sessions_test.go diff --git a/sscookie/LICENSE b/sscookie/LICENSE new file mode 100644 index 0000000..4e2cfb0 --- /dev/null +++ b/sscookie/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Gin-Gonic + +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/sscookie/cookie.go b/sscookie/cookie.go new file mode 100644 index 0000000..99415ef --- /dev/null +++ b/sscookie/cookie.go @@ -0,0 +1,38 @@ +package sessions + +import ( + "github.com/gorilla/sessions" +) + +// CookieStore interface +type CookieStore interface { + Store +} + +// NewCookieStore creates a new cookieStore. +// Keys are defined in pairs to allow key rotation, but the common case is to set a single +// authentication key and optionally an encryption key. +// +// The first key in a pair is used for authentication and the second for encryption. The +// encryption key can be set to nil or omitted in the last pair, but the authentication key +// is required in all pairs. +// +// It is recommended to use an authentication key with 32 or 64 bytes. The encryption key, +// if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes. +func NewCookieStore(keyPairs ...[]byte) CookieStore { + return &cookieStore{sessions.NewCookieStore(keyPairs...)} +} + +type cookieStore struct { + *sessions.CookieStore +} + +func (c *cookieStore) Options(options Options) { + c.CookieStore.Options = &sessions.Options{ + Path: options.Path, + Domain: options.Domain, + MaxAge: options.MaxAge, + Secure: options.Secure, + HttpOnly: options.HTTPOnly, + } +} diff --git a/sscookie/cookie_test.go b/sscookie/cookie_test.go new file mode 100644 index 0000000..9f79cc5 --- /dev/null +++ b/sscookie/cookie_test.go @@ -0,0 +1,30 @@ +package sessions + +import ( + "testing" +) + +var newCookieStore = func(_ *testing.T) Store { + store := NewCookieStore([]byte("secret")) + return store +} + +func TestCookie_SessionGetSet(t *testing.T) { + sessionGetSet(t, newCookieStore) +} + +func TestCookie_SessionDeleteKey(t *testing.T) { + sessionDeleteKey(t, newCookieStore) +} + +func TestCookie_SessionFlashes(t *testing.T) { + sessionFlashes(t, newCookieStore) +} + +func TestCookie_SessionClear(t *testing.T) { + sessionClear(t, newCookieStore) +} + +func TestCookie_SessionOptions(t *testing.T) { + sessionOptions(t, newCookieStore) +} diff --git a/sscookie/sessions.go b/sscookie/sessions.go new file mode 100644 index 0000000..617869a --- /dev/null +++ b/sscookie/sessions.go @@ -0,0 +1,154 @@ +package sessions + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/gorilla/context" + "github.com/gorilla/sessions" +) + +const ( + DefaultKey = "github.com/gin-contrib/sessions" + errorFormat = "[sessions] ERROR! %s\n" +) + +type Store interface { + sessions.Store + Options(Options) +} + +// Options stores configuration for a session or session store. +// Fields are a subset of http.Cookie fields. +type Options struct { + Path string + Domain string + // MaxAge=0 means no 'Max-Age' attribute specified. + // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'. + // MaxAge>0 means Max-Age attribute present and given in seconds. + MaxAge int + Secure bool + HTTPOnly bool +} + +// Session thinly Wraps gorilla-session methods and stores the values and optional configuration for a session. +type Session interface { + // Get returns the session value associated to the given key. + Get(key interface{}) interface{} + // Set sets the session value associated to the given key. + Set(key interface{}, val interface{}) + // Delete removes the session value associated to the given key. + Delete(key interface{}) + // Clear deletes all values in the session. + Clear() + // AddFlash adds a flash message to the session. + // A single variadic argument is accepted, and it is optional: it defines the flash key. + // If not defined "_flash" is used by default. + AddFlash(value interface{}, vars ...string) + // Flashes returns a slice of flash messages from the session. + // A single variadic argument is accepted, and it is optional: it defines the flash key. + // If not defined "_flash" is used by default. + Flashes(vars ...string) []interface{} + // Options sets confuguration for a session. + Options(Options) + // Save saves all sessions used during the current request. + Save() error +} + +// Sessions is used to create middleware +func Sessions(name string, store Store) gin.HandlerFunc { + return func(c *gin.Context) { + s := &session{ + name: name, + request: c.Request, + store: store, + session: nil, + written: false, + writer: c.Writer, + } + c.Set(DefaultKey, s) + defer context.Clear(c.Request) + c.Next() + } +} + +type session struct { + name string + request *http.Request + store Store + session *sessions.Session + written bool + writer http.ResponseWriter +} + +func (s *session) Get(key interface{}) interface{} { + return s.Session().Values[key] +} + +func (s *session) Set(key interface{}, val interface{}) { + s.Session().Values[key] = val + s.written = true +} + +func (s *session) Delete(key interface{}) { + delete(s.Session().Values, key) + s.written = true +} + +func (s *session) Clear() { + for key := range s.Session().Values { + s.Delete(key) + } +} + +func (s *session) AddFlash(value interface{}, vars ...string) { + s.Session().AddFlash(value, vars...) + s.written = true +} + +func (s *session) Flashes(vars ...string) []interface{} { + s.written = true + return s.Session().Flashes(vars...) +} + +func (s *session) Options(options Options) { + s.Session().Options = &sessions.Options{ + Path: options.Path, + Domain: options.Domain, + MaxAge: options.MaxAge, + Secure: options.Secure, + HttpOnly: options.HTTPOnly, + } +} + +func (s *session) Save() error { + if s.Written() { + e := s.Session().Save(s.request, s.writer) + if e == nil { + s.written = false + } + return e + } + return nil +} + +func (s *session) Session() *sessions.Session { + if s.session == nil { + var err error + s.session, err = s.store.Get(s.request, s.name) + if err != nil { + log.Printf(errorFormat, err) + } + } + return s.session +} + +func (s *session) Written() bool { + return s.written +} + +// Default is a shortcut to get session +func Default(c *gin.Context) Session { + return c.MustGet(DefaultKey).(Session) +} diff --git a/sscookie/sessions_test.go b/sscookie/sessions_test.go new file mode 100644 index 0000000..bb587b7 --- /dev/null +++ b/sscookie/sessions_test.go @@ -0,0 +1,220 @@ +package sessions + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +type storeFactory func(*testing.T) Store + +const sessionName = "mysession" + +const ok = "ok" + +func sessionGetSet(t *testing.T, newStore storeFactory) { + r := gin.Default() + r.Use(Sessions(sessionName, newStore(t))) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Save() + c.String(200, ok) + }) + + r.GET("/get", func(c *gin.Context) { + session := Default(c) + if session.Get("key") != ok { + t.Error("Session writing failed") + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/get", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) +} + +func sessionDeleteKey(t *testing.T, newStore storeFactory) { + r := gin.Default() + r.Use(Sessions(sessionName, newStore(t))) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Save() + c.String(200, ok) + }) + + r.GET("/delete", func(c *gin.Context) { + session := Default(c) + session.Delete("key") + session.Save() + c.String(200, ok) + }) + + r.GET("/get", func(c *gin.Context) { + session := Default(c) + if session.Get("key") != nil { + t.Error("Session deleting failed") + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/delete", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) + + res3 := httptest.NewRecorder() + req3, _ := http.NewRequest("GET", "/get", nil) + req3.Header.Set("Cookie", res2.Header().Get("Set-Cookie")) + r.ServeHTTP(res3, req3) +} + +func sessionFlashes(t *testing.T, newStore storeFactory) { + r := gin.Default() + store := newStore(t) + store.Options(Options{ + Domain: "localhost", + }) + r.Use(Sessions(sessionName, store)) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + session.AddFlash(ok) + session.Save() + c.String(200, ok) + }) + + r.GET("/flash", func(c *gin.Context) { + session := Default(c) + l := len(session.Flashes()) + if l != 1 { + t.Error("Flashes count does not equal 1. Equals ", l) + } + session.Save() + c.String(200, ok) + }) + + r.GET("/check", func(c *gin.Context) { + session := Default(c) + l := len(session.Flashes()) + if l != 0 { + t.Error("flashes count is not 0 after reading. Equals ", l) + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/flash", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) + + res3 := httptest.NewRecorder() + req3, _ := http.NewRequest("GET", "/check", nil) + req3.Header.Set("Cookie", res2.Header().Get("Set-Cookie")) + r.ServeHTTP(res3, req3) +} + +func sessionClear(t *testing.T, newStore storeFactory) { + data := map[string]string{ + "key": "val", + "foo": "bar", + } + r := gin.Default() + store := newStore(t) + r.Use(Sessions(sessionName, store)) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + for k, v := range data { + session.Set(k, v) + } + session.Clear() + session.Save() + c.String(200, ok) + }) + + r.GET("/check", func(c *gin.Context) { + session := Default(c) + for k, v := range data { + if session.Get(k) == v { + t.Fatal("Session clear failed") + } + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/check", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) +} + +func sessionOptions(t *testing.T, newStore storeFactory) { + r := gin.Default() + store := newStore(t) + store.Options(Options{ + Domain: "localhost", + }) + r.Use(Sessions(sessionName, store)) + + r.GET("/domain", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Options(Options{ + Path: "/foo/bar/bat", + }) + session.Save() + c.String(200, ok) + }) + r.GET("/path", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Save() + c.String(200, ok) + }) + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/domain", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/path", nil) + r.ServeHTTP(res2, req2) + + s := strings.Split(res1.Header().Get("Set-Cookie"), ";") + if s[1] != " Path=/foo/bar/bat" { + t.Error("Error writing path with options:", s[1]) + } + + s = strings.Split(res2.Header().Get("Set-Cookie"), ";") + if s[1] != " Domain=localhost" { + t.Error("Error writing domain with options:", s[1]) + } +} diff --git a/ssredis/LICENSE b/ssredis/LICENSE new file mode 100644 index 0000000..4e2cfb0 --- /dev/null +++ b/ssredis/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Gin-Gonic + +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/ssredis/redis.go b/ssredis/redis.go new file mode 100644 index 0000000..7afcb0a --- /dev/null +++ b/ssredis/redis.go @@ -0,0 +1,69 @@ +package sessions + +import ( + "github.com/boj/redistore" + "github.com/garyburd/redigo/redis" + "github.com/gorilla/sessions" +) + +type RedisStore interface { + Store +} + +// size: maximum number of idle connections. +// network: tcp or udp +// address: host:port +// password: redis-password +// Keys are defined in pairs to allow key rotation, but the common case is to set a single +// authentication key and optionally an encryption key. +// +// The first key in a pair is used for authentication and the second for encryption. The +// encryption key can be set to nil or omitted in the last pair, but the authentication key +// is required in all pairs. +// +// It is recommended to use an authentication key with 32 or 64 bytes. The encryption key, +// if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes. +func NewRedisStore(size int, network, address, password string, keyPairs ...[]byte) (RedisStore, error) { + store, err := redistore.NewRediStore(size, network, address, password, keyPairs...) + if err != nil { + return nil, err + } + return &redisStore{store}, nil +} + +// NewRedisStoreWithDB - like NewRedisStore but accepts `DB` parameter to select +// redis DB instead of using the default one ("0") +// +// Ref: https://godoc.org/github.com/boj/redistore#NewRediStoreWithDB +func NewRedisStoreWithDB(size int, network, address, password, DB string, keyPairs ...[]byte) (RedisStore, error) { + store, err := redistore.NewRediStoreWithDB(size, network, address, password, DB, keyPairs...) + if err != nil { + return nil, err + } + return &redisStore{store}, nil +} + +// NewRedisStoreWithPool instantiates a RediStore with a *redis.Pool passed in. +// +// Ref: https://godoc.org/github.com/boj/redistore#NewRediStoreWithPool +func NewRedisStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) (RedisStore, error) { + store, err := redistore.NewRediStoreWithPool(pool, keyPairs...) + if err != nil { + return nil, err + } + return &redisStore{store}, nil +} + +type redisStore struct { + *redistore.RediStore +} + +func (c *redisStore) Options(options Options) { + c.RediStore.Options = &sessions.Options{ + Path: options.Path, + Domain: options.Domain, + MaxAge: options.MaxAge, + Secure: options.Secure, + HttpOnly: options.HttpOnly, + } +} diff --git a/ssredis/redis_test.go b/ssredis/redis_test.go new file mode 100644 index 0000000..301e16c --- /dev/null +++ b/ssredis/redis_test.go @@ -0,0 +1,35 @@ +package sessions + +import ( + "testing" +) + +const redisTestServer = "localhost:6379" + +var newRedisStore = func(_ *testing.T) Store { + store, err := NewRedisStore(10, "tcp", redisTestServer, "", []byte("secret")) + if err != nil { + panic(err) + } + return store +} + +func TestRedis_SessionGetSet(t *testing.T) { + sessionGetSet(t, newRedisStore) +} + +func TestRedis_SessionDeleteKey(t *testing.T) { + sessionDeleteKey(t, newRedisStore) +} + +func TestRedis_SessionFlashes(t *testing.T) { + sessionFlashes(t, newRedisStore) +} + +func TestRedis_SessionClear(t *testing.T) { + sessionClear(t, newRedisStore) +} + +func TestRedis_SessionOptions(t *testing.T) { + sessionOptions(t, newRedisStore) +} diff --git a/ssredis/sessions.go b/ssredis/sessions.go new file mode 100644 index 0000000..361bb41 --- /dev/null +++ b/ssredis/sessions.go @@ -0,0 +1,154 @@ +package sessions + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/gorilla/context" + "github.com/gorilla/sessions" +) + +const ( + DefaultKey = "github.com/gin-contrib/sessions" + errorFormat = "[sessions] ERROR! %s\n" +) + +type Store interface { + sessions.Store + Options(Options) +} + +// Options stores configuration for a session or session store. +// Fields are a subset of http.Cookie fields. +type Options struct { + Path string + Domain string + // MaxAge=0 means no 'Max-Age' attribute specified. + // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'. + // MaxAge>0 means Max-Age attribute present and given in seconds. + MaxAge int + Secure bool + HttpOnly bool +} + +// Wraps thinly gorilla-session methods. +// Session stores the values and optional configuration for a session. +type Session interface { + // Get returns the session value associated to the given key. + Get(key interface{}) interface{} + // Set sets the session value associated to the given key. + Set(key interface{}, val interface{}) + // Delete removes the session value associated to the given key. + Delete(key interface{}) + // Clear deletes all values in the session. + Clear() + // AddFlash adds a flash message to the session. + // A single variadic argument is accepted, and it is optional: it defines the flash key. + // If not defined "_flash" is used by default. + AddFlash(value interface{}, vars ...string) + // Flashes returns a slice of flash messages from the session. + // A single variadic argument is accepted, and it is optional: it defines the flash key. + // If not defined "_flash" is used by default. + Flashes(vars ...string) []interface{} + // Options sets confuguration for a session. + Options(Options) + // Save saves all sessions used during the current request. + Save() error +} + +func Sessions(name string, store Store) gin.HandlerFunc { + return func(c *gin.Context) { + s := &session{ + name: name, + request: c.Request, + store: store, + session: nil, + written: false, + writer: c.Writer, + } + c.Set(DefaultKey, s) + defer context.Clear(c.Request) + c.Next() + } +} + +type session struct { + name string + request *http.Request + store Store + session *sessions.Session + written bool + writer http.ResponseWriter +} + +func (s *session) Get(key interface{}) interface{} { + return s.Session().Values[key] +} + +func (s *session) Set(key interface{}, val interface{}) { + s.Session().Values[key] = val + s.written = true +} + +func (s *session) Delete(key interface{}) { + delete(s.Session().Values, key) + s.written = true +} + +func (s *session) Clear() { + for key := range s.Session().Values { + s.Delete(key) + } +} + +func (s *session) AddFlash(value interface{}, vars ...string) { + s.Session().AddFlash(value, vars...) + s.written = true +} + +func (s *session) Flashes(vars ...string) []interface{} { + s.written = true + return s.Session().Flashes(vars...) +} + +func (s *session) Options(options Options) { + s.Session().Options = &sessions.Options{ + Path: options.Path, + Domain: options.Domain, + MaxAge: options.MaxAge, + Secure: options.Secure, + HttpOnly: options.HttpOnly, + } +} + +func (s *session) Save() error { + if s.Written() { + e := s.Session().Save(s.request, s.writer) + if e == nil { + s.written = false + } + return e + } + return nil +} + +func (s *session) Session() *sessions.Session { + if s.session == nil { + var err error + s.session, err = s.store.Get(s.request, s.name) + if err != nil { + log.Printf(errorFormat, err) + } + } + return s.session +} + +func (s *session) Written() bool { + return s.written +} + +// shortcut to get session +func Default(c *gin.Context) Session { + return c.MustGet(DefaultKey).(Session) +} diff --git a/ssredis/sessions_test.go b/ssredis/sessions_test.go new file mode 100644 index 0000000..bb587b7 --- /dev/null +++ b/ssredis/sessions_test.go @@ -0,0 +1,220 @@ +package sessions + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +type storeFactory func(*testing.T) Store + +const sessionName = "mysession" + +const ok = "ok" + +func sessionGetSet(t *testing.T, newStore storeFactory) { + r := gin.Default() + r.Use(Sessions(sessionName, newStore(t))) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Save() + c.String(200, ok) + }) + + r.GET("/get", func(c *gin.Context) { + session := Default(c) + if session.Get("key") != ok { + t.Error("Session writing failed") + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/get", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) +} + +func sessionDeleteKey(t *testing.T, newStore storeFactory) { + r := gin.Default() + r.Use(Sessions(sessionName, newStore(t))) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Save() + c.String(200, ok) + }) + + r.GET("/delete", func(c *gin.Context) { + session := Default(c) + session.Delete("key") + session.Save() + c.String(200, ok) + }) + + r.GET("/get", func(c *gin.Context) { + session := Default(c) + if session.Get("key") != nil { + t.Error("Session deleting failed") + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/delete", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) + + res3 := httptest.NewRecorder() + req3, _ := http.NewRequest("GET", "/get", nil) + req3.Header.Set("Cookie", res2.Header().Get("Set-Cookie")) + r.ServeHTTP(res3, req3) +} + +func sessionFlashes(t *testing.T, newStore storeFactory) { + r := gin.Default() + store := newStore(t) + store.Options(Options{ + Domain: "localhost", + }) + r.Use(Sessions(sessionName, store)) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + session.AddFlash(ok) + session.Save() + c.String(200, ok) + }) + + r.GET("/flash", func(c *gin.Context) { + session := Default(c) + l := len(session.Flashes()) + if l != 1 { + t.Error("Flashes count does not equal 1. Equals ", l) + } + session.Save() + c.String(200, ok) + }) + + r.GET("/check", func(c *gin.Context) { + session := Default(c) + l := len(session.Flashes()) + if l != 0 { + t.Error("flashes count is not 0 after reading. Equals ", l) + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/flash", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) + + res3 := httptest.NewRecorder() + req3, _ := http.NewRequest("GET", "/check", nil) + req3.Header.Set("Cookie", res2.Header().Get("Set-Cookie")) + r.ServeHTTP(res3, req3) +} + +func sessionClear(t *testing.T, newStore storeFactory) { + data := map[string]string{ + "key": "val", + "foo": "bar", + } + r := gin.Default() + store := newStore(t) + r.Use(Sessions(sessionName, store)) + + r.GET("/set", func(c *gin.Context) { + session := Default(c) + for k, v := range data { + session.Set(k, v) + } + session.Clear() + session.Save() + c.String(200, ok) + }) + + r.GET("/check", func(c *gin.Context) { + session := Default(c) + for k, v := range data { + if session.Get(k) == v { + t.Fatal("Session clear failed") + } + } + session.Save() + c.String(200, ok) + }) + + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/set", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/check", nil) + req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie")) + r.ServeHTTP(res2, req2) +} + +func sessionOptions(t *testing.T, newStore storeFactory) { + r := gin.Default() + store := newStore(t) + store.Options(Options{ + Domain: "localhost", + }) + r.Use(Sessions(sessionName, store)) + + r.GET("/domain", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Options(Options{ + Path: "/foo/bar/bat", + }) + session.Save() + c.String(200, ok) + }) + r.GET("/path", func(c *gin.Context) { + session := Default(c) + session.Set("key", ok) + session.Save() + c.String(200, ok) + }) + res1 := httptest.NewRecorder() + req1, _ := http.NewRequest("GET", "/domain", nil) + r.ServeHTTP(res1, req1) + + res2 := httptest.NewRecorder() + req2, _ := http.NewRequest("GET", "/path", nil) + r.ServeHTTP(res2, req2) + + s := strings.Split(res1.Header().Get("Set-Cookie"), ";") + if s[1] != " Path=/foo/bar/bat" { + t.Error("Error writing path with options:", s[1]) + } + + s = strings.Split(res2.Header().Get("Set-Cookie"), ";") + if s[1] != " Domain=localhost" { + t.Error("Error writing domain with options:", s[1]) + } +}