Add support for a redis session store
This commit is contained in:
		
							parent
							
								
									e881612ea6
								
							
						
					
					
						commit
						b1bd3280db
					
				|  | @ -17,6 +17,25 @@ | |||
|   revision = "b26d9c308763d68093482582cea63d69be07a0f0" | ||||
|   version = "v0.3.0" | ||||
| 
 | ||||
| [[projects]] | ||||
|   branch = "master" | ||||
|   digest = "1:3cce78d5d0090e3f1162945fba60ba74e72e8422e8e41bb9c701afb67237bb65" | ||||
|   name = "github.com/alicebob/gopher-json" | ||||
|   packages = ["."] | ||||
|   pruneopts = "" | ||||
|   revision = "5a6b3ba71ee69b77cf64febf8b5a7526ca5eaef0" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:18a07506ddaa87b1612bfd69eef03f510faf122398df3da774d46dcfe751a060" | ||||
|   name = "github.com/alicebob/miniredis" | ||||
|   packages = [ | ||||
|     ".", | ||||
|     "server", | ||||
|   ] | ||||
|   pruneopts = "" | ||||
|   revision = "3d7aa1333af56ab862d446678d93aaa6803e0938" | ||||
|   version = "v2.7.0" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:512883404c2a99156e410e9880e3bb35ecccc0c07c1159eb204b5f3ef3c431b3" | ||||
|   name = "github.com/bitly/go-simplejson" | ||||
|  | @ -49,6 +68,22 @@ | |||
|   revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" | ||||
|   version = "v3.2.0" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:8c7410dae63c74bd92db09bf33af7e0698b635ab6a397fd8e9e10dfcce3138ac" | ||||
|   name = "github.com/go-redis/redis" | ||||
|   packages = [ | ||||
|     ".", | ||||
|     "internal", | ||||
|     "internal/consistenthash", | ||||
|     "internal/hashtag", | ||||
|     "internal/pool", | ||||
|     "internal/proto", | ||||
|     "internal/util", | ||||
|   ] | ||||
|   pruneopts = "" | ||||
|   revision = "d22fde8721cc915a55aeb6b00944a76a92bfeb6e" | ||||
|   version = "v6.15.2" | ||||
| 
 | ||||
| [[projects]] | ||||
|   branch = "master" | ||||
|   digest = "1:3b760d3b93f994df8eb1d9ebfad17d3e9e37edcb7f7efaa15b427c0d7a64f4e4" | ||||
|  | @ -57,6 +92,17 @@ | |||
|   pruneopts = "" | ||||
|   revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:dcf8316121302735c0ac84e05f4686e3b34e284444435e9a206da48d8be18cb1" | ||||
|   name = "github.com/gomodule/redigo" | ||||
|   packages = [ | ||||
|     "internal", | ||||
|     "redis", | ||||
|   ] | ||||
|   pruneopts = "" | ||||
|   revision = "9c11da706d9b7902c6da69c592f75637793fe121" | ||||
|   version = "v2.0.0" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:b3c5b95e56c06f5aa72cb2500e6ee5f44fcd122872d4fec2023a488e561218bc" | ||||
|   name = "github.com/hpcloud/tail" | ||||
|  | @ -173,6 +219,19 @@ | |||
|   pruneopts = "" | ||||
|   revision = "1d66fa95c997864ba4d8479f56609620fe542928" | ||||
| 
 | ||||
| [[projects]] | ||||
|   branch = "master" | ||||
|   digest = "1:378d29a839ff770e9d9150580b4c01ff0a513a296b0487558a7af7c18adab98e" | ||||
|   name = "github.com/yuin/gopher-lua" | ||||
|   packages = [ | ||||
|     ".", | ||||
|     "ast", | ||||
|     "parse", | ||||
|     "pm", | ||||
|   ] | ||||
|   pruneopts = "" | ||||
|   revision = "8bfc7677f583b35a5663a9dd934c08f3b5774bbb" | ||||
| 
 | ||||
