MM-23935 extend session expiry on user activity (#14275)
* MM-23935 extend session expiry on user activity - if user types anything before a session expires the session will be extended to now + session length - ensures new session expiries are not written to DB too frequently - new session store func for updating session ExpiresAt - session length defaults for mobile and web/ldap changed from 180 days to 30 days
Этот коммит содержится в:
@@ -1241,6 +1241,7 @@ func viewChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.App.UpdateLastActivityAtIfNeeded(*c.App.Session())
|
c.App.UpdateLastActivityAtIfNeeded(*c.App.Session())
|
||||||
|
c.ExtendSessionExpiryIfNeeded(w, r)
|
||||||
|
|
||||||
// Returning {"status": "OK", ...} for backwards compatibility
|
// Returning {"status": "OK", ...} for backwards compatibility
|
||||||
resp := &model.ChannelViewResponse{
|
resp := &model.ChannelViewResponse{
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.App.UpdateLastActivityAtIfNeeded(*c.App.Session())
|
c.App.UpdateLastActivityAtIfNeeded(*c.App.Session())
|
||||||
|
c.ExtendSessionExpiryIfNeeded(w, r)
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ type AppIface interface {
|
|||||||
// attributes of the attachment structure. The Slack attachment structure is
|
// attributes of the attachment structure. The Slack attachment structure is
|
||||||
// documented here: https://api.slack.com/docs/attachments
|
// documented here: https://api.slack.com/docs/attachments
|
||||||
ProcessSlackAttachments(attachments []*model.SlackAttachment) []*model.SlackAttachment
|
ProcessSlackAttachments(attachments []*model.SlackAttachment) []*model.SlackAttachment
|
||||||
|
// ExtendSessionExpiryIfNeeded extends Session.ExpiresAt based on session lengths in config.
|
||||||
|
// A new ExpiresAt is only written if enough time has elapsed since last update.
|
||||||
|
// Returns true only if the session was extended.
|
||||||
|
ExtendSessionExpiryIfNeeded(session *model.Session) bool
|
||||||
// FillInPostProps should be invoked before saving posts to fill in properties such as
|
// FillInPostProps should be invoked before saving posts to fill in properties such as
|
||||||
// channel_mentions.
|
// channel_mentions.
|
||||||
//
|
//
|
||||||
@@ -183,6 +187,9 @@ type AppIface interface {
|
|||||||
GetSanitizedConfig() *model.Config
|
GetSanitizedConfig() *model.Config
|
||||||
// GetSchemeRolesForChannel Checks if a channel or its team has an override scheme for channel roles and returns the scheme roles or default channel roles.
|
// GetSchemeRolesForChannel Checks if a channel or its team has an override scheme for channel roles and returns the scheme roles or default channel roles.
|
||||||
GetSchemeRolesForChannel(channelId string) (guestRoleName string, userRoleName string, adminRoleName string, err *model.AppError)
|
GetSchemeRolesForChannel(channelId string) (guestRoleName string, userRoleName string, adminRoleName string, err *model.AppError)
|
||||||
|
// GetSessionLengthInMillis returns the session length, in milliseconds,
|
||||||
|
// based on the type of session (Mobile, SSO, Web/LDAP).
|
||||||
|
GetSessionLengthInMillis(session *model.Session) int64
|
||||||
// GetTeamGroupUsers returns the users who are associated to the team via GroupTeams and GroupMembers.
|
// GetTeamGroupUsers returns the users who are associated to the team via GroupTeams and GroupMembers.
|
||||||
GetTeamGroupUsers(teamID string) ([]*model.User, *model.AppError)
|
GetTeamGroupUsers(teamID string) ([]*model.User, *model.AppError)
|
||||||
// GetTeamSchemeChannelRoles Checks if a team has an override scheme and returns the scheme channel role names or default channel role names.
|
// GetTeamSchemeChannelRoles Checks if a team has an override scheme and returns the scheme channel role names or default channel role names.
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ func (a *App) trackConfig() {
|
|||||||
"uses_letsencrypt": *cfg.ServiceSettings.UseLetsEncrypt,
|
"uses_letsencrypt": *cfg.ServiceSettings.UseLetsEncrypt,
|
||||||
"forward_80_to_443": *cfg.ServiceSettings.Forward80To443,
|
"forward_80_to_443": *cfg.ServiceSettings.Forward80To443,
|
||||||
"maximum_login_attempts": *cfg.ServiceSettings.MaximumLoginAttempts,
|
"maximum_login_attempts": *cfg.ServiceSettings.MaximumLoginAttempts,
|
||||||
|
"extend_session_length_with_activity": *cfg.ServiceSettings.ExtendSessionLengthWithActivity,
|
||||||
"session_length_web_in_days": *cfg.ServiceSettings.SessionLengthWebInDays,
|
"session_length_web_in_days": *cfg.ServiceSettings.SessionLengthWebInDays,
|
||||||
"session_length_mobile_in_days": *cfg.ServiceSettings.SessionLengthMobileInDays,
|
"session_length_mobile_in_days": *cfg.ServiceSettings.SessionLengthMobileInDays,
|
||||||
"session_length_sso_in_days": *cfg.ServiceSettings.SessionLengthSSOInDays,
|
"session_length_sso_in_days": *cfg.ServiceSettings.SessionLengthSSOInDays,
|
||||||
|
|||||||
@@ -3337,6 +3337,23 @@ func (a *OpenTracingAppLayer) ExportPermissions(w io.Writer) error {
|
|||||||
return resultVar0
|
return resultVar0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *OpenTracingAppLayer) ExtendSessionExpiryIfNeeded(session *model.Session) bool {
|
||||||
|
origCtx := a.ctx
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ExtendSessionExpiryIfNeeded")
|
||||||
|
|
||||||
|
a.ctx = newCtx
|
||||||
|
a.app.Srv().Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
a.app.Srv().Store.SetContext(origCtx)
|
||||||
|
a.ctx = origCtx
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
resultVar0 := a.app.ExtendSessionExpiryIfNeeded(session)
|
||||||
|
|
||||||
|
return resultVar0
|
||||||
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) FetchSamlMetadataFromIdp(url string) ([]byte, *model.AppError) {
|
func (a *OpenTracingAppLayer) FetchSamlMetadataFromIdp(url string) ([]byte, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.FetchSamlMetadataFromIdp")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.FetchSamlMetadataFromIdp")
|
||||||
@@ -7407,6 +7424,23 @@ func (a *OpenTracingAppLayer) GetSessionById(sessionId string) (*model.Session,
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *OpenTracingAppLayer) GetSessionLengthInMillis(session *model.Session) int64 {
|
||||||
|
origCtx := a.ctx
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSessionLengthInMillis")
|
||||||
|
|
||||||
|
a.ctx = newCtx
|
||||||
|
a.app.Srv().Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
a.app.Srv().Store.SetContext(origCtx)
|
||||||
|
a.ctx = origCtx
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
resultVar0 := a.app.GetSessionLengthInMillis(session)
|
||||||
|
|
||||||
|
return resultVar0
|
||||||
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) GetSessions(userId string) ([]*model.Session, *model.AppError) {
|
func (a *OpenTracingAppLayer) GetSessions(userId string) ([]*model.Session, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSessions")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSessions")
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/audit"
|
||||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
@@ -74,7 +76,8 @@ func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
|
|||||||
|
|
||||||
if *a.Config().ServiceSettings.SessionIdleTimeoutInMinutes > 0 &&
|
if *a.Config().ServiceSettings.SessionIdleTimeoutInMinutes > 0 &&
|
||||||
!session.IsOAuth &&
|
!session.IsOAuth &&
|
||||||
session.Props[model.SESSION_PROP_TYPE] != model.SESSION_TYPE_USER_ACCESS_TOKEN {
|
session.Props[model.SESSION_PROP_TYPE] != model.SESSION_TYPE_USER_ACCESS_TOKEN &&
|
||||||
|
!*a.Config().ServiceSettings.ExtendSessionLengthWithActivity {
|
||||||
|
|
||||||
timeout := int64(*a.Config().ServiceSettings.SessionIdleTimeoutInMinutes) * 1000 * 60
|
timeout := int64(*a.Config().ServiceSettings.SessionIdleTimeoutInMinutes) * 1000 * 60
|
||||||
if (model.GetMillis() - session.LastActivityAt) > timeout {
|
if (model.GetMillis() - session.LastActivityAt) > timeout {
|
||||||
@@ -284,6 +287,72 @@ func (a *App) UpdateLastActivityAtIfNeeded(session model.Session) {
|
|||||||
a.AddSessionToCache(&session)
|
a.AddSessionToCache(&session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtendSessionExpiryIfNeeded extends Session.ExpiresAt based on session lengths in config.
|
||||||
|
// A new ExpiresAt is only written if enough time has elapsed since last update.
|
||||||
|
// Returns true only if the session was extended.
|
||||||
|
func (a *App) ExtendSessionExpiryIfNeeded(session *model.Session) bool {
|
||||||
|
if session == nil || session.IsExpired() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionLength := a.GetSessionLengthInMillis(session)
|
||||||
|
|
||||||
|
// Only extend the expiry if the lessor of 1% or 1 day has elapsed within the
|
||||||
|
// current session duration.
|
||||||
|
threshold := int64(math.Min(float64(sessionLength)*0.01, float64(24*60*60*1000)))
|
||||||
|
// Minimum session length is 1 day as of this writing, therefore a minimum ~14 minutes threshold.
|
||||||
|
// However we'll add a sanity check here in case that changes. Minimum 5 minute threshold,
|
||||||
|
// meaning we won't write a new expiry more than every 5 minutes.
|
||||||
|
if threshold < 5*60*1000 {
|
||||||
|
threshold = 5 * 60 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
now := model.GetMillis()
|
||||||
|
elapsed := now - (session.ExpiresAt - sessionLength)
|
||||||
|
if elapsed < threshold {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec := a.MakeAuditRecord("extendSessionExpiry", audit.Fail)
|
||||||
|
defer a.LogAuditRec(auditRec, nil)
|
||||||
|
auditRec.AddMeta("session", session)
|
||||||
|
|
||||||
|
newExpiry := now + sessionLength
|
||||||
|
if err := a.Srv().Store.Session().UpdateExpiresAt(session.Id, newExpiry); err != nil {
|
||||||
|
mlog.Error("Failed to update ExpiresAt", mlog.String("user_id", session.UserId), mlog.String("session_id", session.Id), mlog.Err(err))
|
||||||
|
auditRec.AddMeta("err", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update local cache. No need to invalidate cache for cluster as the session cache timeout
|
||||||
|
// ensures each node will get an extended expiry within the next 10 minutes.
|
||||||
|
// Worst case is another node may generate a redundant expiry update.
|
||||||
|
session.ExpiresAt = newExpiry
|
||||||
|
a.AddSessionToCache(session)
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
auditRec.AddMeta("extended_session", session)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSessionLengthInMillis returns the session length, in milliseconds,
|
||||||
|
// based on the type of session (Mobile, SSO, Web/LDAP).
|
||||||
|
func (a *App) GetSessionLengthInMillis(session *model.Session) int64 {
|
||||||
|
if session == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var days int
|
||||||
|
if session.IsMobileApp() {
|
||||||
|
days = *a.Config().ServiceSettings.SessionLengthMobileInDays
|
||||||
|
} else if session.IsOAuth {
|
||||||
|
days = *a.Config().ServiceSettings.SessionLengthSSOInDays
|
||||||
|
} else {
|
||||||
|
days = *a.Config().ServiceSettings.SessionLengthWebInDays
|
||||||
|
}
|
||||||
|
return int64(days * 24 * 60 * 60 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) CreateUserAccessToken(token *model.UserAccessToken) (*model.UserAccessToken, *model.AppError) {
|
func (a *App) CreateUserAccessToken(token *model.UserAccessToken) (*model.UserAccessToken, *model.AppError) {
|
||||||
|
|
||||||
user, err := a.Srv().Store.User().Get(token.UserId)
|
user, err := a.Srv().Store.User().Get(token.UserId)
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
@@ -58,6 +58,7 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) {
|
|||||||
|
|
||||||
th.App.SetLicense(model.NewTestLicense("compliance"))
|
th.App.SetLicense(model.NewTestLicense("compliance"))
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 5 })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 5 })
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ExtendSessionLengthWithActivity = false })
|
||||||
|
|
||||||
rsession, err := th.App.GetSession(session.Token)
|
rsession, err := th.App.GetSession(session.Token)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@@ -177,3 +178,129 @@ func TestUpdateSessionOnPromoteDemote(t *testing.T) {
|
|||||||
assert.Equal(t, "true", rsession.Props[model.SESSION_PROP_IS_GUEST])
|
assert.Equal(t, "true", rsession.Props[model.SESSION_PROP_IS_GUEST])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hourMillis int64 = 60 * 60 * 1000
|
||||||
|
const dayMillis int64 = 24 * hourMillis
|
||||||
|
|
||||||
|
func TestApp_GetSessionLengthInMillis(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionLengthMobileInDays = 3 })
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionLengthSSOInDays = 2 })
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionLengthWebInDays = 1 })
|
||||||
|
|
||||||
|
t.Run("get session length mobile", func(t *testing.T) {
|
||||||
|
session := &model.Session{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
DeviceId: model.NewId(),
|
||||||
|
}
|
||||||
|
session, err := th.App.CreateSession(session)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
sessionLength := th.App.GetSessionLengthInMillis(session)
|
||||||
|
require.Equal(t, dayMillis*3, sessionLength)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get session length SSO", func(t *testing.T) {
|
||||||
|
session := &model.Session{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
IsOAuth: true,
|
||||||
|
}
|
||||||
|
session, err := th.App.CreateSession(session)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
sessionLength := th.App.GetSessionLengthInMillis(session)
|
||||||
|
require.Equal(t, dayMillis*2, sessionLength)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get session length web/LDAP", func(t *testing.T) {
|
||||||
|
session := &model.Session{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
}
|
||||||
|
session, err := th.App.CreateSession(session)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
sessionLength := th.App.GetSessionLengthInMillis(session)
|
||||||
|
require.Equal(t, dayMillis*1, sessionLength)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_ExtendExpiryIfNeeded(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ExtendSessionLengthWithActivity = true })
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionLengthMobileInDays = 3 })
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionLengthSSOInDays = 2 })
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionLengthWebInDays = 1 })
|
||||||
|
|
||||||
|
t.Run("expired session should not be extended", func(t *testing.T) {
|
||||||
|
expires := model.GetMillis() - hourMillis
|
||||||
|
session := &model.Session{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
ExpiresAt: expires,
|
||||||
|
}
|
||||||
|
session, err := th.App.CreateSession(session)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
ok := th.App.ExtendSessionExpiryIfNeeded(session)
|
||||||
|
|
||||||
|
require.False(t, ok)
|
||||||
|
require.Equal(t, expires, session.ExpiresAt)
|
||||||
|
require.True(t, session.IsExpired())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("session within threshold should not be extended", func(t *testing.T) {
|
||||||
|
session := &model.Session{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
}
|
||||||
|
session, err := th.App.CreateSession(session)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
expires := model.GetMillis() + th.App.GetSessionLengthInMillis(session)
|
||||||
|
session.ExpiresAt = expires
|
||||||
|
|
||||||
|
ok := th.App.ExtendSessionExpiryIfNeeded(session)
|
||||||
|
|
||||||
|
require.False(t, ok)
|
||||||
|
require.Equal(t, expires, session.ExpiresAt)
|
||||||
|
require.False(t, session.IsExpired())
|
||||||
|
})
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
session *model.Session
|
||||||
|
}{
|
||||||
|
{name: "mobile", session: &model.Session{UserId: model.NewId(), DeviceId: model.NewId(), Token: model.NewId()}},
|
||||||
|
{name: "SSO", session: &model.Session{UserId: model.NewId(), IsOAuth: true, Token: model.NewId()}},
|
||||||
|
{name: "web/LDAP", session: &model.Session{UserId: model.NewId(), Token: model.NewId()}},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s session beyond threshold should update ExpiresAt", test.name), func(t *testing.T) {
|
||||||
|
session, err := th.App.CreateSession(test.session)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
expires := model.GetMillis() + th.App.GetSessionLengthInMillis(session) - hourMillis
|
||||||
|
session.ExpiresAt = expires
|
||||||
|
|
||||||
|
ok := th.App.ExtendSessionExpiryIfNeeded(session)
|
||||||
|
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Greater(t, session.ExpiresAt, expires)
|
||||||
|
require.False(t, session.IsExpired())
|
||||||
|
|
||||||
|
// check cache was updated
|
||||||
|
ts, ok := th.App.Srv().sessionCache.Get(session.Token)
|
||||||
|
require.True(t, ok)
|
||||||
|
cachedSession := ts.(*model.Session)
|
||||||
|
require.Equal(t, session.ExpiresAt, cachedSession.ExpiresAt)
|
||||||
|
|
||||||
|
// check database was updated.
|
||||||
|
storedSession, err := th.App.Srv().Store.Session().Get(session.Token)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, session.ExpiresAt, storedSession.ExpiresAt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -6910,6 +6910,10 @@
|
|||||||
"id": "store.sql_session.update_device_id.app_error",
|
"id": "store.sql_session.update_device_id.app_error",
|
||||||
"translation": "Unable to update the device id."
|
"translation": "Unable to update the device id."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_session.update_expires_at.app_error",
|
||||||
|
"translation": "Unable to update expires_at."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql_session.update_last_activity.app_error",
|
"id": "store.sql_session.update_last_activity.app_error",
|
||||||
"translation": "Unable to update the last_activity_at."
|
"translation": "Unable to update the last_activity_at."
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ type ServiceSettings struct {
|
|||||||
CorsAllowCredentials *bool `restricted:"true"`
|
CorsAllowCredentials *bool `restricted:"true"`
|
||||||
CorsDebug *bool `restricted:"true"`
|
CorsDebug *bool `restricted:"true"`
|
||||||
AllowCookiesForSubdomains *bool `restricted:"true"`
|
AllowCookiesForSubdomains *bool `restricted:"true"`
|
||||||
|
ExtendSessionLengthWithActivity *bool `restricted:"true"`
|
||||||
SessionLengthWebInDays *int `restricted:"true"`
|
SessionLengthWebInDays *int `restricted:"true"`
|
||||||
SessionLengthMobileInDays *int `restricted:"true"`
|
SessionLengthMobileInDays *int `restricted:"true"`
|
||||||
SessionLengthSSOInDays *int `restricted:"true"`
|
SessionLengthSSOInDays *int `restricted:"true"`
|
||||||
@@ -521,12 +522,25 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
|
|||||||
s.EnableTutorial = NewBool(true)
|
s.EnableTutorial = NewBool(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be manually enabled for existing installations.
|
||||||
|
if s.ExtendSessionLengthWithActivity == nil {
|
||||||
|
s.ExtendSessionLengthWithActivity = NewBool(!isUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
if s.SessionLengthWebInDays == nil {
|
if s.SessionLengthWebInDays == nil {
|
||||||
s.SessionLengthWebInDays = NewInt(180)
|
if isUpdate {
|
||||||
|
s.SessionLengthWebInDays = NewInt(180)
|
||||||
|
} else {
|
||||||
|
s.SessionLengthWebInDays = NewInt(30)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.SessionLengthMobileInDays == nil {
|
if s.SessionLengthMobileInDays == nil {
|
||||||
s.SessionLengthMobileInDays = NewInt(180)
|
if isUpdate {
|
||||||
|
s.SessionLengthMobileInDays = NewInt(180)
|
||||||
|
} else {
|
||||||
|
s.SessionLengthMobileInDays = NewInt(30)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.SessionLengthSSOInDays == nil {
|
if s.SessionLengthSSOInDays == nil {
|
||||||
|
|||||||
@@ -5840,6 +5840,24 @@ func (s *OpenTracingLayerSessionStore) UpdateDeviceId(id string, deviceId string
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OpenTracingLayerSessionStore) UpdateExpiresAt(sessionId string, time int64) *model.AppError {
|
||||||
|
origCtx := s.Root.Store.Context()
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateExpiresAt")
|
||||||
|
s.Root.Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
s.Root.Store.SetContext(origCtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
resultVar0 := s.SessionStore.UpdateExpiresAt(sessionId, time)
|
||||||
|
if resultVar0 != nil {
|
||||||
|
span.LogFields(spanlog.Error(resultVar0))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultVar0
|
||||||
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerSessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
func (s *OpenTracingLayerSessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateLastActivityAt")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateLastActivityAt")
|
||||||
|
|||||||
@@ -160,6 +160,14 @@ func (me SqlSessionStore) PermanentDeleteSessionsByUser(userId string) *model.Ap
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (me SqlSessionStore) UpdateExpiresAt(sessionId string, time int64) *model.AppError {
|
||||||
|
_, err := me.GetMaster().Exec("UPDATE Sessions SET ExpiresAt = :ExpiresAt WHERE Id = :Id", map[string]interface{}{"ExpiresAt": time, "Id": sessionId})
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("SqlSessionStore.UpdateExpiresAt", "store.sql_session.update_expires_at.app_error", nil, "sessionId="+sessionId, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (me SqlSessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
func (me SqlSessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
||||||
_, err := me.GetMaster().Exec("UPDATE Sessions SET LastActivityAt = :LastActivityAt WHERE Id = :Id", map[string]interface{}{"LastActivityAt": time, "Id": sessionId})
|
_, err := me.GetMaster().Exec("UPDATE Sessions SET LastActivityAt = :LastActivityAt WHERE Id = :Id", map[string]interface{}{"LastActivityAt": time, "Id": sessionId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ type SessionStore interface {
|
|||||||
Remove(sessionIdOrToken string) *model.AppError
|
Remove(sessionIdOrToken string) *model.AppError
|
||||||
RemoveAllSessions() *model.AppError
|
RemoveAllSessions() *model.AppError
|
||||||
PermanentDeleteSessionsByUser(teamId string) *model.AppError
|
PermanentDeleteSessionsByUser(teamId string) *model.AppError
|
||||||
|
UpdateExpiresAt(sessionId string, time int64) *model.AppError
|
||||||
UpdateLastActivityAt(sessionId string, time int64) *model.AppError
|
UpdateLastActivityAt(sessionId string, time int64) *model.AppError
|
||||||
UpdateRoles(userId string, roles string) (string, *model.AppError)
|
UpdateRoles(userId string, roles string) (string, *model.AppError)
|
||||||
UpdateDeviceId(id string, deviceId string, expiresAt int64) (string, *model.AppError)
|
UpdateDeviceId(id string, deviceId string, expiresAt int64) (string, *model.AppError)
|
||||||
|
|||||||
@@ -213,6 +213,22 @@ func (_m *SessionStore) UpdateDeviceId(id string, deviceId string, expiresAt int
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateExpiresAt provides a mock function with given fields: sessionId, time
|
||||||
|
func (_m *SessionStore) UpdateExpiresAt(sessionId string, time int64) *model.AppError {
|
||||||
|
ret := _m.Called(sessionId, time)
|
||||||
|
|
||||||
|
var r0 *model.AppError
|
||||||
|
if rf, ok := ret.Get(0).(func(string, int64) *model.AppError); ok {
|
||||||
|
r0 = rf(sessionId, time)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.AppError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateLastActivityAt provides a mock function with given fields: sessionId, time
|
// UpdateLastActivityAt provides a mock function with given fields: sessionId, time
|
||||||
func (_m *SessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
func (_m *SessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
||||||
ret := _m.Called(sessionId, time)
|
ret := _m.Called(sessionId, time)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func TestSessionStore(t *testing.T, ss store.Store) {
|
|||||||
t.Run("SessionRemoveToken", func(t *testing.T) { testSessionRemoveToken(t, ss) })
|
t.Run("SessionRemoveToken", func(t *testing.T) { testSessionRemoveToken(t, ss) })
|
||||||
t.Run("SessionUpdateDeviceId", func(t *testing.T) { testSessionUpdateDeviceId(t, ss) })
|
t.Run("SessionUpdateDeviceId", func(t *testing.T) { testSessionUpdateDeviceId(t, ss) })
|
||||||
t.Run("SessionUpdateDeviceId2", func(t *testing.T) { testSessionUpdateDeviceId2(t, ss) })
|
t.Run("SessionUpdateDeviceId2", func(t *testing.T) { testSessionUpdateDeviceId2(t, ss) })
|
||||||
|
t.Run("UpdateExpiresAt", func(t *testing.T) { testSessionStoreUpdateExpiresAt(t, ss) })
|
||||||
t.Run("UpdateLastActivityAt", func(t *testing.T) { testSessionStoreUpdateLastActivityAt(t, ss) })
|
t.Run("UpdateLastActivityAt", func(t *testing.T) { testSessionStoreUpdateLastActivityAt(t, ss) })
|
||||||
t.Run("SessionCount", func(t *testing.T) { testSessionCount(t, ss) })
|
t.Run("SessionCount", func(t *testing.T) { testSessionCount(t, ss) })
|
||||||
}
|
}
|
||||||
@@ -212,6 +213,21 @@ func testSessionUpdateDeviceId2(t *testing.T, ss store.Store) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSessionStoreUpdateExpiresAt(t *testing.T, ss store.Store) {
|
||||||
|
s1 := &model.Session{}
|
||||||
|
s1.UserId = model.NewId()
|
||||||
|
|
||||||
|
s1, err := ss.Session().Save(s1)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
err = ss.Session().UpdateExpiresAt(s1.Id, 1234567890)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
session, err := ss.Session().Get(s1.Id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.EqualValues(t, session.ExpiresAt, 1234567890, "ExpiresAt not updated correctly")
|
||||||
|
}
|
||||||
|
|
||||||
func testSessionStoreUpdateLastActivityAt(t *testing.T, ss store.Store) {
|
func testSessionStoreUpdateLastActivityAt(t *testing.T, ss store.Store) {
|
||||||
s1 := &model.Session{}
|
s1 := &model.Session{}
|
||||||
s1.UserId = model.NewId()
|
s1.UserId = model.NewId()
|
||||||
|
|||||||
@@ -5291,6 +5291,22 @@ func (s *TimerLayerSessionStore) UpdateDeviceId(id string, deviceId string, expi
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TimerLayerSessionStore) UpdateExpiresAt(sessionId string, time int64) *model.AppError {
|
||||||
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
resultVar0 := s.SessionStore.UpdateExpiresAt(sessionId, time)
|
||||||
|
|
||||||
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
|
if s.Root.Metrics != nil {
|
||||||
|
success := "false"
|
||||||
|
if resultVar0 == nil {
|
||||||
|
success = "true"
|
||||||
|
}
|
||||||
|
s.Root.Metrics.ObserveStoreMethodDuration("SessionStore.UpdateExpiresAt", success, elapsed)
|
||||||
|
}
|
||||||
|
return resultVar0
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TimerLayerSessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
func (s *TimerLayerSessionStore) UpdateLastActivityAt(sessionId string, time int64) *model.AppError {
|
||||||
start := timemodule.Now()
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"EnableUserAccessTokens": false,
|
"EnableUserAccessTokens": false,
|
||||||
"AllowCorsFrom": "",
|
"AllowCorsFrom": "",
|
||||||
"AllowCookiesForSubdomains": false,
|
"AllowCookiesForSubdomains": false,
|
||||||
|
"ExtendSessionLengthWithActivity": true,
|
||||||
"SessionLengthWebInDays": 30,
|
"SessionLengthWebInDays": 30,
|
||||||
"SessionLengthMobileInDays": 30,
|
"SessionLengthMobileInDays": 30,
|
||||||
"SessionLengthSSOInDays": 30,
|
"SessionLengthSSOInDays": 30,
|
||||||
|
|||||||
@@ -182,6 +182,14 @@ func (c *Context) MfaRequired() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtendSessionExpiryIfNeeded will update Session.ExpiresAt based on session lengths in config.
|
||||||
|
// Session cookies will be resent to the client with updated max age.
|
||||||
|
func (c *Context) ExtendSessionExpiryIfNeeded(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if ok := c.App.ExtendSessionExpiryIfNeeded(c.App.Session()); ok {
|
||||||
|
c.App.AttachSessionCookies(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
||||||
subpath, _ := utils.GetSubpathFromConfig(c.App.Config())
|
subpath, _ := utils.GetSubpathFromConfig(c.App.Config())
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ func (api *API) InitUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) userTyping(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) {
|
func (api *API) userTyping(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) {
|
||||||
|
api.App.ExtendSessionExpiryIfNeeded(&req.Session)
|
||||||
|
|
||||||
if api.App.Srv().Busy.IsBusy() {
|
if api.App.Srv().Busy.IsBusy() {
|
||||||
// this is considered a non-critical service and will be disabled when server busy.
|
// this is considered a non-critical service and will be disabled when server busy.
|
||||||
return nil, NewServerBusyWebSocketError(req.Action)
|
return nil, NewServerBusyWebSocketError(req.Action)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user