|
- // 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
- }
|