mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
0d4b0ecd43
I already made a test framework integration but I will commit this when I finish with the basic tests, we are going to final v3
172 lines
5.6 KiB
Go
172 lines
5.6 KiB
Go
package sessions
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/kataras/iris/config"
|
|
"github.com/kataras/iris/context"
|
|
"github.com/kataras/iris/sessions/store"
|
|
"github.com/kataras/iris/utils"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
type (
|
|
// IManager is the interface which Manager should implement
|
|
IManager interface {
|
|
Start(context.IContext) store.IStore
|
|
Destroy(context.IContext)
|
|
GC()
|
|
}
|
|
// Manager implements the IManager interface
|
|
// contains the cookie's name, the provider and a duration for GC and cookie life expire
|
|
Manager struct {
|
|
config *config.Sessions
|
|
provider IProvider
|
|
mu sync.Mutex
|
|
}
|
|
)
|
|
|
|
var _ IManager = &Manager{}
|
|
|
|
var (
|
|
continueOnError = true
|
|
providers = make(map[string]IProvider)
|
|
)
|
|
|
|
// newManager creates & returns a new Manager
|
|
func newManager(c config.Sessions) (*Manager, error) {
|
|
provider, found := providers[c.Provider]
|
|
if !found {
|
|
return nil, ErrProviderNotFound.Format(c.Provider)
|
|
}
|
|
if c.DecodeCookie {
|
|
c.Cookie = base64.URLEncoding.EncodeToString([]byte(c.Cookie)) // change the cookie's name/key to a more safe(?)
|
|
// get the real value for your tests by:
|
|
//sessIdKey := url.QueryEscape(base64.URLEncoding.EncodeToString([]byte(iris.Config.Sessions.Cookie)))
|
|
}
|
|
|
|
manager := &Manager{}
|
|
manager.config = &c
|
|
manager.provider = provider
|
|
return manager, nil
|
|
}
|
|
|
|
// Register registers a provider
|
|
func Register(provider IProvider) {
|
|
if provider == nil {
|
|
ErrProviderRegister.Panic()
|
|
}
|
|
providerName := provider.Name()
|
|
|
|
if _, exists := providers[providerName]; exists {
|
|
if !continueOnError {
|
|
ErrProviderAlreadyExists.Panicf(providerName)
|
|
} else {
|
|
// do nothing it's a map it will overrides the existing provider.
|
|
}
|
|
}
|
|
|
|
providers[providerName] = provider
|
|
}
|
|
|
|
// Manager implementation
|
|
|
|
func (m *Manager) generateSessionID() string {
|
|
return base64.URLEncoding.EncodeToString(utils.Random(32))
|
|
}
|
|
|
|
var dotB = byte('.')
|
|
|
|
// Start starts the session
|
|
func (m *Manager) Start(ctx context.IContext) store.IStore {
|
|
|
|
m.mu.Lock()
|
|
var store store.IStore
|
|
requestCtx := ctx.GetRequestCtx()
|
|
cookieValue := string(requestCtx.Request.Header.Cookie(m.config.Cookie))
|
|
|
|
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
|
|
sid := m.generateSessionID()
|
|
store, _ = m.provider.Init(sid)
|
|
cookie := fasthttp.AcquireCookie()
|
|
// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
|
|
cookie.SetKey(m.config.Cookie)
|
|
cookie.SetValue(sid)
|
|
cookie.SetPath("/")
|
|
if !m.config.DisableSubdomainPersistence {
|
|
requestDomain := ctx.HostString()
|
|
if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
|
|
requestDomain = requestDomain[0:portIdx]
|
|
}
|
|
|
|
if requestDomain == "0.0.0.0" || requestDomain == "127.0.0.1" {
|
|
// for these type of hosts, we can't allow subdomains persistance,
|
|
// the web browser doesn't understand the mysubdomain.0.0.0.0 and mysubdomain.127.0.0.1 as scorrectly ubdomains because of the many dots
|
|
// so don't set a domain here
|
|
|
|
} else if strings.Count(requestDomain, ".") > 0 { // there is a problem with .localhost setted as the domain, so we check that first
|
|
|
|
// RFC2109, we allow level 1 subdomains, but no further
|
|
// if we have localhost.com , we want the localhost.com.
|
|
// so if we have something like: mysubdomain.localhost.com we want the localhost here
|
|
// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
|
|
// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
|
|
if dotIdx := strings.LastIndexByte(requestDomain, dotB); dotIdx > 0 {
|
|
// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
|
|
s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
|
if secondDotIdx := strings.LastIndexByte(s, dotB); secondDotIdx > 0 {
|
|
//is mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
|
s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
|
|
}
|
|
// replace the s with the requestDomain before the domain's siffux
|
|
subdomainSuff := strings.LastIndexByte(requestDomain, dotB)
|
|
if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
|
|
requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
|
|
}
|
|
}
|
|
// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
|
|
cookie.SetDomain("." + requestDomain) // . to allow persistance
|
|
}
|
|
|
|
}
|
|
cookie.SetHTTPOnly(true)
|
|
cookie.SetExpire(m.config.Expires)
|
|
requestCtx.Response.Header.SetCookie(cookie)
|
|
fasthttp.ReleaseCookie(cookie)
|
|
} else {
|
|
store, _ = m.provider.Read(cookieValue)
|
|
}
|
|
|
|
m.mu.Unlock()
|
|
return store
|
|
}
|
|
|
|
// Destroy kills the session and remove the associated cookie
|
|
func (m *Manager) Destroy(ctx context.IContext) {
|
|
cookieValue := string(ctx.GetRequestCtx().Request.Header.Cookie(m.config.Cookie))
|
|
if cookieValue == "" { // nothing to destroy
|
|
return
|
|
}
|
|
|
|
m.mu.Lock()
|
|
m.provider.Destroy(cookieValue)
|
|
ctx.RemoveCookie(m.config.Cookie)
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
// GC tick-tock for the store cleanup
|
|
// it's a blocking function, so run it with go routine, it's totally safe
|
|
func (m *Manager) GC() {
|
|
m.mu.Lock()
|
|
|
|
m.provider.GC(m.config.GcDuration)
|
|
// set a timer for the next GC
|
|
time.AfterFunc(m.config.GcDuration, func() {
|
|
m.GC()
|
|
}) // or m.expire.Unix() if Nanosecond() doesn't works here
|
|
m.mu.Unlock()
|
|
}
|