293 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| // Copyright 2012 The Gorilla Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package sessions
 | |
| 
 | |
| import (
 | |
| 	"encoding/base32"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/gorilla/securecookie"
 | |
| )
 | |
| 
 | |
| // Store is an interface for custom session stores.
 | |
| //
 | |
| // See CookieStore and FilesystemStore for examples.
 | |
| type Store interface {
 | |
| 	// Get should return a cached session.
 | |
| 	Get(r *http.Request, name string) (*Session, error)
 | |
| 
 | |
| 	// New should create and return a new session.
 | |
| 	//
 | |
| 	// Note that New should never return a nil session, even in the case of
 | |
| 	// an error if using the Registry infrastructure to cache the session.
 | |
| 	New(r *http.Request, name string) (*Session, error)
 | |
| 
 | |
| 	// Save should persist session to the underlying store implementation.
 | |
| 	Save(r *http.Request, w http.ResponseWriter, s *Session) error
 | |
| }
 | |
| 
 | |
| // CookieStore ----------------------------------------------------------------
 | |
| 
 | |
| // NewCookieStore returns 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 {
 | |
| 	cs := &CookieStore{
 | |
| 		Codecs: securecookie.CodecsFromPairs(keyPairs...),
 | |
| 		Options: &Options{
 | |
| 			Path:   "/",
 | |
| 			MaxAge: 86400 * 30,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	cs.MaxAge(cs.Options.MaxAge)
 | |
| 	return cs
 | |
| }
 | |
| 
 | |
| // CookieStore stores sessions using secure cookies.
 | |
| type CookieStore struct {
 | |
| 	Codecs  []securecookie.Codec
 | |
| 	Options *Options // default configuration
 | |
| }
 | |
| 
 | |
| // Get returns a session for the given name after adding it to the registry.
 | |
| //
 | |
| // It returns a new session if the sessions doesn't exist. Access IsNew on
 | |
| // the session to check if it is an existing session or a new one.
 | |
| //
 | |
| // It returns a new session and an error if the session exists but could
 | |
| // not be decoded.
 | |
| func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
 | |
| 	return GetRegistry(r).Get(s, name)
 | |
| }
 | |
| 
 | |
| // New returns a session for the given name without adding it to the registry.
 | |
| //
 | |
| // The difference between New() and Get() is that calling New() twice will
 | |
| // decode the session data twice, while Get() registers and reuses the same
 | |
| // decoded session after the first call.
 | |
| func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
 | |
| 	session := NewSession(s, name)
 | |
| 	opts := *s.Options
 | |
| 	session.Options = &opts
 | |
| 	session.IsNew = true
 | |
| 	var err error
 | |
| 	if c, errCookie := r.Cookie(name); errCookie == nil {
 | |
| 		err = securecookie.DecodeMulti(name, c.Value, &session.Values,
 | |
| 			s.Codecs...)
 | |
| 		if err == nil {
 | |
| 			session.IsNew = false
 | |
| 		}
 | |
| 	}
 | |
| 	return session, err
 | |
| }
 | |
| 
 | |
| // Save adds a single session to the response.
 | |
| func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
 | |
| 	session *Session) error {
 | |
| 	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
 | |
| 		s.Codecs...)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // MaxAge sets the maximum age for the store and the underlying cookie
 | |
| // implementation. Individual sessions can be deleted by setting Options.MaxAge
 | |
| // = -1 for that session.
 | |
