2019-04-20 09:44:50 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package session
import (
"encoding/json"
"fmt"
"sync"
2019-08-23 19:40:30 +03:00
"gitea.com/macaron/session"
couchbase "gitea.com/macaron/session/couchbase"
memcache "gitea.com/macaron/session/memcache"
mysql "gitea.com/macaron/session/mysql"
nodb "gitea.com/macaron/session/nodb"
postgres "gitea.com/macaron/session/postgres"
2019-04-20 09:44:50 +03:00
)
// VirtualSessionProvider represents a shadowed session provider implementation.
type VirtualSessionProvider struct {
2019-06-12 22:41:28 +03:00
lock sync . RWMutex
provider session . Provider
2019-04-20 09:44:50 +03:00
}
// Init initializes the cookie session provider with given root path.
func ( o * VirtualSessionProvider ) Init ( gclifetime int64 , config string ) error {
var opts session . Options
if err := json . Unmarshal ( [ ] byte ( config ) , & opts ) ; err != nil {
return err
}
// Note that these options are unprepared so we can't just use NewManager here.
// Nor can we access the provider map in session.
// So we will just have to do this by hand.
// This is only slightly more wrong than modules/setting/session.go:23
switch opts . Provider {
case "memory" :
2020-09-03 00:10:35 +03:00
o . provider = & session . MemProvider { }
2019-04-20 09:44:50 +03:00
case "file" :
o . provider = & session . FileProvider { }
case "redis" :
2020-09-28 00:09:46 +03:00
o . provider = & RedisProvider { }
2019-04-20 09:44:50 +03:00
case "mysql" :
o . provider = & mysql . MysqlProvider { }
case "postgres" :
o . provider = & postgres . PostgresProvider { }
case "couchbase" :
o . provider = & couchbase . CouchbaseProvider { }
case "memcache" :
o . provider = & memcache . MemcacheProvider { }
case "nodb" :
o . provider = & nodb . NodbProvider { }
default :
return fmt . Errorf ( "VirtualSessionProvider: Unknown Provider: %s" , opts . Provider )
}
return o . provider . Init ( gclifetime , opts . ProviderConfig )
}
// Read returns raw session store by session ID.
func ( o * VirtualSessionProvider ) Read ( sid string ) ( session . RawStore , error ) {
o . lock . RLock ( )
defer o . lock . RUnlock ( )
if o . provider . Exist ( sid ) {
return o . provider . Read ( sid )
}
kv := make ( map [ interface { } ] interface { } )
kv [ "_old_uid" ] = "0"
return NewVirtualStore ( o , sid , kv ) , nil
}
// Exist returns true if session with given ID exists.
func ( o * VirtualSessionProvider ) Exist ( sid string ) bool {
return true
}
2019-08-23 19:40:30 +03:00
// Destroy deletes a session by session ID.
func ( o * VirtualSessionProvider ) Destroy ( sid string ) error {
2019-04-20 09:44:50 +03:00
o . lock . Lock ( )
defer o . lock . Unlock ( )
2019-08-23 19:40:30 +03:00
return o . provider . Destroy ( sid )
2019-04-20 09:44:50 +03:00
}
// Regenerate regenerates a session store from old session ID to new one.
func ( o * VirtualSessionProvider ) Regenerate ( oldsid , sid string ) ( session . RawStore , error ) {
o . lock . Lock ( )
defer o . lock . Unlock ( )
return o . provider . Regenerate ( oldsid , sid )
}
// Count counts and returns number of sessions.
func ( o * VirtualSessionProvider ) Count ( ) int {
o . lock . RLock ( )
defer o . lock . RUnlock ( )
return o . provider . Count ( )
}
// GC calls GC to clean expired sessions.
func ( o * VirtualSessionProvider ) GC ( ) {
o . provider . GC ( )
}
func init ( ) {
session . Register ( "VirtualSession" , & VirtualSessionProvider { } )
}
// VirtualStore represents a virtual session store implementation.
type VirtualStore struct {
2020-05-24 02:59:04 +03:00
p * VirtualSessionProvider
sid string
lock sync . RWMutex
data map [ interface { } ] interface { }
released bool
2019-04-20 09:44:50 +03:00
}
// NewVirtualStore creates and returns a virtual session store.
func NewVirtualStore ( p * VirtualSessionProvider , sid string , kv map [ interface { } ] interface { } ) * VirtualStore {
return & VirtualStore {
p : p ,
sid : sid ,
data : kv ,
}
}
// Set sets value to given key in session.
func ( s * VirtualStore ) Set ( key , val interface { } ) error {
s . lock . Lock ( )
defer s . lock . Unlock ( )
s . data [ key ] = val
return nil
}
// Get gets value by given key in session.
func ( s * VirtualStore ) Get ( key interface { } ) interface { } {
s . lock . RLock ( )
defer s . lock . RUnlock ( )
return s . data [ key ]
}
// Delete delete a key from session.
func ( s * VirtualStore ) Delete ( key interface { } ) error {
s . lock . Lock ( )
defer s . lock . Unlock ( )
delete ( s . data , key )
return nil
}
// ID returns current session ID.
func ( s * VirtualStore ) ID ( ) string {
return s . sid
}
// Release releases resource and save data to provider.
func ( s * VirtualStore ) Release ( ) error {
s . lock . Lock ( )
defer s . lock . Unlock ( )
// Now need to lock the provider
s . p . lock . Lock ( )
defer s . p . lock . Unlock ( )
if oldUID , ok := s . data [ "_old_uid" ] ; ( ok && ( oldUID != "0" || len ( s . data ) > 1 ) ) || ( ! ok && len ( s . data ) > 0 ) {
// Now ensure that we don't exist!
realProvider := s . p . provider
2020-05-24 02:59:04 +03:00
if ! s . released && realProvider . Exist ( s . sid ) {
2019-04-20 09:44:50 +03:00
// This is an error!
return fmt . Errorf ( "new sid '%s' already exists" , s . sid )
}
realStore , err := realProvider . Read ( s . sid )
if err != nil {
return err
}
2020-05-24 02:59:04 +03:00
if err := realStore . Flush ( ) ; err != nil {
return err
}
2019-04-20 09:44:50 +03:00
for key , value := range s . data {
if err := realStore . Set ( key , value ) ; err != nil {
return err
}
}
2020-05-24 02:59:04 +03:00
err = realStore . Release ( )
if err == nil {
s . released = true
}
return err
2019-04-20 09:44:50 +03:00
}
return nil
}
// Flush deletes all session data.
func ( s * VirtualStore ) Flush ( ) error {
s . lock . Lock ( )
defer s . lock . Unlock ( )
s . data = make ( map [ interface { } ] interface { } )
return nil
}