initial
This commit is contained in:
		
							
								
								
									
										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]) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Darko Luketic
					Darko Luketic