|
- // Copyright 2013 Beego Authors
- // Copyright 2014 The Macaron Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License"): you may
- // not use this file except in compliance with the License. You may obtain
- // a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- // License for the specific language governing permissions and limitations
- // under the License.
-
- // Package session a middleware that provides the session management of Macaron.
- package session
-
- import (
- "encoding/hex"
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "time"
-
- "gitea.com/macaron/macaron"
- )
-
- const _VERSION = "0.6.0"
-
- func Version() string {
- return _VERSION
- }
-
- // RawStore is the interface that operates the session data.
- type RawStore interface {
- // Set sets value to given key in session.
- Set(interface{}, interface{}) error
- // Get gets value by given key in session.
- Get(interface{}) interface{}
- // Delete deletes a key from session.
- Delete(interface{}) error
- // ID returns current session ID.
- ID() string
- // Release releases session resource and save data to provider.
- Release() error
- // Flush deletes all session data.
- Flush() error
- }
-
- // Store is the interface that contains all data for one session process with specific ID.
- type Store interface {
- RawStore
- // Read returns raw session store by session ID.
- Read(string) (RawStore, error)
- // Destroy deletes a session.
- Destroy(*macaron.Context) error
- // RegenerateId regenerates a session store from old session ID to new one.
- RegenerateId(*macaron.Context) (RawStore, error)
- // Count counts and returns number of sessions.
- Count() int
- // GC calls GC to clean expired sessions.
- GC()
- }
-
- type store struct {
- RawStore
- *Manager
- }
-
- var _ Store = &store{}
-
- // Options represents a struct for specifying configuration options for the session middleware.
- type Options struct {
- // Name of provider. Default is "memory".
- Provider string
- // Provider configuration, it's corresponding to provider.
- ProviderConfig string
- // Cookie name to save session ID. Default is "MacaronSession".
- CookieName string
- // Cookie path to store. Default is "/".
- CookiePath string
- // GC interval time in seconds. Default is 3600.
- Gclifetime int64
- // Max life time in seconds. Default is whatever GC interval time is.
- Maxlifetime int64
- // Use HTTPS only. Default is false.
- Secure bool
- // Cookie life time. Default is 0.
- CookieLifeTime int
- // Cookie domain name. Default is empty.
- Domain string
- // Session ID length. Default is 16.
- IDLength int
- // Configuration section name. Default is "session".
- Section string
- // Ignore release for websocket. Default is false.
- IgnoreReleaseForWebSocket bool
- }
-
- func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
- if len(opt.Section) == 0 {
- opt.Section = "session"
- }
- sec := macaron.Config().Section(opt.Section)
-
- if len(opt.Provider) == 0 {
- opt.Provider = sec.Key("PROVIDER").MustString("memory")
- }
- if len(opt.ProviderConfig) == 0 {
- opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions")
- }
- if len(opt.CookieName) == 0 {
- opt.CookieName = sec.Key("COOKIE_NAME").MustString("MacaronSession")
- }
- if len(opt.CookiePath) == 0 {
- opt.CookiePath = sec.Key("COOKIE_PATH").MustString("/")
- }
- if opt.Gclifetime == 0 {
- opt.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(3600)
- }
- if opt.Maxlifetime == 0 {
- opt.Maxlifetime = sec.Key("MAX_LIFE_TIME").MustInt64(opt.Gclifetime)
- }
- if !opt.Secure {
- opt.Secure = sec.Key("SECURE").MustBool()
- }
- if opt.CookieLifeTime == 0 {
- opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt()
- }
- if len(opt.Domain) == 0 {
- opt.Domain = sec.Key("DOMAIN").String()
- }
- if opt.IDLength == 0 {
- opt.IDLength = sec.Key("ID_LENGTH").MustInt(16)
- }
- if !opt.IgnoreReleaseForWebSocket {
- opt.IgnoreReleaseForWebSocket = sec.Key("IGNORE_RELEASE_FOR_WEBSOCKET").MustBool()
- }
-
- return opt
- }
-
- // Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain.
- // An single variadic session.Options struct can be optionally provided to configure.
- func Sessioner(options ...Options) macaron.Handler {
- opt := prepareOptions(options)
- manager, err := NewManager(opt.Provider, opt)
- if err != nil {
- panic(err)
- }
- go manager.startGC()
-
- return func(ctx *macaron.Context) {
- sess, err := manager.Start(ctx)
- if err != nil {
- panic("session(start): " + err.Error())
- }
-
- // Get flash.
- vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash"))
- if len(vals) > 0 {
- f := &Flash{Values: vals}
- f.ErrorMsg = f.Get("error")
- f.SuccessMsg = f.Get("success")
- f.InfoMsg = f.Get("info")
- f.WarningMsg = f.Get("warning")
- ctx.Data["Flash"] = f
- ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath)
- }
-
- f := &Flash{ctx, url.Values{}, "", "", "", ""}
- ctx.Resp.Before(func(macaron.ResponseWriter) {
- if flash := f.Encode(); len(flash) > 0 {
- ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath)
- }
- })
-
- ctx.Map(f)
- s := store{
- RawStore: sess,
- Manager: manager,
- }
-
- ctx.MapTo(s, (*Store)(nil))
-
- ctx.Next()
-
- if manager.opt.IgnoreReleaseForWebSocket && ctx.Req.Header.Get("Upgrade") == "websocket" {
- return
- }
-
- if err = sess.Release(); err != nil {
- panic("session(release): " + err.Error())
- }
- }
- }
-
- // Provider is the interface that provides session manipulations.
- type Provider interface {
- // Init initializes session provider.
- Init(gclifetime int64, config string) error
- // Read returns raw session store by session ID.
- Read(sid string) (RawStore, error)
- // Exist returns true if session with given ID exists.
- Exist(sid string) bool
- // Destroy deletes a session by session ID.
- Destroy(sid string) error
- // Regenerate regenerates a session store from old session ID to new one.
- Regenerate(oldsid, sid string) (RawStore, error)
- // Count counts and returns number of sessions.
- Count() int
- // GC calls GC to clean expired sessions.
- GC()
- }
-
- var providers = make(map[string]Provider)
-
- // Register registers a provider.
- func Register(name string, provider Provider) {
- if provider == nil {
- panic("session: cannot register provider with nil value")
- }
- if _, dup := providers[name]; dup {
- panic(fmt.Errorf("session: cannot register provider '%s' twice", name))
- }
- providers[name] = provider
- }
-
- // _____
- // / \ _____ ____ _____ ____ ___________
- // / \ / \\__ \ / \\__ \ / ___\_/ __ \_ __ \
- // / Y \/ __ \| | \/ __ \_/ /_/ > ___/| | \/
- // \____|__ (____ /___| (____ /\___ / \___ >__|
- // \/ \/ \/ \//_____/ \/
-
- // Manager represents a struct that contains session provider and its configuration.
- type Manager struct {
- provider Provider
- opt Options
- }
-
- // NewManager creates and returns a new session manager by given provider name and configuration.
- // It panics when given provider isn't registered.
- func NewManager(name string, opt Options) (*Manager, error) {
- p, ok := providers[name]
- if !ok {
- return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name)
- }
- return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig)
- }
-
- // sessionID generates a new session ID with rand string, unix nano time, remote addr by hash function.
- func (m *Manager) sessionID() string {
- return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2))
- }
-
- // validSessionID tests whether a provided session ID is a valid session ID.
- func (m *Manager) validSessionID(sid string) (bool, error) {
- if len(sid) != m.opt.IDLength {
- return false, errors.New("invalid 'sid': " + sid)
- }
-
- for i := range sid {
- switch {
- case '0' <= sid[i] && sid[i] <= '9':
- case 'a' <= sid[i] && sid[i] <= 'f':
- default:
- return false, errors.New("invalid 'sid': " + sid)
- }
- }
- return true, nil
- }
-
- // Start starts a session by generating new one
- // or retrieve existence one by reading session ID from HTTP request if it's valid.
- func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) {
- sid := ctx.GetCookie(m.opt.CookieName)
- valid, _ := m.validSessionID(sid)
- if len(sid) > 0 && valid && m.provider.Exist(sid) {
- return m.provider.Read(sid)
- }
-
- sid = m.sessionID()
- sess, err := m.provider.Read(sid)
- if err != nil {
- return nil, err
- }
-
- cookie := &http.Cookie{
- Name: m.opt.CookieName,
- Value: sid,
- Path: m.opt.CookiePath,
- HttpOnly: true,
- Secure: m.opt.Secure,
- Domain: m.opt.Domain,
- }
- if m.opt.CookieLifeTime >= 0 {
- cookie.MaxAge = m.opt.CookieLifeTime
- }
- http.SetCookie(ctx.Resp, cookie)
- ctx.Req.AddCookie(cookie)
- return sess, nil
- }
-
- // Read returns raw session store by session ID.
- func (m *Manager) Read(sid string) (RawStore, error) {
- // Ensure we're trying to read a valid session ID
- if _, err := m.validSessionID(sid); err != nil {
- return nil, err
- }
-
- return m.provider.Read(sid)
- }
-
- // Destroy deletes a session by given ID.
- func (m *Manager) Destroy(ctx *macaron.Context) error {
- sid := ctx.GetCookie(m.opt.CookieName)
- if len(sid) == 0 {
- return nil
- }
-
- if _, err := m.validSessionID(sid); err != nil {
- return err
- }
-
- if err := m.provider.Destroy(sid); err != nil {
- return err
- }
- cookie := &http.Cookie{
- Name: m.opt.CookieName,
- Path: m.opt.CookiePath,
- HttpOnly: true,
- Expires: time.Now(),
- MaxAge: -1,
- }
- http.SetCookie(ctx.Resp, cookie)
- return nil
- }
-
- // RegenerateId regenerates a session store from old session ID to new one.
- func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
- sid := m.sessionID()
- oldsid := ctx.GetCookie(m.opt.CookieName)
- _, err = m.validSessionID(oldsid)
- if err != nil {
- return nil, err
- }
- sess, err = m.provider.Regenerate(oldsid, sid)
- if err != nil {
- return nil, err
- }
- cookie := &http.Cookie{
- Name: m.opt.CookieName,
- Value: sid,
- Path: m.opt.CookiePath,
- HttpOnly: true,
- Secure: m.opt.Secure,
- Domain: m.opt.Domain,
- }
- if m.opt.CookieLifeTime >= 0 {
- cookie.MaxAge = m.opt.CookieLifeTime
- }
- http.SetCookie(ctx.Resp, cookie)
- ctx.Req.AddCookie(cookie)
- return sess, nil
- }
-
- // Count counts and returns number of sessions.
- func (m *Manager) Count() int {
- return m.provider.Count()
- }
-
- // GC starts GC job in a certain period.
- func (m *Manager) GC() {
- m.provider.GC()
- }
-
- // startGC starts GC job in a certain period.
- func (m *Manager) startGC() {
- m.GC()
- time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() })
- }
-
- // SetSecure indicates whether to set cookie with HTTPS or not.
- func (m *Manager) SetSecure(secure bool) {
- m.opt.Secure = secure
- }
|