feat: support customize session timeout (#17767)

Add configuration session_timeout for API, then user can customize the
timeout from system config page or API. The timeout is 60 minutes by
default.

Signed-off-by: chlins <chenyuzh@vmware.com>

Signed-off-by: chlins <chenyuzh@vmware.com>
This commit is contained in:
Chlins Zhang 2022-11-15 11:30:01 +08:00 committed by GitHub
parent cf036df68b
commit 9c9aa58d6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 46 additions and 6 deletions

View File

@ -8631,6 +8631,9 @@ definitions:
type: integer type: integer
description: 'The offset in seconds of UTC 0 o''clock, only valid when the policy type is "daily"' description: 'The offset in seconds of UTC 0 o''clock, only valid when the policy type is "daily"'
description: 'The parameters of the policy, the values are dependent on the type of the policy.' description: 'The parameters of the policy, the values are dependent on the type of the policy.'
session_timeout:
$ref: '#/definitions/IntegerConfigItem'
description: The session timeout in minutes
Configurations: Configurations:
type: object type: object
properties: properties:
@ -8883,7 +8886,12 @@ definitions:
type: boolean type: boolean
description: Skip audit log database description: Skip audit log database
x-omitempty: true x-omitempty: true
x-isnullable: true x-isnullable: true
session_timeout:
type: integer
description: The session timeout for harbor, in minutes.
x-omitempty: true
x-isnullable: true
StringConfigItem: StringConfigItem:
type: object type: object
properties: properties:

View File

@ -218,4 +218,7 @@ const (
SkipAuditLogDatabase = "skip_audit_log_database" SkipAuditLogDatabase = "skip_audit_log_database"
// MaxAuditRetentionHour allowed in audit log purge // MaxAuditRetentionHour allowed in audit log purge
MaxAuditRetentionHour = 240000 MaxAuditRetentionHour = 240000
// SessionTimeout defines the web session timeout
SessionTimeout = "session_timeout"
) )

View File

