gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

store.go (8569B)


      1 // Copyright 2012 The Gorilla Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package sessions
      6 
      7 import (
      8 	"encoding/base32"
      9 	"io/ioutil"
     10 	"net/http"
     11 	"os"
     12 	"path/filepath"
     13 	"strings"
     14 	"sync"
     15 
     16 	"github.com/gorilla/securecookie"
     17 )
     18 
     19 // Store is an interface for custom session stores.
     20 //
     21 // See CookieStore and FilesystemStore for examples.
     22 type Store interface {
     23 	// Get should return a cached session.
     24 	Get(r *http.Request, name string) (*Session, error)
     25 
     26 	// New should create and return a new session.
     27 	//
     28 	// Note that New should never return a nil session, even in the case of
     29 	// an error if using the Registry infrastructure to cache the session.
     30 	New(r *http.Request, name string) (*Session, error)
     31 
     32 	// Save should persist session to the underlying store implementation.
     33 	Save(r *http.Request, w http.ResponseWriter, s *Session) error
     34 }
     35 
     36 // CookieStore ----------------------------------------------------------------
     37 
     38 // NewCookieStore returns a new CookieStore.
     39 //
     40 // Keys are defined in pairs to allow key rotation, but the common case is
     41 // to set a single authentication key and optionally an encryption key.
     42 //
     43 // The first key in a pair is used for authentication and the second for
     44 // encryption. The encryption key can be set to nil or omitted in the last
     45 // pair, but the authentication key is required in all pairs.
     46 //
     47 // It is recommended to use an authentication key with 32 or 64 bytes.
     48 // The encryption key, if set, must be either 16, 24, or 32 bytes to select
     49 // AES-128, AES-192, or AES-256 modes.
     50 func NewCookieStore(keyPairs ...[]byte) *CookieStore {
     51 	cs := &CookieStore{
     52 		Codecs: securecookie.CodecsFromPairs(keyPairs...),
     53 		Options: &Options{
     54 			Path:   "/",
     55 			MaxAge: 86400 * 30,
     56 		},
     57 	}
     58 
     59 	cs.MaxAge(cs.Options.MaxAge)
     60 	return cs
     61 }
     62 
     63 // CookieStore stores sessions using secure cookies.
     64 type CookieStore struct {
     65 	Codecs  []securecookie.Codec
     66 	Options *Options // default configuration
     67 }
     68 
     69 // Get returns a session for the given name after adding it to the registry.
     70 //
     71 // It returns a new session if the sessions doesn't exist. Access IsNew on
     72 // the session to check if it is an existing session or a new one.
     73 //
     74 // It returns a new session and an error if the session exists but could
     75 // not be decoded.
     76 func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
     77 	return GetRegistry(r).Get(s, name)
     78 }
     79 
     80 // New returns a session for the given name without adding it to the registry.
     81 //
     82 // The difference between New() and Get() is that calling New() twice will
     83 // decode the session data twice, while Get() registers and reuses the same
     84 // decoded session after the first call.
     85 func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
     86 	session := NewSession(s, name)
     87 	opts := *s.Options
     88 	session.Options = &opts
     89 	session.IsNew = true
     90 	var err error
     91 	if c, errCookie := r.Cookie(name); errCookie == nil {
     92 		err = securecookie.DecodeMulti(name, c.Value, &session.Values,
     93 			s.Codecs...)
     94 		if err == nil {
     95 			session.IsNew = false
     96 		}
     97 	}
     98 	return session, err
     99 }
    100 
    101 // Save adds a single session to the response.
    102 func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
    103 	session *Session) error {
    104 	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
    105 		s.Codecs...)
    106 	if err != nil {
    107 		return err
    108 	}
    109 	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
    110 	return nil
    111 }
    112 
    113 // MaxAge sets the maximum age for the store and the underlying cookie
    114 // implementation. Individual sessions can be deleted by setting Options.MaxAge
    115 // = -1 for that session.
    116 func (s *CookieStore) MaxAge(age int) {
    117 	s.Options.MaxAge = age
    118 
    119 	// Set the maxAge for each securecookie instance.
    120 	for _, codec := range s.Codecs {
    121 		if sc, ok := codec.(*securecookie.SecureCookie); ok {
    122 			sc.MaxAge(age)
    123 		}
    124 	}
    125 }
    126 
    127 // FilesystemStore ------------------------------------------------------------
    128 
    129 var fileMutex sync.RWMutex
    130 
    131 // NewFilesystemStore returns a new FilesystemStore.
    132 //
    133 // The path argument is the directory where sessions will be saved. If empty
    134 // it will use os.TempDir().
    135 //
    136 // See NewCookieStore() for a description of the other parameters.
    137 func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
    138 	if path == "" {
    139 		path = os.TempDir()
    140 	}
    141 	fs := &FilesystemStore{
    142 		Codecs: securecookie.CodecsFromPairs(keyPairs...),
    143 		Options: &Options{
    144 			Path:   "/",
    145 			MaxAge: 86400 * 30,
    146 		},
    147 		path: path,
    148 	}
    149 
    150 	fs.MaxAge(fs.Options.MaxAge)
    151 	return fs
    152 }
    153 
    154 // FilesystemStore stores sessions in the filesystem.
    155 //
    156 // It also serves as a reference for custom stores.
    157 //
    158 // This store is still experimental and not well tested. Feedback is welcome.
    159 type FilesystemStore struct {
    160 	Codecs  []securecookie.Codec
    161 	Options *Options // default configuration
    162 	path    string
    163 }
    164 
    165 // MaxLength restricts the maximum length of new sessions to l.
    166 // If l is 0 there is no limit to the size of a session, use with caution.
    167 // The default for a new FilesystemStore is 4096.
    168 func (s *FilesystemStore) MaxLength(l int) {
    169 	for _, c := range s.Codecs {
    170 		if codec, ok := c.(*securecookie.SecureCookie); ok {
    171 			codec.MaxLength(l)
    172 		}
    173 	}
    174 }
    175 
    176 // Get returns a session for the given name after adding it to the registry.
    177 //
    178 // See CookieStore.Get().
    179 func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
    180 	return GetRegistry(r).Get(s, name)
    181 }
    182 
    183 // New returns a session for the given name without adding it to the registry.
    184 //
    185 // See CookieStore.New().
    186 func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
    187 	session := NewSession(s, name)
    188 	opts := *s.Options
    189 	session.Options = &opts
    190 	session.IsNew = true
    191 	var err error
    192 	if c, errCookie := r.Cookie(name); errCookie == nil {
    193 		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
    194 		if err == nil {
    195 			err = s.load(session)
    196 			if err == nil {
    197 				session.IsNew = false
    198 			}
    199 		}
    200 	}
    201 	return session, err
    202 }
    203 
    204 // Save adds a single session to the response.
    205 //
    206 // If the Options.MaxAge of the session is <= 0 then the session file will be
    207 // deleted from the store path. With this process it enforces the properly
    208 // session cookie handling so no need to trust in the cookie management in the
    209 // web browser.
    210 func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
    211 	session *Session) error {
    212 	// Delete if max-age is <= 0
    213 	if session.Options.MaxAge <= 0 {
    214 		if err := s.erase(session); err != nil {
    215 			return err
    216 		}
    217 		http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
    218 		return nil
    219 	}
    220 
    221 	if session.ID == "" {
    222 		// Because the ID is used in the filename, encode it to
    223 		// use alphanumeric characters only.
    224 		session.ID = strings.TrimRight(
    225 			base32.StdEncoding.EncodeToString(
    226 				securecookie.GenerateRandomKey(32)), "=")
    227 	}
    228 	if err := s.save(session); err != nil {
    229 		return err
    230 	}
    231 	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
    232 		s.Codecs...)
    233 	if err != nil {
    234 		return err
    235 	}
    236 	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
    237 	return nil
    238 }
    239 
    240 // MaxAge sets the maximum age for the store and the underlying cookie
    241 // implementation. Individual sessions can be deleted by setting Options.MaxAge
    242 // = -1 for that session.
    243 func (s *FilesystemStore) MaxAge(age int) {
    244 	s.Options.MaxAge = age
    245 
    246 	// Set the maxAge for each securecookie instance.
    247 	for _, codec := range s.Codecs {
    248 		if sc, ok := codec.(*securecookie.SecureCookie); ok {
    249 			sc.MaxAge(age)
    250 		}
    251 	}
    252 }
    253 
    254 // save writes encoded session.Values to a file.
    255 func (s *FilesystemStore) save(session *Session) error {
    256 	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
    257 		s.Codecs...)
    258 	if err != nil {
    259 		return err
    260 	}
    261 	filename := filepath.Join(s.path, "session_"+session.ID)
    262 	fileMutex.Lock()
    263 	defer fileMutex.Unlock()
    264 	return ioutil.WriteFile(filename, []byte(encoded), 0600)
    265 }
    266 
    267 // load reads a file and decodes its content into session.Values.
    268 func (s *FilesystemStore) load(session *Session) error {
    269 	filename := filepath.Join(s.path, "session_"+session.ID)
    270 	fileMutex.RLock()
    271 	defer fileMutex.RUnlock()
    272 	fdata, err := ioutil.ReadFile(filename)
    273 	if err != nil {
    274 		return err
    275 	}
    276 	if err = securecookie.DecodeMulti(session.Name(), string(fdata),
    277 		&session.Values, s.Codecs...); err != nil {
    278 		return err
    279 	}
    280 	return nil
    281 }
    282 
    283 // delete session file
    284 func (s *FilesystemStore) erase(session *Session) error {
    285 	filename := filepath.Join(s.path, "session_"+session.ID)
    286 
    287 	fileMutex.RLock()
    288 	defer fileMutex.RUnlock()
    289 
    290 	err := os.Remove(filename)
    291 	return err
    292 }