| func (s *CookieStore) MaxAge(age int) {
 | |
| 	s.Options.MaxAge = age
 | |
| 
 | |
| 	// Set the maxAge for each securecookie instance.
 | |
| 	for _, codec := range s.Codecs {
 | |
| 		if sc, ok := codec.(*securecookie.SecureCookie); ok {
 | |
| 			sc.MaxAge(age)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // FilesystemStore ------------------------------------------------------------
 | |
| 
 | |
| var fileMutex sync.RWMutex
 | |
| 
 | |
| // NewFilesystemStore returns a new FilesystemStore.
 | |
| //
 | |
| // The path argument is the directory where sessions will be saved. If empty
 | |
| // it will use os.TempDir().
 | |
| //
 | |
| // See NewCookieStore() for a description of the other parameters.
 | |
| func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
 | |
| 	if path == "" {
 | |
| 		path = os.TempDir()
 | |
| 	}
 | |
| 	fs := &FilesystemStore{
 | |
| 		Codecs: securecookie.CodecsFromPairs(keyPairs...),
 | |
| 		Options: &Options{
 | |
| 			Path:   "/",
 | |
| 			MaxAge: 86400 * 30,
 | |
| 		},
 | |
| 		path: path,
 | |
| 	}
 | |
| 
 | |
| 	fs.MaxAge(fs.Options.MaxAge)
 | |
| 	return fs
 | |
| }
 | |
| 
 | |
| // FilesystemStore stores sessions in the filesystem.
 | |
| //
 | |
| // It also serves as a reference for custom stores.
 | |
| //
 | |
| // This store is still experimental and not well tested. Feedback is welcome.
 | |
| type FilesystemStore struct {
 | |
| 	Codecs  []securecookie.Codec
 | |
| 	Options *Options // default configuration
 | |
| 	path    string
 | |
| }
 | |
| 
 | |
| // MaxLength restricts the maximum length of new sessions to l.
 | |
| // If l is 0 there is no limit to the size of a session, use with caution.
 | |
| // The default for a new FilesystemStore is 4096.
 | |
| func (s *FilesystemStore) MaxLength(l int) {
 | |
| 	for _, c := range s.Codecs {
 | |
| 		if codec, ok := c.(*securecookie.SecureCookie); ok {
 | |
| 			codec.MaxLength(l)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Get returns a session for the given name after adding it to the registry.
 | |
| //
 | |
| // See CookieStore.Get().
 | |
| func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
 | |
| 	return GetRegistry(r).Get(s, name)
 | |
| }
 | |
| 
 | |
| // New returns a session for the given name without adding it to the registry.
 | |
| //
 | |
| // See CookieStore.New().
 | |
| func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
 | |
| 	session := NewSession(s, name)
 | |
| 	opts := *s.Options
 | |
| 	session.Options = &opts
 | |
| 	session.IsNew = true
 | |
| 	var err error
 | |
| 	if c, errCookie := r.Cookie(name); errCookie == nil {
 | |
| 		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
 | |
| 		if err == nil {
 | |
| 			err = s.load(session)
 | |
| 			if err == nil {
 | |
| 				session.IsNew = false
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return session, err
 | |
| }
 | |
| 
 | |
| // Save adds a single session to the response.
 | |
| //
 | |
| // If the Options.MaxAge of the session is <= 0 then the session file will be
 | |
| // deleted from the store path. With this process it enforces the properly
 | |
| // session cookie handling so no need to trust in the cookie management in the
 | |
| // web browser.
 | |
| func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
 | |
| 	session *Session) error {
 | |
| 	// Delete if max-age is <= 0
 | |
| 	if session.Options.MaxAge <= 0 {
 | |
| 		if err := s.erase(session); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if session.ID == "" {
 | |
| 		// Because the ID is used in the filename, encode it to
 | |
| 		// use alphanumeric characters only.
 | |
| 		session.ID = strings.TrimRight(
 | |
| 			base32.StdEncoding.EncodeToString(
 | |
| 				securecookie.GenerateRandomKey(32)), "=")
 | |
| 	}
 | |
| 	if err := s.save(session); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
 | |
| 		s.Codecs...)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // MaxAge sets the maximum age for the store and the underlying cookie
 | |
| // implementation. Individual sessions can be deleted by setting Options.MaxAge
 | |
| // = -1 for that session.
 | |
| func (s *FilesystemStore) MaxAge(age int) {
 | |
| 	s.Options.MaxAge = age
 | |
| 
 | |
| 	// Set the maxAge for each securecookie instance.
 | |
| 	for _, codec := range s.Codecs {
 | |
| 		if sc, ok := codec.(*securecookie.SecureCookie); ok {
 | |
| 			sc.MaxAge(age)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // save writes encoded session.Values to a file.
 | |
| func (s *FilesystemStore) save(session *Session) error {
 | |
| 	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
 | |
| 		s.Codecs...)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	filename := filepath.Join(s.path, "session_"+session.ID)
 | |
| 	fileMutex.Lock()
 | |
| 	defer fileMutex.Unlock()
 | |
| 	return ioutil.WriteFile(filename, []byte(encoded), 0600)
 | |
| }
 | |
| 
 | |
| // load reads a file and decodes its content into session.Values.
 | |
| func (s *FilesystemStore) load(session *Session) error {
 | |
| 	filename := filepath.Join(s.path, "session_"+session.ID)
 | |
| 	fileMutex.RLock()
 | |
| 	defer fileMutex.RUnlock()
 | |
| 	fdata, err := ioutil.ReadFile(filename)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err = securecookie.DecodeMulti(session.Name(), string(fdata),
 | |
| 		&session.Values, s.Codecs...); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // delete session file
 | |
| func (s *FilesystemStore) erase(session *Session) error {
 | |
| 	filename := filepath.Join(s.path, "session_"+session.ID)
 | |
| 
 | |
| 	fileMutex.RLock()
 | |
| 	defer fileMutex.RUnlock()
 | |
| 
 | |
| 	err := os.Remove(filename)
 | |
| 	return err
 | |
| }
 |