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 }