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 }