@ -26,6 +26,7 @@ import (
"github.com/goharbor/harbor/src/lib/cache" "github.com/goharbor/harbor/src/lib/cache"
"github.com/goharbor/harbor/src/lib/cache/redis" "github.com/goharbor/harbor/src/lib/cache/redis"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
) )
@ -91,8 +92,10 @@ func (rs *Store) SessionRelease(w http.ResponseWriter) {
return return
} }
ctx := context.TODO()
maxlifetime := time.Duration(systemSessionTimeout(ctx, rs.maxlifetime))
if rdb, ok := rs.c.(*redis.Cache); ok { if rdb, ok := rs.c.(*redis.Cache); ok {
cmd := rdb.Client.Set(context.TODO(), rs.sid, string(b), time.Duration(rs.maxlifetime)) cmd := rdb.Client.Set(ctx, rs.sid, string(b), maxlifetime)
if cmd.Err() != nil { if cmd.Err() != nil {
log.Debugf("release session error: %v", err) log.Debugf("release session error: %v", err)
} }
@ -136,8 +139,9 @@ func (rp *Provider) SessionExist(sid string) bool {
// SessionRegenerate generate new sid for redis session // SessionRegenerate generate new sid for redis session
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
ctx := context.TODO() ctx := context.TODO()
maxlifetime := time.Duration(systemSessionTimeout(ctx, rp.maxlifetime))
if !rp.SessionExist(oldsid) { if !rp.SessionExist(oldsid) {
err := rp.c.Save(ctx, sid, "", time.Duration(rp.maxlifetime)) err := rp.c.Save(ctx, sid, "", maxlifetime)
if err != nil { if err != nil {
log.Debugf("failed to save sid=%s, where oldsid=%s, error: %s", sid, oldsid, err) log.Debugf("failed to save sid=%s, where oldsid=%s, error: %s", sid, oldsid, err)
} }
@ -145,7 +149,7 @@ func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error)
if rdb, ok := rp.c.(*redis.Cache); ok { if rdb, ok := rp.c.(*redis.Cache); ok {
// redis has rename command // redis has rename command
rdb.Rename(ctx, oldsid, sid) rdb.Rename(ctx, oldsid, sid)
rdb.Expire(ctx, sid, time.Duration(rp.maxlifetime)) rdb.Expire(ctx, sid, maxlifetime)
} else { } else {
kv := make(map[interface{}]interface{}) kv := make(map[interface{}]interface{})
err := rp.c.Fetch(ctx, sid, &kv) err := rp.c.Fetch(ctx, sid, &kv)
@ -157,7 +161,7 @@ func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error)
if err != nil { if err != nil {
log.Debugf("failed to delete oldsid=%s, error: %s", oldsid, err) log.Debugf("failed to delete oldsid=%s, error: %s", oldsid, err)
} }
err = rp.c.Save(ctx, sid, kv) err = rp.c.Save(ctx, sid, kv, maxlifetime)
if err != nil { if err != nil {
log.Debugf("failed to save sid=%s, error: %s", sid, err) log.Debugf("failed to save sid=%s, error: %s", sid, err)
} }
@ -181,6 +185,18 @@ func (rp *Provider) SessionAll() int {
return 0 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() { func init() {
session.Register(HarborProviderName, harborpder) session.Register(HarborProviderName, harborpder)
} }

View File

@ -19,6 +19,10 @@ import (
"github.com/beego/beego/session" "github.com/beego/beego/session"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/goharbor/harbor/src/lib/config"
_ "github.com/goharbor/harbor/src/pkg/config/db"
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
) )
type sessionTestSuite struct { type sessionTestSuite struct {
@ -27,7 +31,9 @@ type sessionTestSuite struct {
provider session.Provider provider session.Provider
} }
func (s *sessionTestSuite) SetupTest() { func (s *sessionTestSuite) SetupSuite() {
config.Init()
var err error var err error
s.provider, err = session.GetProvider("harbor") s.provider, err = session.GetProvider("harbor")
s.NoError(err, "should get harbor provider") s.NoError(err, "should get harbor provider")

View File

@ -183,5 +183,7 @@ var (
{Name: common.AuditLogForwardEndpoint, Scope: UserScope, Group: BasicGroup, EnvKey: "AUDIT_LOG_FORWARD_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The endpoint to forward the audit log.`}, {Name: common.AuditLogForwardEndpoint, Scope: UserScope, Group: BasicGroup, EnvKey: "AUDIT_LOG_FORWARD_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The endpoint to forward the audit log.`},
{Name: common.SkipAuditLogDatabase, Scope: UserScope, Group: BasicGroup, EnvKey: "SKIP_LOG_AUDIT_DATABASE", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `The option to skip audit log in database`}, {Name: common.SkipAuditLogDatabase, Scope: UserScope, Group: BasicGroup, EnvKey: "SKIP_LOG_AUDIT_DATABASE", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `The option to skip audit log in database`},
{Name: common.SessionTimeout, Scope: UserScope, Group: BasicGroup, EnvKey: "SESSION_TIMEOUT", DefaultValue: "60", ItemType: &Int64Type{}, Editable: true, Description: `The session timeout in minutes`},
} }
) )

View File

@ -84,6 +84,11 @@ func LDAPGroupConf(ctx context.Context) (*cfgModels.GroupConf, error) {
}, nil }, nil
} }
// SessionTimeout returns the session timeout for web (in minute).
func SessionTimeout(ctx context.Context) int64 {
return DefaultMgr().Get(ctx, common.SessionTimeout).GetInt64()
}
// TokenExpiration returns the token expiration time (in minute) // TokenExpiration returns the token expiration time (in minute)
func TokenExpiration(ctx context.Context) (int, error) { func TokenExpiration(ctx context.Context) (int, error) {
return DefaultMgr().Get(ctx, common.TokenExpiration).GetInt(), nil return DefaultMgr().Get(ctx, common.TokenExpiration).GetInt(), nil