mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 18:00:31 -06:00
Auth: Replace maximum inactive/lifetime settings of days to duration (#27150)
Allows login_maximum_inactive_lifetime_duration and login_maximum_lifetime_duration to be configured using time.Duration-compatible values while retaining backward compatibility. Fixes #17554 Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
parent
f529223455
commit
8d971ab2f2
@ -279,11 +279,11 @@ editors_can_admin = false
|
||||
# Login cookie name
|
||||
login_cookie_name = grafana_session
|
||||
|
||||
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
|
||||
login_maximum_inactive_lifetime_days = 7
|
||||
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation (token_rotation_interval_minutes).
|
||||
login_maximum_inactive_lifetime_duration =
|
||||
|
||||
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
|
||||
login_maximum_lifetime_days = 30
|
||||
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
|
||||
login_maximum_lifetime_duration =
|
||||
|
||||
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
||||
token_rotation_interval_minutes = 10
|
||||
|
@ -278,11 +278,11 @@
|
||||
# Login cookie name
|
||||
;login_cookie_name = grafana_session
|
||||
|
||||
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
|
||||
;login_maximum_inactive_lifetime_days = 7
|
||||
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation
|
||||
;login_maximum_inactive_lifetime_duration =
|
||||
|
||||
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
|
||||
;login_maximum_lifetime_days = 30
|
||||
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
|
||||
;login_maximum_lifetime_duration =
|
||||
|
||||
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
||||
;token_rotation_interval_minutes = 10
|
||||
|
@ -59,11 +59,13 @@ Example:
|
||||
# Login cookie name
|
||||
login_cookie_name = grafana_session
|
||||
|
||||
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
|
||||
login_maximum_inactive_lifetime_days = 7
|
||||
|
||||
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
|
||||
login_maximum_lifetime_days = 30
|
||||
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation (token_rotation_interval_minutes).
|
||||
login_maximum_inactive_lifetime_duration =
|
||||
|
||||
|
||||
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
|
||||
login_maximum_lifetime_duration =
|
||||
|
||||
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
||||
token_rotation_interval_minutes = 10
|
||||
|
@ -249,7 +249,7 @@ func (hs *HTTPServer) loginUserWithUser(user *models.User, c *models.ReqContext)
|
||||
}
|
||||
|
||||
hs.log.Info("Successful Login", "User", user.Email)
|
||||
middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetimeDays)
|
||||
middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetime)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -261,22 +261,21 @@ func rotateEndOfRequestFunc(ctx *models.ReqContext, authTokenService models.User
|
||||
}
|
||||
|
||||
if rotated {
|
||||
WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetimeDays)
|
||||
WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetimeDays int) {
|
||||
func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetime time.Duration) {
|
||||
if setting.Env == setting.DEV {
|
||||
ctx.Logger.Info("New token", "unhashed token", value)
|
||||
}
|
||||
|
||||
var maxAge int
|
||||
if maxLifetimeDays <= 0 {
|
||||
if maxLifetime <= 0 {
|
||||
maxAge = -1
|
||||
} else {
|
||||
maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour) + time.Hour
|
||||
maxAge = int(maxAgeHours.Seconds())
|
||||
maxAge = int(maxLifetime.Seconds())
|
||||
}
|
||||
|
||||
WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, newCookieOptions)
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/gtime"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy"
|
||||
@ -253,8 +254,7 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour)
|
||||
maxAge := (maxAgeHours + time.Hour).Seconds()
|
||||
maxAge := int(setting.LoginMaxLifetime.Seconds())
|
||||
|
||||
sameSitePolicies := []http.SameSite{
|
||||
http.SameSiteNoneMode,
|
||||
@ -272,7 +272,7 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
Value: "rotated",
|
||||
Path: expectedCookiePath,
|
||||
HttpOnly: true,
|
||||
MaxAge: int(maxAge),
|
||||
MaxAge: maxAge,
|
||||
Secure: setting.CookieSecure,
|
||||
SameSite: sameSitePolicy,
|
||||
}
|
||||
@ -303,7 +303,7 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
Value: "rotated",
|
||||
Path: expectedCookiePath,
|
||||
HttpOnly: true,
|
||||
MaxAge: int(maxAge),
|
||||
MaxAge: maxAge,
|
||||
Secure: setting.CookieSecure,
|
||||
}
|
||||
|
||||
@ -546,7 +546,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
setting.LoginCookieName = "grafana_session"
|
||||
setting.LoginMaxLifetimeDays = 30
|
||||
setting.LoginMaxLifetime, _ = gtime.ParseInterval("30d")
|
||||
|
||||
sc := &scenarioContext{}
|
||||
|
||||
@ -637,7 +637,7 @@ func TestTokenRotationAtEndOfRequest(t *testing.T) {
|
||||
|
||||
func initTokenRotationTest(ctx context.Context) (*models.ReqContext, *httptest.ResponseRecorder, error) {
|
||||
setting.LoginCookieName = "login_token"
|
||||
setting.LoginMaxLifetimeDays = 7
|
||||
setting.LoginMaxLifetime, _ = gtime.ParseInterval("7d")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequestWithContext(ctx, "", "", nil)
|
||||
|
@ -397,13 +397,11 @@ func (s *UserAuthTokenService) GetUserTokens(ctx context.Context, userId int64)
|
||||
}
|
||||
|
||||
func (s *UserAuthTokenService) createdAfterParam() int64 {
|
||||
tokenMaxLifetime := time.Duration(s.Cfg.LoginMaxLifetimeDays) * 24 * time.Hour
|
||||
return getTime().Add(-tokenMaxLifetime).Unix()
|
||||
return getTime().Add(-s.Cfg.LoginMaxLifetime).Unix()
|
||||
}
|
||||
|
||||
func (s *UserAuthTokenService) rotatedAfterParam() int64 {
|
||||
tokenMaxInactiveLifetime := time.Duration(s.Cfg.LoginMaxInactiveLifetimeDays) * 24 * time.Hour
|
||||
return getTime().Add(-tokenMaxInactiveLifetime).Unix()
|
||||
return getTime().Add(-s.Cfg.LoginMaxInactiveLifetime).Unix()
|
||||
}
|
||||
|
||||
func hashToken(token string) string {
|
||||
|
@ -494,13 +494,14 @@ func TestUserAuthToken(t *testing.T) {
|
||||
|
||||
func createTestContext(t *testing.T) *testContext {
|
||||
t.Helper()
|
||||
|
||||
maxInactiveDurationVal, _ := time.ParseDuration("168h")
|
||||
maxLifetimeDurationVal, _ := time.ParseDuration("720h")
|
||||
sqlstore := sqlstore.InitTestDB(t)
|
||||
tokenService := &UserAuthTokenService{
|
||||
SQLStore: sqlstore,
|
||||
Cfg: &setting.Cfg{
|
||||
LoginMaxInactiveLifetimeDays: 7,
|
||||
LoginMaxLifetimeDays: 30,
|
||||
LoginMaxInactiveLifetime: maxInactiveDurationVal,
|
||||
LoginMaxLifetime: maxLifetimeDurationVal,
|
||||
TokenRotationIntervalMinutes: 10,
|
||||
},
|
||||
log: log.New("test-logger"),
|
||||
|
@ -8,11 +8,12 @@ import (
|
||||
)
|
||||
|
||||
func (srv *UserAuthTokenService) Run(ctx context.Context) error {
|
||||
var err error
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
maxInactiveLifetime := time.Duration(srv.Cfg.LoginMaxInactiveLifetimeDays) * 24 * time.Hour
|
||||
maxLifetime := time.Duration(srv.Cfg.LoginMaxLifetimeDays) * 24 * time.Hour
|
||||
maxInactiveLifetime := srv.Cfg.LoginMaxInactiveLifetime
|
||||
maxLifetime := srv.Cfg.LoginMaxLifetime
|
||||
|
||||
err := srv.ServerLockService.LockAndExecute(ctx, "cleanup expired auth tokens", time.Hour*12, func() {
|
||||
err = srv.ServerLockService.LockAndExecute(ctx, "cleanup expired auth tokens", time.Hour*12, func() {
|
||||
if _, err := srv.deleteExpiredTokens(ctx, maxInactiveLifetime, maxLifetime); err != nil {
|
||||
srv.log.Error("An error occurred while deleting expired tokens", "err", err)
|
||||
}
|
||||
@ -24,7 +25,7 @@ func (srv *UserAuthTokenService) Run(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
err := srv.ServerLockService.LockAndExecute(ctx, "cleanup expired auth tokens", time.Hour*12, func() {
|
||||
err = srv.ServerLockService.LockAndExecute(ctx, "cleanup expired auth tokens", time.Hour*12, func() {
|
||||
if _, err := srv.deleteExpiredTokens(ctx, maxInactiveLifetime, maxLifetime); err != nil {
|
||||
srv.log.Error("An error occurred while deleting expired tokens", "err", err)
|
||||
}
|
||||
|
@ -12,8 +12,10 @@ import (
|
||||
func TestUserAuthTokenCleanup(t *testing.T) {
|
||||
Convey("Test user auth token cleanup", t, func() {
|
||||
ctx := createTestContext(t)
|
||||
ctx.tokenService.Cfg.LoginMaxInactiveLifetimeDays = 7
|
||||
ctx.tokenService.Cfg.LoginMaxLifetimeDays = 30
|
||||
maxInactiveLifetime, _ := time.ParseDuration("168h")
|
||||
maxLifetime, _ := time.ParseDuration("720h")
|
||||
ctx.tokenService.Cfg.LoginMaxInactiveLifetime = maxInactiveLifetime
|
||||
ctx.tokenService.Cfg.LoginMaxLifetime = maxLifetime
|
||||
|
||||
insertToken := func(token string, prev string, createdAt, rotatedAt int64) {
|
||||
ut := userAuthToken{AuthToken: token, PrevAuthToken: prev, CreatedAt: createdAt, RotatedAt: rotatedAt, UserAgent: "", ClientIp: ""}
|
||||
@ -27,7 +29,7 @@ func TestUserAuthTokenCleanup(t *testing.T) {
|
||||
}
|
||||
|
||||
Convey("should delete tokens where token rotation age is older than or equal 7 days", func() {
|
||||
from := t.Add(-7 * 24 * time.Hour)
|
||||
from := t.Add(-168 * time.Hour)
|
||||
|
||||
// insert three old tokens that should be deleted
|
||||
for i := 0; i < 3; i++ {
|
||||
@ -40,7 +42,7 @@ func TestUserAuthTokenCleanup(t *testing.T) {
|
||||
insertToken(fmt.Sprintf("newA%d", i), fmt.Sprintf("newB%d", i), from.Unix(), from.Unix())
|
||||
}
|
||||
|
||||
affected, err := ctx.tokenService.deleteExpiredTokens(context.Background(), 7*24*time.Hour, 30*24*time.Hour)
|
||||
affected, err := ctx.tokenService.deleteExpiredTokens(context.Background(), 168*time.Hour, 30*24*time.Hour)
|
||||
So(err, ShouldBeNil)
|
||||
So(affected, ShouldEqual, 3)
|
||||
})
|
||||
|
@ -140,10 +140,10 @@ var (
|
||||
ViewersCanEdit bool
|
||||
|
||||
// Http auth
|
||||
AdminUser string
|
||||
AdminPassword string
|
||||
LoginCookieName string
|
||||
LoginMaxLifetimeDays int
|
||||
AdminUser string
|
||||
AdminPassword string
|
||||
LoginCookieName string
|
||||
LoginMaxLifetime time.Duration
|
||||
|
||||
AnonymousEnabled bool
|
||||
AnonymousOrgName string
|
||||
@ -278,8 +278,8 @@ type Cfg struct {
|
||||
|
||||
// Auth
|
||||
LoginCookieName string
|
||||
LoginMaxInactiveLifetimeDays int
|
||||
LoginMaxLifetimeDays int
|
||||
LoginMaxInactiveLifetime time.Duration
|
||||
LoginMaxLifetime time.Duration
|
||||
TokenRotationIntervalMinutes int
|
||||
|
||||
// OAuth
|
||||
@ -946,15 +946,38 @@ func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAuthSettings(iniFile *ini.File, cfg *Cfg) error {
|
||||
func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
||||
auth := iniFile.Section("auth")
|
||||
|
||||
LoginCookieName = valueAsString(auth, "login_cookie_name", "grafana_session")
|
||||
cfg.LoginCookieName = LoginCookieName
|
||||
cfg.LoginMaxInactiveLifetimeDays = auth.Key("login_maximum_inactive_lifetime_days").MustInt(7)
|
||||
maxInactiveDaysVal := auth.Key("login_maximum_inactive_lifetime_days").MustString("")
|
||||
if maxInactiveDaysVal != "" {
|
||||
maxInactiveDaysVal = fmt.Sprintf("%sd", maxInactiveDaysVal)
|
||||
cfg.Logger.Warn("[Deprecated] the configuration setting 'login_maximum_inactive_lifetime_days' is deprecated, please use 'login_maximum_inactive_lifetime_duration' instead")
|
||||
} else {
|
||||
maxInactiveDaysVal = "7d"
|
||||
}
|
||||
maxInactiveDurationVal := valueAsString(auth, "login_maximum_inactive_lifetime_duration", maxInactiveDaysVal)
|
||||
cfg.LoginMaxInactiveLifetime, err = gtime.ParseInterval(maxInactiveDurationVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxLifetimeDaysVal := auth.Key("login_maximum_lifetime_days").MustString("")
|
||||
if maxLifetimeDaysVal != "" {
|
||||
maxLifetimeDaysVal = fmt.Sprintf("%sd", maxLifetimeDaysVal)
|
||||
cfg.Logger.Warn("[Deprecated] the configuration setting 'login_maximum_lifetime_days' is deprecated, please use 'login_maximum_lifetime_duration' instead")
|
||||
} else {
|
||||
maxLifetimeDaysVal = "7d"
|
||||
}
|
||||
maxLifetimeDurationVal := valueAsString(auth, "login_maximum_lifetime_duration", maxLifetimeDaysVal)
|
||||
cfg.LoginMaxLifetime, err = gtime.ParseInterval(maxLifetimeDurationVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
LoginMaxLifetime = cfg.LoginMaxLifetime
|
||||
|
||||
LoginMaxLifetimeDays = auth.Key("login_maximum_lifetime_days").MustInt(30)
|
||||
cfg.LoginMaxLifetimeDays = LoginMaxLifetimeDays
|
||||
cfg.ApiKeyMaxSecondsToLive = auth.Key("api_key_max_seconds_to_live").MustInt64(-1)
|
||||
|
||||
cfg.TokenRotationIntervalMinutes = auth.Key("token_rotation_interval_minutes").MustInt(10)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -314,3 +315,49 @@ func TestParseAppUrlAndSubUrl(t *testing.T) {
|
||||
require.Equal(t, tc.expectedAppSubURL, appSubURL)
|
||||
}
|
||||
}
|
||||
func TestAuthDurationSettings(t *testing.T) {
|
||||
f := ini.Empty()
|
||||
cfg := NewCfg()
|
||||
sec, err := f.NewSection("auth")
|
||||
require.NoError(t, err)
|
||||
_, err = sec.NewKey("login_maximum_inactive_lifetime_days", "10")
|
||||
require.NoError(t, err)
|
||||
_, err = sec.NewKey("login_maximum_inactive_lifetime_duration", "")
|
||||
require.NoError(t, err)
|
||||
maxInactiveDaysTest, _ := time.ParseDuration("240h")
|
||||
err = readAuthSettings(f, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, maxInactiveDaysTest, cfg.LoginMaxInactiveLifetime)
|
||||
|
||||
f = ini.Empty()
|
||||
sec, err = f.NewSection("auth")
|
||||
require.NoError(t, err)
|
||||
_, err = sec.NewKey("login_maximum_inactive_lifetime_duration", "824h")
|
||||
require.NoError(t, err)
|
||||
maxInactiveDurationTest, _ := time.ParseDuration("824h")
|
||||
err = readAuthSettings(f, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, maxInactiveDurationTest, cfg.LoginMaxInactiveLifetime)
|
||||
|
||||
f = ini.Empty()
|
||||
sec, err = f.NewSection("auth")
|
||||
require.NoError(t, err)
|
||||
_, err = sec.NewKey("login_maximum_lifetime_days", "24")
|
||||
require.NoError(t, err)
|
||||
_, err = sec.NewKey("login_maximum_lifetime_duration", "")
|
||||
require.NoError(t, err)
|
||||
maxLifetimeDaysTest, _ := time.ParseDuration("576h")
|
||||
err = readAuthSettings(f, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, maxLifetimeDaysTest, cfg.LoginMaxLifetime)
|
||||
|
||||
f = ini.Empty()
|
||||
sec, err = f.NewSection("auth")
|
||||
require.NoError(t, err)
|
||||
_, err = sec.NewKey("login_maximum_lifetime_duration", "824h")
|
||||
require.NoError(t, err)
|
||||
maxLifetimeDurationTest, _ := time.ParseDuration("824h")
|
||||
err = readAuthSettings(f, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, maxLifetimeDurationTest, cfg.LoginMaxLifetime)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user