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.

394 lines
11 KiB

  1. // Copyright 2013 Beego Authors
  2. // Copyright 2014 The Macaron Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. // Package session a middleware that provides the session management of Macaron.
  16. package session
  17. import (
  18. "encoding/hex"
  19. "errors"
  20. "fmt"
  21. "net/http"
  22. "net/url"
  23. "time"
  24. "gopkg.in/macaron.v1"
  25. )
  26. const _VERSION = "0.6.0"
  27. func Version() string {
  28. return _VERSION
  29. }
  30. // RawStore is the interface that operates the session data.
  31. type RawStore interface {
  32. // Set sets value to given key in session.
  33. Set(interface{}, interface{}) error
  34. // Get gets value by given key in session.
  35. Get(interface{}) interface{}
  36. // Delete deletes a key from session.
  37. Delete(interface{}) error
  38. // ID returns current session ID.
  39. ID() string
  40. // Release releases session resource and save data to provider.
  41. Release() error
  42. // Flush deletes all session data.
  43. Flush() error
  44. }
  45. // Store is the interface that contains all data for one session process with specific ID.
  46. type Store interface {
  47. RawStore
  48. // Read returns raw session store by session ID.
  49. Read(string) (RawStore, error)
  50. // Destory deletes a session.
  51. Destory(*macaron.Context) error
  52. // RegenerateId regenerates a session store from old session ID to new one.
  53. RegenerateId(*macaron.Context) (RawStore, error)
  54. // Count counts and returns number of sessions.
  55. Count() int
  56. // GC calls GC to clean expired sessions.
  57. GC()
  58. }
  59. type store struct {
  60. RawStore
  61. *Manager
  62. }
  63. var _ Store = &store{}
  64. // Options represents a struct for specifying configuration options for the session middleware.
  65. type Options struct {
  66. // Name of provider. Default is "memory".
  67. Provider string
  68. // Provider configuration, it's corresponding to provider.
  69. ProviderConfig string
  70. // Cookie name to save session ID. Default is "MacaronSession".
  71. CookieName string
  72. // Cookie path to store. Default is "/".
  73. CookiePath string
  74. // GC interval time in seconds. Default is 3600.
  75. Gclifetime int64
  76. // Max life time in seconds. Default is whatever GC interval time is.
  77. Maxlifetime int64
  78. // Use HTTPS only. Default is false.
  79. Secure bool
  80. // Cookie life time. Default is 0.
  81. CookieLifeTime int
  82. // Cookie domain name. Default is empty.
  83. Domain string
  84. // Session ID length. Default is 16.
  85. IDLength int
  86. // Configuration section name. Default is "session".
  87. Section string
  88. // Ignore release for websocket. Default is false.
  89. IgnoreReleaseForWebSocket bool
  90. }
  91. func prepareOptions(options []Options) Options {
  92. var opt Options
  93. if len(options) > 0 {
  94. opt = options[0]
  95. }
  96. if len(opt.Section) == 0 {
  97. opt.Section = "session"
  98. }
  99. sec := macaron.Config().Section(opt.Section)
  100. if len(opt.Provider) == 0 {
  101. opt.Provider = sec.Key("PROVIDER").MustString("memory")
  102. }
  103. if len(opt.ProviderConfig) == 0 {
  104. opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions")
  105. }
  106. if len(opt.CookieName) == 0 {
  107. opt.CookieName = sec.Key("COOKIE_NAME").MustString("MacaronSession")
  108. }
  109. if len(opt.CookiePath) == 0 {
  110. opt.CookiePath = sec.Key("COOKIE_PATH").MustString("/")
  111. }
  112. if opt.Gclifetime == 0 {
  113. opt.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(3600)
  114. }
  115. if opt.Maxlifetime == 0 {
  116. opt.Maxlifetime = sec.Key("MAX_LIFE_TIME").MustInt64(opt.Gclifetime)
  117. }
  118. if !opt.Secure {
  119. opt.Secure = sec.Key("SECURE").MustBool()
  120. }
  121. if opt.CookieLifeTime == 0 {
  122. opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt()
  123. }
  124. if len(opt.Domain) == 0 {
  125. opt.Domain = sec.Key("DOMAIN").String()
  126. }
  127. if opt.IDLength == 0 {
  128. opt.IDLength = sec.Key("ID_LENGTH").MustInt(16)
  129. }
  130. if !opt.IgnoreReleaseForWebSocket {
  131. opt.IgnoreReleaseForWebSocket = sec.Key("IGNORE_RELEASE_FOR_WEBSOCKET").MustBool()
  132. }
  133. return opt
  134. }
  135. // Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain.
  136. // An single variadic session.Options struct can be optionally provided to configure.
  137. func Sessioner(options ...Options) macaron.Handler {
  138. opt := prepareOptions(options)
  139. manager, err := NewManager(opt.Provider, opt)
  140. if err != nil {
  141. panic(err)
  142. }
  143. go manager.startGC()
  144. return func(ctx *macaron.Context) {
  145. sess, err := manager.Start(ctx)
  146. if err != nil {
  147. panic("session(start): " + err.Error())
  148. }
  149. // Get flash.
  150. vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash"))
  151. if len(vals) > 0 {
  152. f := &Flash{Values: vals}
  153. f.ErrorMsg = f.Get("error")
  154. f.SuccessMsg = f.Get("success")
  155. f.InfoMsg = f.Get("info")
  156. f.WarningMsg = f.Get("warning")
  157. ctx.Data["Flash"] = f
  158. ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath)
  159. }
  160. f := &Flash{ctx, url.Values{}, "", "", "", ""}
  161. ctx.Resp.Before(func(macaron.ResponseWriter) {
  162. if flash := f.Encode(); len(flash) > 0 {
  163. ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath)
  164. }
  165. })
  166. ctx.Map(f)
  167. s := store{
  168. RawStore: sess,
  169. Manager: manager,
  170. }
  171. ctx.MapTo(s, (*Store)(nil))
  172. ctx.Next()
  173. if manager.opt.IgnoreReleaseForWebSocket && ctx.Req.Header.Get("Upgrade") == "websocket" {
  174. return
  175. }
  176. if err = sess.Release(); err != nil {
  177. panic("session(release): " + err.Error())
  178. }
  179. }
  180. }
  181. // Provider is the interface that provides session manipulations.
  182. type Provider interface {
  183. // Init initializes session provider.
  184. Init(gclifetime int64, config string) error
  185. // Read returns raw session store by session ID.
  186. Read(sid string) (RawStore, error)
  187. // Exist returns true if session with given ID exists.
  188. Exist(sid string) bool
  189. // Destory deletes a session by session ID.
  190. Destory(sid string) error
  191. // Regenerate regenerates a session store from old session ID to new one.
  192. Regenerate(oldsid, sid string) (RawStore, error)
  193. // Count counts and returns number of sessions.
  194. Count() int
  195. // GC calls GC to clean expired sessions.
  196. GC()
  197. }
  198. var providers = make(map[string]Provider)
  199. // Register registers a provider.
  200. func Register(name string, provider Provider) {
  201. if provider == nil {
  202. panic("session: cannot register provider with nil value")
  203. }
  204. if _, dup := providers[name]; dup {
  205. panic(fmt.Errorf("session: cannot register provider '%s' twice", name))
  206. }
  207. providers[name] = provider
  208. }
  209. // _____
  210. // / \ _____ ____ _____ ____ ___________
  211. // / \ / \\__ \ / \\__ \ / ___\_/ __ \_ __ \
  212. // / Y \/ __ \| | \/ __ \_/ /_/ > ___/| | \/
  213. // \____|__ (____ /___| (____ /\___ / \___ >__|
  214. // \/ \/ \/ \//_____/ \/
  215. // Manager represents a struct that contains session provider and its configuration.
  216. type Manager struct {
  217. provider Provider
  218. opt Options
  219. }
  220. // NewManager creates and returns a new session manager by given provider name and configuration.
  221. // It panics when given provider isn't registered.
  222. func NewManager(name string, opt Options) (*Manager, error) {
  223. p, ok := providers[name]
  224. if !ok {
  225. return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name)
  226. }
  227. return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig)
  228. }
  229. // sessionID generates a new session ID with rand string, unix nano time, remote addr by hash function.
  230. func (m *Manager) sessionID() string {
  231. return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2))
  232. }
  233. // validSessionID tests whether a provided session ID is a valid session ID.
  234. func (m *Manager) validSessionID(sid string) (bool, error) {
  235. if len(sid) != m.opt.IDLength {
  236. return false, errors.New("invalid 'sid': " + sid)
  237. }
  238. for i := range sid {
  239. switch {
  240. case '0' <= sid[i] && sid[i] <= '9':
  241. case 'a' <= sid[i] && sid[i] <= 'f':
  242. default:
  243. return false, errors.New("invalid 'sid': " + sid)
  244. }
  245. }
  246. return true, nil
  247. }
  248. // Start starts a session by generating new one
  249. // or retrieve existence one by reading session ID from HTTP request if it's valid.
  250. func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) {
  251. sid := ctx.GetCookie(m.opt.CookieName)
  252. valid, _ := m.validSessionID(sid)
  253. if len(sid) > 0 && valid && m.provider.Exist(sid) {
  254. return m.provider.Read(sid)
  255. }
  256. sid = m.sessionID()
  257. sess, err := m.provider.Read(sid)
  258. if err != nil {
  259. return nil, err
  260. }
  261. cookie := &http.Cookie{
  262. Name: m.opt.CookieName,
  263. Value: sid,
  264. Path: m.opt.CookiePath,
  265. HttpOnly: true,
  266. Secure: m.opt.Secure,
  267. Domain: m.opt.Domain,
  268. }
  269. if m.opt.CookieLifeTime >= 0 {
  270. cookie.MaxAge = m.opt.CookieLifeTime
  271. }
  272. http.SetCookie(ctx.Resp, cookie)
  273. ctx.Req.AddCookie(cookie)
  274. return sess, nil
  275. }
  276. // Read returns raw session store by session ID.
  277. func (m *Manager) Read(sid string) (RawStore, error) {
  278. // Ensure we're trying to read a valid session ID
  279. if _, err := m.validSessionID(sid); err != nil {
  280. return nil, err
  281. }
  282. return m.provider.Read(sid)
  283. }
  284. // Destory deletes a session by given ID.
  285. func (m *Manager) Destory(ctx *macaron.Context) error {
  286. sid := ctx.GetCookie(m.opt.CookieName)
  287. if len(sid) == 0 {
  288. return nil
  289. }
  290. if _, err := m.validSessionID(sid); err != nil {
  291. return err
  292. }
  293. if err := m.provider.Destory(sid); err != nil {
  294. return err
  295. }
  296. cookie := &http.Cookie{
  297. Name: m.opt.CookieName,
  298. Path: m.opt.CookiePath,
  299. HttpOnly: true,
  300. Expires: time.Now(),
  301. MaxAge: -1,
  302. }
  303. http.SetCookie(ctx.Resp, cookie)
  304. return nil
  305. }
  306. // RegenerateId regenerates a session store from old session ID to new one.
  307. func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
  308. sid := m.sessionID()
  309. oldsid := ctx.GetCookie(m.opt.CookieName)
  310. _, err = m.validSessionID(oldsid)
  311. if err != nil {
  312. return nil, err
  313. }
  314. sess, err = m.provider.Regenerate(oldsid, sid)
  315. if err != nil {
  316. return nil, err
  317. }
  318. cookie := &http.Cookie{
  319. Name: m.opt.CookieName,
  320. Value: sid,
  321. Path: m.opt.CookiePath,
  322. HttpOnly: true,
  323. Secure: m.opt.Secure,
  324. Domain: m.opt.Domain,
  325. }
  326. if m.opt.CookieLifeTime >= 0 {
  327. cookie.MaxAge = m.opt.CookieLifeTime
  328. }
  329. http.SetCookie(ctx.Resp, cookie)
  330. ctx.Req.AddCookie(cookie)
  331. return sess, nil
  332. }
  333. // Count counts and returns number of sessions.
  334. func (m *Manager) Count() int {
  335. return m.provider.Count()
  336. }
  337. // GC starts GC job in a certain period.
  338. func (m *Manager) GC() {
  339. m.provider.GC()
  340. }
  341. // startGC starts GC job in a certain period.
  342. func (m *Manager) startGC() {
  343. m.GC()
  344. time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() })
  345. }
  346. // SetSecure indicates whether to set cookie with HTTPS or not.
  347. func (m *Manager) SetSecure(secure bool) {
  348. m.opt.Secure = secure
  349. }