Commit 8d971ab2 by Hansuuuuuuuuuu Committed by GitHub

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>
parent f5292234
......@@ -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)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment