2016-11-04 01:16:01 +03:00
// Copyright 2013 Beego Authors
// Copyright 2014 The Macaron Authors
2020-09-28 00:09:46 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2016-11-04 01:16:01 +03:00
//
// 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
import (
"fmt"
"sync"
"time"
2020-09-28 00:09:46 +03:00
"code.gitea.io/gitea/modules/nosql"
2021-01-26 18:36:53 +03:00
"gitea.com/go-chi/session"
2020-09-28 00:09:46 +03:00
"github.com/go-redis/redis/v7"
2016-11-04 01:16:01 +03:00
)
// RedisStore represents a redis session store implementation.
type RedisStore struct {
2020-09-28 00:09:46 +03:00
c redis . UniversalClient
2016-11-04 01:16:01 +03:00
prefix , sid string
duration time . Duration
lock sync . RWMutex
data map [ interface { } ] interface { }
}
// NewRedisStore creates and returns a redis session store.
2020-09-28 00:09:46 +03:00
func NewRedisStore ( c redis . UniversalClient , prefix , sid string , dur time . Duration , kv map [ interface { } ] interface { } ) * RedisStore {
2016-11-04 01:16:01 +03:00
return & RedisStore {
c : c ,
prefix : prefix ,
sid : sid ,
duration : dur ,
data : kv ,
}
}
// Set sets value to given key in session.
func ( s * RedisStore ) 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 * RedisStore ) Get ( key interface { } ) interface { } {
s . lock . RLock ( )
defer s . lock . RUnlock ( )
return s . data [ key ]
}
// Delete delete a key from session.
func ( s * RedisStore ) Delete ( key interface { } ) error {
s . lock . Lock ( )
defer s . lock . Unlock ( )
delete ( s . data , key )
return nil
}
// ID returns current session ID.
func ( s * RedisStore ) ID ( ) string {
return s . sid
}
// Release releases resource and save data to provider.
func ( s * RedisStore ) Release ( ) error {
2019-02-05 19:52:51 +03:00
// Skip encoding if the data is empty
if len ( s . data ) == 0 {
return nil
}
2016-11-04 01:16:01 +03:00
data , err := session . EncodeGob ( s . data )
if err != nil {
return err
}
2019-08-23 19:40:30 +03:00
return s . c . Set ( s . prefix + s . sid , string ( data ) , s . duration ) . Err ( )
2016-11-04 01:16:01 +03:00
}
// Flush deletes all session data.
func ( s * RedisStore ) Flush ( ) error {
s . lock . Lock ( )
defer s . lock . Unlock ( )
s . data = make ( map [ interface { } ] interface { } )
return nil
}
// RedisProvider represents a redis session provider implementation.
type RedisProvider struct {
2020-09-28 00:09:46 +03:00
c redis . UniversalClient
2016-11-04 01:16:01 +03:00
duration time . Duration
prefix string
}
// Init initializes redis session provider.
// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session;
func ( p * RedisProvider ) Init ( maxlifetime int64 , configs string ) ( err error ) {
p . duration , err = time . ParseDuration ( fmt . Sprintf ( "%ds" , maxlifetime ) )
if err != nil {
return err
}
2020-09-28 00:09:46 +03:00
uri := nosql . ToRedisURI ( configs )
2016-11-04 01:16:01 +03:00
2020-09-28 00:09:46 +03:00
for k , v := range uri . Query ( ) {
2016-11-04 01:16:01 +03:00
switch k {
case "prefix" :
2020-09-28 00:09:46 +03:00
p . prefix = v [ 0 ]
2016-11-04 01:16:01 +03:00
}
}
2020-09-28 00:09:46 +03:00
p . c = nosql . GetManager ( ) . GetRedisClient ( uri . String ( ) )
2016-11-04 01:16:01 +03:00
return p . c . Ping ( ) . Err ( )
}
// Read returns raw session store by session ID.
func ( p * RedisProvider ) Read ( sid string ) ( session . RawStore , error ) {
psid := p . prefix + sid
if ! p . Exist ( sid ) {
2019-08-23 19:40:30 +03:00
if err := p . c . Set ( psid , "" , p . duration ) . Err ( ) ; err != nil {
2016-11-04 01:16:01 +03:00
return nil , err
}
}
var kv map [ interface { } ] interface { }
kvs , err := p . c . Get ( psid ) . Result ( )
if err != nil {
return nil , err
}
if len ( kvs ) == 0 {
kv = make ( map [ interface { } ] interface { } )
} else {
kv , err = session . DecodeGob ( [ ] byte ( kvs ) )
if err != nil {
return nil , err
}
}
return NewRedisStore ( p . c , p . prefix , sid , p . duration , kv ) , nil
}
// Exist returns true if session with given ID exists.
func ( p * RedisProvider ) Exist ( sid string ) bool {
2019-08-23 19:40:30 +03:00
v , err := p . c . Exists ( p . prefix + sid ) . Result ( )
return err == nil && v == 1
2016-11-04 01:16:01 +03:00
}
2019-08-23 19:40:30 +03:00
// Destroy deletes a session by session ID.
func ( p * RedisProvider ) Destroy ( sid string ) error {
2016-11-04 01:16:01 +03:00
return p . c . Del ( p . prefix + sid ) . Err ( )
}
// Regenerate regenerates a session store from old session ID to new one.
func ( p * RedisProvider ) Regenerate ( oldsid , sid string ) ( _ session . RawStore , err error ) {
poldsid := p . prefix + oldsid
psid := p . prefix + sid
if p . Exist ( sid ) {
return nil , fmt . Errorf ( "new sid '%s' already exists" , sid )
} else if ! p . Exist ( oldsid ) {
// Make a fake old session.
2019-08-23 19:40:30 +03:00
if err = p . c . Set ( poldsid , "" , p . duration ) . Err ( ) ; err != nil {
2016-11-04 01:16:01 +03:00
return nil , err
}
}
if err = p . c . Rename ( poldsid , psid ) . Err ( ) ; err != nil {
return nil , err
}
var kv map [ interface { } ] interface { }
kvs , err := p . c . Get ( psid ) . Result ( )
if err != nil {
return nil , err
}
if len ( kvs ) == 0 {
kv = make ( map [ interface { } ] interface { } )
} else {
kv , err = session . DecodeGob ( [ ] byte ( kvs ) )
if err != nil {
return nil , err
}
}
return NewRedisStore ( p . c , p . prefix , sid , p . duration , kv ) , nil
}
// Count counts and returns number of sessions.
func ( p * RedisProvider ) Count ( ) int {
2020-09-28 00:09:46 +03:00
return int ( p . c . DBSize ( ) . Val ( ) )
2016-11-04 01:16:01 +03:00
}
// GC calls GC to clean expired sessions.
2020-09-28 00:09:46 +03:00
func ( * RedisProvider ) GC ( ) { }
2016-11-04 01:16:01 +03:00
func init ( ) {
session . Register ( "redis" , & RedisProvider { } )
}