| [[projects]] | ||||
|   branch = "master" | ||||
|   digest = "1:f6a006d27619a4d93bf9b66fe1999b8c8d1fa62bdc63af14f10fbe6fcaa2aa1a" | ||||
|  | @ -341,9 +400,11 @@ | |||
|   analyzer-version = 1 | ||||
|   input-imports = [ | ||||
|     "github.com/BurntSushi/toml", | ||||
|     "github.com/alicebob/miniredis", | ||||
|     "github.com/bitly/go-simplejson", | ||||
|     "github.com/coreos/go-oidc", | ||||
|     "github.com/dgrijalva/jwt-go", | ||||
|     "github.com/go-redis/redis", | ||||
|     "github.com/mbland/hmacauth", | ||||
|     "github.com/mreiferson/go-options", | ||||
|     "github.com/onsi/ginkgo", | ||||
|  |  | |||
|  | @ -46,3 +46,11 @@ | |||
| [[constraint]] | ||||
|   name = "gopkg.in/natefinch/lumberjack.v2" | ||||
|   version = "2.1.0" | ||||
| 
 | ||||
| [[constraint]] | ||||
|   branch = "master" | ||||
|   name = "github.com/go-redis/redis" | ||||
| 
 | ||||
| [[constraint]] | ||||
|   name = "github.com/alicebob/miniredis" | ||||
|   version = "2.7.0" | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ type SessionOptions struct { | |||
| 	Type   string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"` | ||||
| 	Cipher *cookie.Cipher | ||||
| 	CookieStoreOptions | ||||
| 	RedisStoreOptions | ||||
| } | ||||
| 
 | ||||
| // CookieSessionStoreType is used to indicate the CookieSessionStore should be
 | ||||
|  | @ -17,3 +18,12 @@ var CookieSessionStoreType = "cookie" | |||
| 
 | ||||
| // CookieStoreOptions contains configuration options for the CookieSessionStore.
 | ||||
| type CookieStoreOptions struct{} | ||||
| 
 | ||||
| // RedisSessionStoreType is used to indicate the CookieSessionStore should be
 | ||||
| // used for storing sessions.
 | ||||
| var RedisSessionStoreType = "redis" | ||||
| 
 | ||||
| // RedisStoreOptions contains configuration options for the CookieSessionStore.
 | ||||
| type RedisStoreOptions struct { | ||||
| 	RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"` | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,271 @@ | |||
| package redis | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/aes" | ||||
| 	"crypto/cipher" | ||||
| 	"crypto/rand" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/go-redis/redis" | ||||
| 	"github.com/pusher/oauth2_proxy/cookie" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/apis/options" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/apis/sessions" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/cookies" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/sessions/utils" | ||||
| ) | ||||
| 
 | ||||
| // TicketData is a structure representing a used in server session storage
 | ||||
| type TicketData struct { | ||||
| 	TicketID string | ||||
| 	Secret   []byte | ||||
| } | ||||
| 
 | ||||
| // SessionStore is an implementation of the sessions.SessionStore
 | ||||
| // interface that stores sessions in redis
 | ||||
| type SessionStore struct { | ||||
| 	CookieCipher   *cookie.Cipher | ||||
| 	CookieDomain   string | ||||
| 	CookieExpire   time.Duration | ||||
| 	CookieHTTPOnly bool | ||||
| 	CookieName     string | ||||
| 	CookiePath     string | ||||
| 	CookieSecret   string | ||||
| 	CookieSecure   bool | ||||
| 	Client         *redis.Client | ||||
| } | ||||
| 
 | ||||
| // NewRedisSessionStore initialises a new instance of the SessionStore from
 | ||||
| // the configuration given
 | ||||
| func NewRedisSessionStore(opts options.RedisStoreOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) { | ||||
| 	opt, err := redis.ParseURL(opts.RedisConnectionURL) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("unable to parse redis url: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var cookieCipher *cookie.Cipher | ||||
| 	if len(cookieOpts.CookieSecret) > 0 { | ||||
| 		var err error | ||||
| 		cookieCipher, err = cookie.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret)) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("unable to create cookieCipher: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	client := redis.NewClient(opt) | ||||
| 
 | ||||
| 	rs := &SessionStore{ | ||||
| 		Client:         client, | ||||
| 		CookieCipher:   cookieCipher, | ||||
| 		CookieDomain:   cookieOpts.CookieDomain, | ||||
| 		CookieExpire:   cookieOpts.CookieExpire, | ||||
| 		CookieHTTPOnly: cookieOpts.CookieHTTPOnly, | ||||
| 		CookieName:     cookieOpts.CookieName, | ||||
| 		CookiePath:     cookieOpts.CookiePath, | ||||
| 		CookieSecret:   cookieOpts.CookieSecret, | ||||
| 		CookieSecure:   cookieOpts.CookieSecure, | ||||
| 	} | ||||
| 	return rs, nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Save takes a sessions.SessionState and stores the information from it
 | ||||
| // to redies, and adds a new ticket cookie on the HTTP response writer
 | ||||
| func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error { | ||||
| 	requestCookie, _ := req.Cookie(store.CookieName) | ||||
| 	value, err := s.EncodeSessionState(store.CookieCipher) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	ticketString, err := store.storeValue(value, s.ExpiresOn, requestCookie) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	ticketCookie := cookies.MakeCookie( | ||||
| 		req, | ||||
| 		store.CookieName, | ||||
| 		ticketString, | ||||
| 		store.CookiePath, | ||||
| 		store.CookieDomain, | ||||
| 		store.CookieHTTPOnly, | ||||
| 		store.CookieSecure, | ||||
| 		store.CookieExpire, | ||||
| 		time.Now(), | ||||
| 	) | ||||
| 
 | ||||
| 	http.SetCookie(rw, ticketCookie) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Load reads sessions.SessionState information from a ticket
 | ||||
| // cookie within the HTTP request object
 | ||||
| func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) { | ||||
| 	requestCookie, _ := req.Cookie(store.CookieName) | ||||
| 	// No cookie validation necessary
 | ||||
| 	session, err := store.LoadSessionFromString(requestCookie.Value) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error loading session: %s", err) | ||||
| 	} | ||||
| 	return session, nil | ||||
| } | ||||
| 
 | ||||
| // LoadSessionFromString loads the session based on the ticket value
 | ||||
| func (store *SessionStore) LoadSessionFromString(value string) (*sessions.SessionState, error) { | ||||
| 	ticket, err := decodeTicket(store.CookieName, value) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	result, err := store.Client.Get(ticket.asHandle(store.CookieName)).Result() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	resultBytes := []byte(result) | ||||
| 	block, err := aes.NewCipher(ticket.Secret) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	// Use secret as the IV too, because each entry has it's own key
 | ||||
| 	stream := cipher.NewCFBDecrypter(block, ticket.Secret) | ||||
| 	stream.XORKeyStream(resultBytes, resultBytes) | ||||
| 
 | ||||
| 	session, err := sessions.DecodeSessionState(string(resultBytes), store.CookieCipher) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return session, nil | ||||
| } | ||||
| 
 | ||||
| // Clear clears any saved session information for a given ticket cookie
 | ||||
| // from redis, and then clears the session
 | ||||
| func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error { | ||||
| 	requestCookie, _ := req.Cookie(store.CookieName) | ||||
| 
 | ||||
| 	// We go ahead and clear the cookie first, always.
 | ||||
| 	clearCookie := cookies.MakeCookie( | ||||
| 		req, | ||||
| 		store.CookieName, | ||||
| 		"", | ||||
| 		store.CookiePath, | ||||
| 		store.CookieDomain, | ||||
| 		store.CookieHTTPOnly, | ||||
| 		store.CookieSecure, | ||||
| 		time.Hour*-1, | ||||
| 		time.Now(), | ||||
| 	) | ||||
| 	http.SetCookie(rw, clearCookie) | ||||
| 
 | ||||
| 	// We only return an error if we had an issue with redis
 | ||||
| 	// If there's an issue decoding the ticket, ignore it
 | ||||
| 	ticket, _ := decodeTicket(store.CookieName, requestCookie.Value) | ||||
| 	if ticket != nil { | ||||
| 		deleted, err := store.Client.Del(ticket.asHandle(store.CookieName)).Result() | ||||
| 		fmt.Println("delted %n", deleted) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("error clearing cookie from redis: %s", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (store *SessionStore) storeValue(value string, expiresOn time.Time, requestCookie *http.Cookie) (string, error) { | ||||
| 	var ticket *TicketData | ||||
| 	if requestCookie != nil { | ||||
| 		var err error | ||||
| 		ticket, err = decodeTicket(store.CookieName, requestCookie.Value) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} else { | ||||
| 		var err error | ||||
| 		ticket, err = newTicket() | ||||
| 		if err != nil { | ||||
| 			return "", fmt.Errorf("error creating new ticket: %s", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ciphertext := make([]byte, len(value)) | ||||
| 	block, err := aes.NewCipher(ticket.Secret) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("error initiating cipher block %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Use secret as the IV too, because each entry has it's own key
 | ||||
| 	stream := cipher.NewCFBEncrypter(block, ticket.Secret) | ||||
| 	stream.XORKeyStream(ciphertext, []byte(value)) | ||||
| 
 | ||||
| 	handle := ticket.asHandle(store.CookieName) | ||||
| 	expires := expiresOn.Sub(time.Now()) | ||||
| 	err = store.Client.Set(handle, ciphertext, expires).Err() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return ticket.encodeTicket(store.CookieName), nil | ||||
| } | ||||
| 
 | ||||
| func newTicket() (*TicketData, error) { | ||||
| 	rawID := make([]byte, 16) | ||||
| 	if _, err := io.ReadFull(rand.Reader, rawID); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create new ticket ID %s", err) | ||||
| 	} | ||||
| 	// ticketID is hex encoded
 | ||||
| 	ticketID := fmt.Sprintf("%x", rawID) | ||||
| 
 | ||||
| 	secret := make([]byte, aes.BlockSize) | ||||
| 	if _, err := io.ReadFull(rand.Reader, secret); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create initialization vector %s", err) | ||||
| 	} | ||||
| 	ticket := &TicketData{ | ||||
| 		TicketID: ticketID, | ||||
| 		Secret:   secret, | ||||
| 	} | ||||
| 	return ticket, nil | ||||
| } | ||||
| 
 | ||||
| func (ticket *TicketData) asHandle(prefix string) string { | ||||
| 	return fmt.Sprintf("%s-%s", prefix, ticket.TicketID) | ||||
| } | ||||
| 
 | ||||
| func decodeTicket(cookieName string, ticketString string) (*TicketData, error) { | ||||
| 	prefix := cookieName + "-" | ||||
| 	if !strings.HasPrefix(ticketString, prefix) { | ||||
| 		return nil, fmt.Errorf("failed to decode ticket handle") | ||||
| 	} | ||||
| 	trimmedTicket := strings.TrimPrefix(ticketString, prefix) | ||||
| 
 | ||||
| 	ticketParts := strings.Split(trimmedTicket, ".") | ||||
| 	if len(ticketParts) != 2 { | ||||
| 		return nil, fmt.Errorf("failed to decode ticket") | ||||
| 	} | ||||
| 	ticketID, secretBase64 := ticketParts[0], ticketParts[1] | ||||
| 
 | ||||
| 	// ticketID must be a hexadecimal string
 | ||||
| 	_, err := hex.DecodeString(ticketID) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("server ticket failed sanity checks") | ||||
| 		// s is not a valid
 | ||||
| 	} | ||||
| 
 | ||||
| 	secret, err := base64.RawURLEncoding.DecodeString(secretBase64) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to decode initialization vector %s", err) | ||||
| 	} | ||||
| 	ticketData := &TicketData{ | ||||
| 		TicketID: ticketID, | ||||
| 		Secret:   secret, | ||||
| 	} | ||||
| 	return ticketData, nil | ||||
| } | ||||
| 
 | ||||
| func (ticket *TicketData) encodeTicket(prefix string) string { | ||||
| 	handle := ticket.asHandle(prefix) | ||||
| 	ticketString := handle + "." + base64.RawURLEncoding.EncodeToString(ticket.Secret) | ||||
| 	return ticketString | ||||
| } | ||||
|  | @ -6,6 +6,7 @@ import ( | |||
| 	"github.com/pusher/oauth2_proxy/pkg/apis/options" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/apis/sessions" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/sessions/cookie" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/sessions/redis" | ||||
| ) | ||||
| 
 | ||||
| // NewSessionStore creates a SessionStore from the provided configuration
 | ||||
|  | @ -13,6 +14,8 @@ func NewSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOpt | |||
| 	switch opts.Type { | ||||
| 	case options.CookieSessionStoreType: | ||||
| 		return cookie.NewCookieSessionStore(opts, cookieOpts) | ||||
| 	case options.RedisSessionStoreType: | ||||
| 		return redis.NewRedisSessionStore(opts.RedisStoreOptions, cookieOpts) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unknown session store type '%s'", opts.Type) | ||||
| 	} | ||||
|  |  | |||
|  | @ -10,14 +10,15 @@ import ( | |||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/alicebob/miniredis" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/gomega" | ||||
| 	"github.com/pusher/oauth2_proxy/cookie" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/apis/options" | ||||
| 	sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/cookies" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/sessions" | ||||
| 	sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/sessions/redis" | ||||
| 	"github.com/pusher/oauth2_proxy/pkg/sessions/utils" | ||||
| ) | ||||
| 
 | ||||
|  | @ -109,22 +110,18 @@ var _ = Describe("NewSessionStore", func() { | |||
| 
 | ||||
| 		Context("when Clear is called", func() { | ||||
| 			BeforeEach(func() { | ||||
| 				cookie := cookies.MakeCookie(request, | ||||
| 					cookieOpts.CookieName, | ||||
| 					"foo", | ||||
| 					cookieOpts.CookiePath, | ||||
| 					cookieOpts.CookieDomain, | ||||
| 					cookieOpts.CookieHTTPOnly, | ||||
| 					cookieOpts.CookieSecure, | ||||
| 					cookieOpts.CookieExpire, | ||||
| 					time.Now(), | ||||
| 				) | ||||
| 				request.AddCookie(cookie) | ||||
| 				err := ss.Clear(response, request) | ||||
| 				req := httptest.NewRequest("GET", "http://example.com/", nil) | ||||
| 				saveResp := httptest.NewRecorder() | ||||
| 				err := ss.Save(saveResp, req, session) | ||||
| 				Expect(err).ToNot(HaveOccurred()) | ||||
| 
 | ||||
| 				resultCookie := saveResp.Result().Cookies()[0] | ||||
| 				request.AddCookie(resultCookie) | ||||
| 				err = ss.Clear(response, request) | ||||
| 				Expect(err).ToNot(HaveOccurred()) | ||||
| 			}) | ||||
| 
 | ||||
| 			It("sets a `set-cookie` header in the response", func() { | ||||
| 			It("sets a `set-cookie` header in the response and session to not be loadable after clear", func() { | ||||
| 				Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) | ||||
| 			}) | ||||
| 
 | ||||
|  | @ -171,6 +168,35 @@ var _ = Describe("NewSessionStore", func() { | |||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	PersistentSessionStoreTests := func() { | ||||
| 		Context("when Clear is called the session should not be recoverable", func() { | ||||
| 			var loadedAfterClear *sessionsapi.SessionState | ||||
| 			BeforeEach(func() { | ||||
| 				req := httptest.NewRequest("GET", "http://example.com/", nil) | ||||
| 				saveResp := httptest.NewRecorder() | ||||
| 				err := ss.Save(saveResp, req, session) | ||||
| 				Expect(err).ToNot(HaveOccurred()) | ||||
| 
 | ||||
| 				resultCookie := saveResp.Result().Cookies()[0] | ||||
| 				request.AddCookie(resultCookie) | ||||
| 				err = ss.Clear(response, request) | ||||
| 				Expect(err).ToNot(HaveOccurred()) | ||||
| 
 | ||||
| 				// The following should only be for server stores
 | ||||
| 				loadReq := httptest.NewRequest("GET", "http://example.com/", nil) | ||||
| 				loadReq.AddCookie(resultCookie) | ||||
| 				loadedAfterClear, err = ss.Load(loadReq) | ||||
| 			}) | ||||
| 
 | ||||
| 			It("sets a `set-cookie` header in the response and session to not be loadable after clear", func() { | ||||
| 				Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) | ||||
| 				Expect(loadedAfterClear).To(BeNil()) | ||||
| 			}) | ||||
| 
 | ||||
| 			CheckCookieOptions() | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	RunSessionTests := func() { | ||||
| 		Context("with default options", func() { | ||||
| 			BeforeEach(func() { | ||||
|  | @ -221,6 +247,18 @@ var _ = Describe("NewSessionStore", func() { | |||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	RunPersistentSessionStoreTests := func() { | ||||
| 		Context("with default options", func() { | ||||
| 			BeforeEach(func() { | ||||
| 				var err error | ||||
| 				ss, err = sessions.NewSessionStore(opts, cookieOpts) | ||||
| 				Expect(err).ToNot(HaveOccurred()) | ||||
| 			}) | ||||
| 
 | ||||
| 			PersistentSessionStoreTests() | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	BeforeEach(func() { | ||||
| 		ss = nil | ||||
| 		opts = &options.SessionOptions{} | ||||
|  | @ -264,6 +302,28 @@ var _ = Describe("NewSessionStore", func() { | |||
| 		}) | ||||
| 	}) | ||||
| 
 | ||||
| 	Context("with type 'redis'", func() { | ||||
| 		BeforeEach(func() { | ||||
| 			mr, err := miniredis.Run() | ||||
| 			if err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 			opts.Type = options.RedisSessionStoreType | ||||
| 			opts.RedisConnectionURL = "redis://" + mr.Addr() | ||||
| 		}) | ||||
| 
 | ||||
| 		It("creates a redis.SessionStore", func() { | ||||
| 			ss, err := sessions.NewSessionStore(opts, cookieOpts) | ||||
| 			Expect(err).NotTo(HaveOccurred()) | ||||
| 			Expect(ss).To(BeAssignableToTypeOf(&redis.SessionStore{})) | ||||
| 		}) | ||||
| 
 | ||||
| 		Context("the redis.SessionStore", func() { | ||||
| 			RunSessionTests() | ||||
| 			RunPersistentSessionStoreTests() | ||||
| 		}) | ||||
| 	}) | ||||
| 
 | ||||
| 	Context("with an invalid type", func() { | ||||
| 		BeforeEach(func() { | ||||
| 			opts.Type = "invalid-type" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue