github.com/astaxie/beego@v1.12.3/session/session.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package session provider
    16  //
    17  // Usage:
    18  // import(
    19  //   "github.com/astaxie/beego/session"
    20  // )
    21  //
    22  //	func init() {
    23  //      globalSessions, _ = session.NewManager("memory", `{"cookieName":"gosessionid", "enableSetCookie,omitempty": true, "gclifetime":3600, "maxLifetime": 3600, "secure": false, "cookieLifeTime": 3600, "providerConfig": ""}`)
    24  //		go globalSessions.GC()
    25  //	}
    26  //
    27  // more docs: http://beego.me/docs/module/session.md
    28  package session
    29  
    30  import (
    31  	"crypto/rand"
    32  	"encoding/hex"
    33  	"errors"
    34  	"fmt"
    35  	"io"
    36  	"log"
    37  	"net/http"
    38  	"net/textproto"
    39  	"net/url"
    40  	"os"
    41  	"time"
    42  )
    43  
    44  // Store contains all data for one session process with specific id.
    45  type Store interface {
    46  	Set(key, value interface{}) error     //set session value
    47  	Get(key interface{}) interface{}      //get session value
    48  	Delete(key interface{}) error         //delete session value
    49  	SessionID() string                    //back current sessionID
    50  	SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data
    51  	Flush() error                         //delete all data
    52  }
    53  
    54  // Provider contains global session methods and saved SessionStores.
    55  // it can operate a SessionStore by its id.
    56  type Provider interface {
    57  	SessionInit(gclifetime int64, config string) error
    58  	SessionRead(sid string) (Store, error)
    59  	SessionExist(sid string) bool
    60  	SessionRegenerate(oldsid, sid string) (Store, error)
    61  	SessionDestroy(sid string) error
    62  	SessionAll() int //get all active session
    63  	SessionGC()
    64  }
    65  
    66  var provides = make(map[string]Provider)
    67  
    68  // SLogger a helpful variable to log information about session
    69  var SLogger = NewSessionLog(os.Stderr)
    70  
    71  // Register makes a session provide available by the provided name.
    72  // If Register is called twice with the same name or if driver is nil,
    73  // it panics.
    74  func Register(name string, provide Provider) {
    75  	if provide == nil {
    76  		panic("session: Register provide is nil")
    77  	}
    78  	if _, dup := provides[name]; dup {
    79  		panic("session: Register called twice for provider " + name)
    80  	}
    81  	provides[name] = provide
    82  }
    83  
    84  //GetProvider
    85  func GetProvider(name string) (Provider, error) {
    86  	provider, ok := provides[name]
    87  	if !ok {
    88  		return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", name)
    89  	}
    90  	return provider, nil
    91  }
    92  
    93  // ManagerConfig define the session config
    94  type ManagerConfig struct {
    95  	CookieName              string        `json:"cookieName"`
    96  	EnableSetCookie         bool          `json:"enableSetCookie,omitempty"`
    97  	Gclifetime              int64         `json:"gclifetime"`
    98  	Maxlifetime             int64         `json:"maxLifetime"`
    99  	DisableHTTPOnly         bool          `json:"disableHTTPOnly"`
   100  	Secure                  bool          `json:"secure"`
   101  	CookieLifeTime          int           `json:"cookieLifeTime"`
   102  	ProviderConfig          string        `json:"providerConfig"`
   103  	Domain                  string        `json:"domain"`
   104  	SessionIDLength         int64         `json:"sessionIDLength"`
   105  	EnableSidInHTTPHeader   bool          `json:"EnableSidInHTTPHeader"`
   106  	SessionNameInHTTPHeader string        `json:"SessionNameInHTTPHeader"`
   107  	EnableSidInURLQuery     bool          `json:"EnableSidInURLQuery"`
   108  	SessionIDPrefix         string        `json:"sessionIDPrefix"`
   109  	CookieSameSite          http.SameSite `json:"cookieSameSite"`
   110  }
   111  
   112  // Manager contains Provider and its configuration.
   113  type Manager struct {
   114  	provider Provider
   115  	config   *ManagerConfig
   116  }
   117  
   118  // NewManager Create new Manager with provider name and json config string.
   119  // provider name:
   120  // 1. cookie
   121  // 2. file
   122  // 3. memory
   123  // 4. redis
   124  // 5. mysql
   125  // json config:
   126  // 1. is https  default false
   127  // 2. hashfunc  default sha1
   128  // 3. hashkey default beegosessionkey
   129  // 4. maxage default is none
   130  func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) {
   131  	provider, ok := provides[provideName]
   132  	if !ok {
   133  		return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
   134  	}
   135  
   136  	if cf.Maxlifetime == 0 {
   137  		cf.Maxlifetime = cf.Gclifetime
   138  	}
   139  
   140  	if cf.EnableSidInHTTPHeader {
   141  		if cf.SessionNameInHTTPHeader == "" {
   142  			panic(errors.New("SessionNameInHTTPHeader is empty"))
   143  		}
   144  
   145  		strMimeHeader := textproto.CanonicalMIMEHeaderKey(cf.SessionNameInHTTPHeader)
   146  		if cf.SessionNameInHTTPHeader != strMimeHeader {
   147  			strErrMsg := "SessionNameInHTTPHeader (" + cf.SessionNameInHTTPHeader + ") has the wrong format, it should be like this : " + strMimeHeader
   148  			panic(errors.New(strErrMsg))
   149  		}
   150  	}
   151  
   152  	err := provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	if cf.SessionIDLength == 0 {
   158  		cf.SessionIDLength = 16
   159  	}
   160  
   161  	return &Manager{
   162  		provider,
   163  		cf,
   164  	}, nil
   165  }
   166  
   167  // GetProvider return current manager's provider
   168  func (manager *Manager) GetProvider() Provider {
   169  	return manager.provider
   170  }
   171  
   172  // getSid retrieves session identifier from HTTP Request.
   173  // First try to retrieve id by reading from cookie, session cookie name is configurable,
   174  // if not exist, then retrieve id from querying parameters.
   175  //
   176  // error is not nil when there is anything wrong.
   177  // sid is empty when need to generate a new session id
   178  // otherwise return an valid session id.
   179  func (manager *Manager) getSid(r *http.Request) (string, error) {
   180  	cookie, errs := r.Cookie(manager.config.CookieName)
   181  	if errs != nil || cookie.Value == "" {
   182  		var sid string
   183  		if manager.config.EnableSidInURLQuery {
   184  			errs := r.ParseForm()
   185  			if errs != nil {
   186  				return "", errs
   187  			}
   188  
   189  			sid = r.FormValue(manager.config.CookieName)
   190  		}
   191  
   192  		// if not found in Cookie / param, then read it from request headers
   193  		if manager.config.EnableSidInHTTPHeader && sid == "" {
   194  			sids, isFound := r.Header[manager.config.SessionNameInHTTPHeader]
   195  			if isFound && len(sids) != 0 {
   196  				return sids[0], nil
   197  			}
   198  		}
   199  
   200  		return sid, nil
   201  	}
   202  
   203  	// HTTP Request contains cookie for sessionid info.
   204  	return url.QueryUnescape(cookie.Value)
   205  }
   206  
   207  // SessionStart generate or read the session id from http request.
   208  // if session id exists, return SessionStore with this id.
   209  func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Store, err error) {
   210  	sid, errs := manager.getSid(r)
   211  	if errs != nil {
   212  		return nil, errs
   213  	}
   214  
   215  	if sid != "" && manager.provider.SessionExist(sid) {
   216  		return manager.provider.SessionRead(sid)
   217  	}
   218  
   219  	// Generate a new session
   220  	sid, errs = manager.sessionID()
   221  	if errs != nil {
   222  		return nil, errs
   223  	}
   224  
   225  	session, err = manager.provider.SessionRead(sid)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	cookie := &http.Cookie{
   230  		Name:     manager.config.CookieName,
   231  		Value:    url.QueryEscape(sid),
   232  		Path:     "/",
   233  		HttpOnly: !manager.config.DisableHTTPOnly,
   234  		Secure:   manager.isSecure(r),
   235  		Domain:   manager.config.Domain,
   236  		SameSite: manager.config.CookieSameSite,
   237  	}
   238  	if manager.config.CookieLifeTime > 0 {
   239  		cookie.MaxAge = manager.config.CookieLifeTime
   240  		cookie.Expires = time.Now().Add(time.Duration(manager.config.CookieLifeTime) * time.Second)
   241  	}
   242  	if manager.config.EnableSetCookie {
   243  		http.SetCookie(w, cookie)
   244  	}
   245  	r.AddCookie(cookie)
   246  
   247  	if manager.config.EnableSidInHTTPHeader {
   248  		r.Header.Set(manager.config.SessionNameInHTTPHeader, sid)
   249  		w.Header().Set(manager.config.SessionNameInHTTPHeader, sid)
   250  	}
   251  
   252  	return
   253  }
   254  
   255  // SessionDestroy Destroy session by its id in http request cookie.
   256  func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
   257  	if manager.config.EnableSidInHTTPHeader {
   258  		r.Header.Del(manager.config.SessionNameInHTTPHeader)
   259  		w.Header().Del(manager.config.SessionNameInHTTPHeader)
   260  	}
   261  
   262  	cookie, err := r.Cookie(manager.config.CookieName)
   263  	if err != nil || cookie.Value == "" {
   264  		return
   265  	}
   266  
   267  	sid, _ := url.QueryUnescape(cookie.Value)
   268  	manager.provider.SessionDestroy(sid)
   269  	if manager.config.EnableSetCookie {
   270  		expiration := time.Now()
   271  		cookie = &http.Cookie{Name: manager.config.CookieName,
   272  			Path:     "/",
   273  			HttpOnly: !manager.config.DisableHTTPOnly,
   274  			Expires:  expiration,
   275  			MaxAge:   -1,
   276  			Domain:   manager.config.Domain,
   277  			SameSite: manager.config.CookieSameSite,
   278  		}
   279  
   280  		http.SetCookie(w, cookie)
   281  	}
   282  }
   283  
   284  // GetSessionStore Get SessionStore by its id.
   285  func (manager *Manager) GetSessionStore(sid string) (sessions Store, err error) {
   286  	sessions, err = manager.provider.SessionRead(sid)
   287  	return
   288  }
   289  
   290  // GC Start session gc process.
   291  // it can do gc in times after gc lifetime.
   292  func (manager *Manager) GC() {
   293  	manager.provider.SessionGC()
   294  	time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() })
   295  }
   296  
   297  // SessionRegenerateID Regenerate a session id for this SessionStore who's id is saving in http request.
   298  func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Request) (session Store) {
   299  	sid, err := manager.sessionID()
   300  	if err != nil {
   301  		return
   302  	}
   303  	cookie, err := r.Cookie(manager.config.CookieName)
   304  	if err != nil || cookie.Value == "" {
   305  		//delete old cookie
   306  		session, _ = manager.provider.SessionRead(sid)
   307  		cookie = &http.Cookie{Name: manager.config.CookieName,
   308  			Value:    url.QueryEscape(sid),
   309  			Path:     "/",
   310  			HttpOnly: !manager.config.DisableHTTPOnly,
   311  			Secure:   manager.isSecure(r),
   312  			Domain:   manager.config.Domain,
   313  			SameSite: manager.config.CookieSameSite,
   314  		}
   315  	} else {
   316  		oldsid, _ := url.QueryUnescape(cookie.Value)
   317  		session, _ = manager.provider.SessionRegenerate(oldsid, sid)
   318  		cookie.Value = url.QueryEscape(sid)
   319  		cookie.HttpOnly = true
   320  		cookie.Path = "/"
   321  	}
   322  	if manager.config.CookieLifeTime > 0 {
   323  		cookie.MaxAge = manager.config.CookieLifeTime
   324  		cookie.Expires = time.Now().Add(time.Duration(manager.config.CookieLifeTime) * time.Second)
   325  	}
   326  	if manager.config.EnableSetCookie {
   327  		http.SetCookie(w, cookie)
   328  	}
   329  	r.AddCookie(cookie)
   330  
   331  	if manager.config.EnableSidInHTTPHeader {
   332  		r.Header.Set(manager.config.SessionNameInHTTPHeader, sid)
   333  		w.Header().Set(manager.config.SessionNameInHTTPHeader, sid)
   334  	}
   335  
   336  	return
   337  }
   338  
   339  // GetActiveSession Get all active sessions count number.
   340  func (manager *Manager) GetActiveSession() int {
   341  	return manager.provider.SessionAll()
   342  }
   343  
   344  // SetSecure Set cookie with https.
   345  func (manager *Manager) SetSecure(secure bool) {
   346  	manager.config.Secure = secure
   347  }
   348  
   349  func (manager *Manager) sessionID() (string, error) {
   350  	b := make([]byte, manager.config.SessionIDLength)
   351  	n, err := rand.Read(b)
   352  	if n != len(b) || err != nil {
   353  		return "", fmt.Errorf("Could not successfully read from the system CSPRNG")
   354  	}
   355  	return manager.config.SessionIDPrefix + hex.EncodeToString(b), nil
   356  }
   357  
   358  // Set cookie with https.
   359  func (manager *Manager) isSecure(req *http.Request) bool {
   360  	if !manager.config.Secure {
   361  		return false
   362  	}
   363  	if req.URL.Scheme != "" {
   364  		return req.URL.Scheme == "https"
   365  	}
   366  	if req.TLS == nil {
   367  		return false
   368  	}
   369  	return true
   370  }
   371  
   372  // Log implement the log.Logger
   373  type Log struct {
   374  	*log.Logger
   375  }
   376  
   377  // NewSessionLog set io.Writer to create a Logger for session.
   378  func NewSessionLog(out io.Writer) *Log {
   379  	sl := new(Log)
   380  	sl.Logger = log.New(out, "[SESSION]", 1e9)
   381  	return sl
   382  }