store.go 8.4 KB

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