[MM-58560] Configurable session revocation during password resets (#27286)
* [MM-58560] Allow for configurable session revocation during password reset * Missing i18n additions * Update Settings Wording * Update Settings Wording #2 * Update default_config.ts for Session Termination --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
cbd5d95bbb
Коммит
1bbc3b4e83
@@ -1453,6 +1453,31 @@ func (a *App) UpdatePassword(rctx request.CTX, user *model.User, newPassword str
|
||||
|
||||
a.InvalidateCacheForUser(user.Id)
|
||||
|
||||
if *a.Config().ServiceSettings.TerminateSessionsOnPasswordChange {
|
||||
// Get currently active sessions if request is user-initiated to retain it
|
||||
currentSession := ""
|
||||
if rctx.Session() != nil && rctx.Session().UserId == user.Id {
|
||||
currentSession = rctx.Session().Id
|
||||
}
|
||||
|
||||
sessions, err := a.GetSessions(rctx, user.Id)
|
||||
if err != nil {
|
||||
return model.NewAppError("UpdatePassword", "api.user.update_password.failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
// Revoke all but current session
|
||||
for _, session := range sessions {
|
||||
if session.Id == currentSession {
|
||||
continue
|
||||
}
|
||||
|
||||
err := a.RevokeSessionById(rctx, session.Id)
|
||||
if err != nil {
|
||||
return model.NewAppError("UpdatePassword", "api.user.update_password.failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1210,6 +1210,153 @@ func TestInvalidatePasswordRecoveryTokens(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPasswordChangeSessionTermination(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("user-initiated password change with termination enabled", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(c *model.Config) {
|
||||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = true
|
||||
})
|
||||
|
||||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.Context.Session().UserId = th.BasicUser2.Id
|
||||
th.Context.Session().Id = session.Id
|
||||
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
|
||||
require.Nil(t, err)
|
||||
|
||||
session, err = th.App.GetSession(session.Token)
|
||||
require.Nil(t, err)
|
||||
require.False(t, session.IsExpired())
|
||||
|
||||
session2, err = th.App.GetSession(session2.Token)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, session2)
|
||||
|
||||
// Cleanup
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
|
||||
require.Nil(t, err)
|
||||
th.Context.Session().UserId = ""
|
||||
th.Context.Session().Id = ""
|
||||
})
|
||||
|
||||
t.Run("user-initiated password change with termination disabled", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(c *model.Config) {
|
||||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = false
|
||||
})
|
||||
|
||||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.Context.Session().UserId = th.BasicUser2.Id
|
||||
th.Context.Session().Id = session.Id
|
||||
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
|
||||
require.Nil(t, err)
|
||||
|
||||
session, err = th.App.GetSession(session.Token)
|
||||
require.Nil(t, err)
|
||||
require.False(t, session.IsExpired())
|
||||
|
||||
session2, err = th.App.GetSession(session2.Token)
|
||||
require.Nil(t, err)
|
||||
require.False(t, session2.IsExpired())
|
||||
|
||||
// Cleanup
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
|
||||
require.Nil(t, err)
|
||||
th.Context.Session().UserId = ""
|
||||
th.Context.Session().Id = ""
|
||||
})
|
||||
|
||||
t.Run("admin-initiated password change with termination enabled", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(c *model.Config) {
|
||||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = true
|
||||
})
|
||||
|
||||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
|
||||
require.Nil(t, err)
|
||||
|
||||
session, err = th.App.GetSession(session.Token)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, session)
|
||||
|
||||
session2, err = th.App.GetSession(session2.Token)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, session2)
|
||||
|
||||
// Cleanup
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("admin-initiated password change with termination disabled", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(c *model.Config) {
|
||||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = false
|
||||
})
|
||||
|
||||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||||
UserId: th.BasicUser2.Id,
|
||||
Roles: model.SystemUserRoleId,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
|
||||
require.Nil(t, err)
|
||||
|
||||
session, err = th.App.GetSession(session.Token)
|
||||
require.Nil(t, err)
|
||||
require.False(t, session.IsExpired())
|
||||
|
||||
session2, err = th.App.GetSession(session2.Token)
|
||||
require.Nil(t, err)
|
||||
require.False(t, session2.IsExpired())
|
||||
|
||||
// Cleanup
|
||||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetViewUsersRestrictions(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -430,6 +430,7 @@ func (ts *TelemetryService) trackConfig() {
|
||||
"forward_80_to_443": *cfg.ServiceSettings.Forward80To443,
|
||||
"maximum_login_attempts": *cfg.ServiceSettings.MaximumLoginAttempts,
|
||||
"extend_session_length_with_activity": *cfg.ServiceSettings.ExtendSessionLengthWithActivity,
|
||||
"terminate_sessions_on_password_change": *cfg.ServiceSettings.TerminateSessionsOnPasswordChange,
|
||||
"session_length_web_in_hours": *cfg.ServiceSettings.SessionLengthWebInHours,
|
||||
"session_length_mobile_in_hours": *cfg.ServiceSettings.SessionLengthMobileInHours,
|
||||
"session_length_sso_in_hours": *cfg.ServiceSettings.SessionLengthSSOInHours,
|
||||
|
||||
@@ -341,6 +341,7 @@ type ServiceSettings struct {
|
||||
CorsDebug *bool `access:"integrations_cors,write_restrictable,cloud_restrictable"`
|
||||
AllowCookiesForSubdomains *bool `access:"write_restrictable,cloud_restrictable"`
|
||||
ExtendSessionLengthWithActivity *bool `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
|
||||
TerminateSessionsOnPasswordChange *bool `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
|
||||
|
||||
// Deprecated
|
||||
SessionLengthWebInDays *int `access:"environment_session_lengths,write_restrictable,cloud_restrictable"` // telemetry: none
|
||||
@@ -631,6 +632,11 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
|
||||
s.ExtendSessionLengthWithActivity = NewBool(!isUpdate)
|
||||
}
|
||||
|
||||
// Must be manually enabled for existing installations.
|
||||
if s.TerminateSessionsOnPasswordChange == nil {
|
||||
s.TerminateSessionsOnPasswordChange = NewBool(!isUpdate)
|
||||
}
|
||||
|
||||
if s.SessionLengthWebInDays == nil {
|
||||
if isUpdate {
|
||||
s.SessionLengthWebInDays = NewInt(180)
|
||||
|
||||
Ссылка в новой задаче
Block a user