initial
This commit is contained in:
commit
dbc12bc64e
21
sscookie/LICENSE
Normal file
21
sscookie/LICENSE
Normal file
@ -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.
|
38
sscookie/cookie.go
Normal file
38
sscookie/cookie.go
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
30
sscookie/cookie_test.go
Normal file
30
sscookie/cookie_test.go
Normal file
@ -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)
|
||||||
|
}
|
154
sscookie/sessions.go
Normal file
154
sscookie/sessions.go
Normal file
@ -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)
|
||||||
|
}
|
220
sscookie/sessions_test.go
Normal file
220
sscookie/sessions_test.go
Normal file
@ -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])
|
||||||
|
}
|
||||||
|
}
|
21
ssredis/LICENSE
Normal file
21
ssredis/LICENSE
Normal file
@ -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.
|
69
ssredis/redis.go
Normal file
69
ssredis/redis.go
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
35
ssredis/redis_test.go
Normal file
35
ssredis/redis_test.go
Normal file
@ -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)
|
||||||
|
}
|
154
ssredis/sessions.go
Normal file
154
ssredis/sessions.go
Normal file
@ -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)
|
||||||
|
}
|
220
ssredis/sessions_test.go
Normal file
220
ssredis/sessions_test.go
Normal file
@ -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])
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user