You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

store.go 8.5KB

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