// Copyright Project Harbor Authors // // 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 ( "context" "net/http" "strings" "sync" "time" "github.com/beego/beego/session" goredis "github.com/go-redis/redis/v8" "github.com/goharbor/harbor/src/lib/cache" "github.com/goharbor/harbor/src/lib/cache/redis" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/log" ) const ( // HarborProviderName is the harbor session provider name HarborProviderName = "harbor" ) var harborpder = &Provider{} // Store redis session store type Store struct { c cache.Cache sid string lock sync.RWMutex values map[interface{}]interface{} maxlifetime int64 } // Set value in redis session func (rs *Store) Set(key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value return nil } // Get value in redis session func (rs *Store) Get(key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { return v } return nil } // Delete value in redis session func (rs *Store) Delete(key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) return nil } // Flush clear all values in redis session func (rs *Store) Flush() error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) return nil } // SessionID get redis session id func (rs *Store) SessionID() string { return rs.sid } // SessionRelease save session values to redis func (rs *Store) SessionRelease(w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return } ctx := context.TODO() maxlifetime := time.Duration(systemSessionTimeout(ctx, rs.maxlifetime)) if rdb, ok := rs.c.(*redis.Cache); ok { cmd := rdb.Client.Set(ctx, rs.sid, string(b), maxlifetime) if cmd.Err() != nil { log.Debugf("release session error: %v", err) } } } // Provider redis session provider type Provider struct { maxlifetime int64 c cache.Cache } // SessionInit init redis session func (rp *Provider) SessionInit(maxlifetime int64, url string) (err error) { rp.maxlifetime = maxlifetime * int64(time.Second) rp.c, err = redis.New(cache.Options{Address: url, Codec: codec}) if err != nil { return err } return rp.c.Ping(context.TODO()) } // SessionRead read redis session by sid func (rp *Provider) SessionRead(sid string) (session.Store, error) { kv := make(map[interface{}]interface{}) err := rp.c.Fetch(context.TODO(), sid, &kv) if err != nil && !strings.Contains(err.Error(), goredis.Nil.Error()) { return nil, err } rs := &Store{c: rp.c, sid: sid, values: kv, maxlifetime: rp.maxlifetime} return rs, nil } // SessionExist check redis session exist by sid func (rp *Provider) SessionExist(sid string) bool { return rp.c.Contains(context.TODO(), sid) } // SessionRegenerate generate new sid for redis session func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { ctx := context.TODO() maxlifetime := time.Duration(systemSessionTimeout(ctx, rp.maxlifetime)) if !rp.SessionExist(oldsid) { err := rp.c.Save(ctx, sid, "", maxlifetime) if err != nil { log.Debugf("failed to save sid=%s, where oldsid=%s, error: %s", sid, oldsid, err) } } else { if rdb, ok := rp.c.(*redis.Cache); ok { // redis has rename command rdb.Rename(ctx, oldsid, sid) rdb.Expire(ctx, sid, maxlifetime) } else { kv := make(map[interface{}]interface{}) err := rp.c.Fetch(ctx, sid, &kv) if err != nil && !strings.Contains(err.Error(), goredis.Nil.Error()) { return nil, err } err = rp.c.Delete(ctx, oldsid) if err != nil { log.Debugf("failed to delete oldsid=%s, error: %s", oldsid, err) } err = rp.c.Save(ctx, sid, kv, maxlifetime) if err != nil { log.Debugf("failed to save sid=%s, error: %s", sid, err) } } } return rp.SessionRead(sid) } // SessionDestroy delete redis session by id func (rp *Provider) SessionDestroy(sid string) error { return rp.c.Delete(context.TODO(), sid) } // SessionGC Implement method, no used. func (rp *Provider) SessionGC() { } // SessionAll return all activeSession func (rp *Provider) SessionAll() int { return 0 } // systemSessionTimeout return the system session timeout set by user. func systemSessionTimeout(ctx context.Context, beegoTimeout int64) int64 { // read from system config if it is meaningful to support change session timeout in runtime for user. // otherwise, use parameters beegoTimeout which set from beego. timeout := beegoTimeout if sysTimeout := config.SessionTimeout(ctx); sysTimeout > 0 { timeout = sysTimeout * int64(time.Minute) } return timeout } func init() { session.Register(HarborProviderName, harborpder) }