summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api4/context.go1
-rw-r--r--app/session.go12
-rw-r--r--app/session_test.go119
-rw-r--r--config/default.json1
-rw-r--r--model/config.go6
5 files changed, 139 insertions, 0 deletions
diff --git a/api4/context.go b/api4/context.go
index c526f38bb..9af15d72b 100644
--- a/api4/context.go
+++ b/api4/context.go
@@ -157,6 +157,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized)
} else {
c.Session = *session
+ c.App.UpdateLastActivityAtIfNeeded(*session)
}
}
diff --git a/app/session.go b/app/session.go
index f0245acba..0e7701135 100644
--- a/app/session.go
+++ b/app/session.go
@@ -71,6 +71,18 @@ func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token}, "", http.StatusUnauthorized)
}
+ if *utils.Cfg.ServiceSettings.SessionIdleTimeoutInMinutes > 0 &&
+ utils.IsLicensed() && *utils.License().Features.Compliance &&
+ session != nil && !session.IsOAuth && !session.IsMobileApp() &&
+ session.Props[model.SESSION_PROP_TYPE] != model.SESSION_TYPE_USER_ACCESS_TOKEN {
+
+ timeout := int64(*utils.Cfg.ServiceSettings.SessionIdleTimeoutInMinutes) * 1000 * 60
+ if model.GetMillis()-session.LastActivityAt > timeout {
+ a.RevokeSessionById(session.Id)
+ return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token}, "idle timeout", http.StatusUnauthorized)
+ }
+ }
+
return session, nil
}
diff --git a/app/session_test.go b/app/session_test.go
index e91132a8a..c001655db 100644
--- a/app/session_test.go
+++ b/app/session_test.go
@@ -7,6 +7,10 @@ import (
"testing"
"github.com/mattermost/mattermost-server/model"
+ "github.com/mattermost/mattermost-server/utils"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestCache(t *testing.T) {
@@ -32,3 +36,118 @@ func TestCache(t *testing.T) {
t.Fatal("should have one less")
}
}
+
+func TestGetSessionIdleTimeoutInMinutes(t *testing.T) {
+ th := Setup().InitBasic()
+
+ session := &model.Session{
+ UserId: model.NewId(),
+ }
+
+ session, _ = th.App.CreateSession(session)
+
+ isLicensed := utils.IsLicensed()
+ license := utils.License()
+ timeout := *utils.Cfg.ServiceSettings.SessionIdleTimeoutInMinutes
+ defer func() {
+ utils.SetIsLicensed(isLicensed)
+ utils.SetLicense(license)
+ *utils.Cfg.ServiceSettings.SessionIdleTimeoutInMinutes = timeout
+ }()
+ utils.SetIsLicensed(true)
+ utils.SetLicense(&model.License{Features: &model.Features{}})
+ utils.License().Features.SetDefaults()
+ *utils.License().Features.Compliance = true
+ *utils.Cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 5
+
+ rsession, err := th.App.GetSession(session.Token)
+ require.Nil(t, err)
+ assert.Equal(t, rsession.Id, session.Id)
+
+ rsession, err = th.App.GetSession(session.Token)
+
+ // Test regular session, should timeout
+ time := session.LastActivityAt - (1000 * 60 * 6)
+ <-th.App.Srv.Store.Session().UpdateLastActivityAt(session.Id, time)
+ th.App.ClearSessionCacheForUserSkipClusterSend(session.UserId)
+
+ rsession, err = th.App.GetSession(session.Token)
+ require.NotNil(t, err)
+ assert.Equal(t, "api.context.invalid_token.error", err.Id)
+ assert.Equal(t, "idle timeout", err.DetailedError)
+ assert.Nil(t, rsession)
+
+ // Test mobile session, should not timeout
+ session = &model.Session{
+ UserId: model.NewId(),
+ DeviceId: "android:" + model.NewId(),
+ }
+
+ session, _ = th.App.CreateSession(session)
+ time = session.LastActivityAt - (1000 * 60 * 6)
+ <-th.App.Srv.Store.Session().UpdateLastActivityAt(session.Id, time)
+ th.App.ClearSessionCacheForUserSkipClusterSend(session.UserId)
+
+ _, err = th.App.GetSession(session.Token)
+ assert.Nil(t, err)
+
+ // Test oauth session, should not timeout
+ session = &model.Session{
+ UserId: model.NewId(),
+ IsOAuth: true,
+ }
+
+ session, _ = th.App.CreateSession(session)
+ time = session.LastActivityAt - (1000 * 60 * 6)
+ <-th.App.Srv.Store.Session().UpdateLastActivityAt(session.Id, time)
+ th.App.ClearSessionCacheForUserSkipClusterSend(session.UserId)
+
+ _, err = th.App.GetSession(session.Token)
+ assert.Nil(t, err)
+
+ // Test personal access token session, should not timeout
+ session = &model.Session{
+ UserId: model.NewId(),
+ }
+ session.AddProp(model.SESSION_PROP_TYPE, model.SESSION_TYPE_USER_ACCESS_TOKEN)
+
+ session, _ = th.App.CreateSession(session)
+ time = session.LastActivityAt - (1000 * 60 * 6)
+ <-th.App.Srv.Store.Session().UpdateLastActivityAt(session.Id, time)
+ th.App.ClearSessionCacheForUserSkipClusterSend(session.UserId)
+
+ _, err = th.App.GetSession(session.Token)
+ assert.Nil(t, err)
+
+ // Test regular session with license off, should not timeout
+ *utils.License().Features.Compliance = false
+
+ session = &model.Session{
+ UserId: model.NewId(),
+ }
+
+ session, _ = th.App.CreateSession(session)
+ time = session.LastActivityAt - (1000 * 60 * 6)
+ <-th.App.Srv.Store.Session().UpdateLastActivityAt(session.Id, time)
+ th.App.ClearSessionCacheForUserSkipClusterSend(session.UserId)
+
+ _, err = th.App.GetSession(session.Token)
+ assert.Nil(t, err)
+
+ *utils.License().Features.Compliance = true
+
+ // Test regular session with timeout set to 0, should not timeout
+ *utils.Cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 0
+
+ session = &model.Session{
+ UserId: model.NewId(),
+ }
+
+ session, _ = th.App.CreateSession(session)
+ time = session.LastActivityAt - (1000 * 60 * 6)
+ <-th.App.Srv.Store.Session().UpdateLastActivityAt(session.Id, time)
+ th.App.ClearSessionCacheForUserSkipClusterSend(session.UserId)
+
+ _, err = th.App.GetSession(session.Token)
+ assert.Nil(t, err)
+}
diff --git a/config/default.json b/config/default.json
index 34fdb97a4..f9c5647e7 100644
--- a/config/default.json
+++ b/config/default.json
@@ -36,6 +36,7 @@
"SessionLengthMobileInDays": 30,
"SessionLengthSSOInDays": 30,
"SessionCacheInMinutes": 10,
+ "SessionIdleTimeout": 0,
"WebsocketSecurePort": 443,
"WebsocketPort": 80,
"WebserverMode": "gzip",
diff --git a/model/config.go b/model/config.go
index c55324ea7..8b6aad0e5 100644
--- a/model/config.go
+++ b/model/config.go
@@ -182,6 +182,7 @@ type ServiceSettings struct {
SessionLengthMobileInDays *int
SessionLengthSSOInDays *int
SessionCacheInMinutes *int
+ SessionIdleTimeoutInMinutes *int
WebsocketSecurePort *int
WebsocketPort *int
WebserverMode *string
@@ -1155,6 +1156,11 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.SessionCacheInMinutes = 10
}
+ if o.ServiceSettings.SessionIdleTimeoutInMinutes == nil {
+ o.ServiceSettings.SessionIdleTimeoutInMinutes = new(int)
+ *o.ServiceSettings.SessionIdleTimeoutInMinutes = 0
+ }
+
if o.ServiceSettings.EnableCommands == nil {
o.ServiceSettings.EnableCommands = new(bool)
*o.ServiceSettings.EnableCommands = false