keycloak-uncached
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java 12(+12 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 5(+4 -1)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java 23(+20 -3)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java 14(+14 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java 4(+4 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java 65(+65 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java 109(+109 -0)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 8da979e..940c457 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -44,6 +44,8 @@ public class RealmRepresentation {
protected Integer accessTokenLifespanForImplicitFlow;
protected Integer ssoSessionIdleTimeout;
protected Integer ssoSessionMaxLifespan;
+ protected Integer ssoSessionIdleTimeoutRememberMe;
+ protected Integer ssoSessionMaxLifespanRememberMe;
protected Integer offlineSessionIdleTimeout;
// KEYCLOAK-7688 Offline Session Max for Offline Token
protected Boolean offlineSessionMaxLifespanEnabled;
@@ -300,6 +302,22 @@ public class RealmRepresentation {
this.ssoSessionMaxLifespan = ssoSessionMaxLifespan;
}
+ public Integer getSsoSessionMaxLifespanRememberMe() {
+ return ssoSessionMaxLifespanRememberMe;
+ }
+
+ public void setSsoSessionMaxLifespanRememberMe(Integer ssoSessionMaxLifespanRememberMe) {
+ this.ssoSessionMaxLifespanRememberMe = ssoSessionMaxLifespanRememberMe;
+ }
+
+ public Integer getSsoSessionIdleTimeoutRememberMe() {
+ return ssoSessionIdleTimeoutRememberMe;
+ }
+
+ public void setSsoSessionIdleTimeoutRememberMe(Integer ssoSessionIdleTimeoutRememberMe) {
+ this.ssoSessionIdleTimeoutRememberMe = ssoSessionIdleTimeoutRememberMe;
+ }
+
public Integer getOfflineSessionIdleTimeout() {
return offlineSessionIdleTimeout;
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
index 389724b..c2877e1 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
@@ -79,6 +79,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
protected int refreshTokenMaxReuse;
protected int ssoSessionIdleTimeout;
protected int ssoSessionMaxLifespan;
+ protected int ssoSessionIdleTimeoutRememberMe;
+ protected int ssoSessionMaxLifespanRememberMe;
protected int offlineSessionIdleTimeout;
// KEYCLOAK-7688 Offline Session Max for Offline Token
protected boolean offlineSessionMaxLifespanEnabled;
@@ -185,6 +187,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
refreshTokenMaxReuse = model.getRefreshTokenMaxReuse();
ssoSessionIdleTimeout = model.getSsoSessionIdleTimeout();
ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan();
+ ssoSessionIdleTimeoutRememberMe = model.getSsoSessionIdleTimeoutRememberMe();
+ ssoSessionMaxLifespanRememberMe = model.getSsoSessionMaxLifespanRememberMe();
offlineSessionIdleTimeout = model.getOfflineSessionIdleTimeout();
// KEYCLOAK-7688 Offline Session Max for Offline Token
offlineSessionMaxLifespanEnabled = model.isOfflineSessionMaxLifespanEnabled();
@@ -413,6 +417,14 @@ public class CachedRealm extends AbstractExtendableRevisioned {
return ssoSessionMaxLifespan;
}
+ public int getSsoSessionIdleTimeoutRememberMe() {
+ return ssoSessionIdleTimeoutRememberMe;
+ }
+
+ public int getSsoSessionMaxLifespanRememberMe() {
+ return ssoSessionMaxLifespanRememberMe;
+ }
+
public int getOfflineSessionIdleTimeout() {
return offlineSessionIdleTimeout;
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 0771469..62aee89 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -418,6 +418,30 @@ public class RealmAdapter implements CachedRealmModel {
}
@Override
+ public int getSsoSessionIdleTimeoutRememberMe() {
+ if (updated != null) return updated.getSsoSessionIdleTimeoutRememberMe();
+ return cached.getSsoSessionIdleTimeoutRememberMe();
+ }
+
+ @Override
+ public void setSsoSessionIdleTimeoutRememberMe(int seconds) {
+ getDelegateForUpdate();
+ updated.setSsoSessionIdleTimeoutRememberMe(seconds);
+ }
+
+ @Override
+ public int getSsoSessionMaxLifespanRememberMe() {
+ if (updated != null) return updated.getSsoSessionMaxLifespanRememberMe();
+ return cached.getSsoSessionMaxLifespanRememberMe();
+ }
+
+ @Override
+ public void setSsoSessionMaxLifespanRememberMe(int seconds) {
+ getDelegateForUpdate();
+ updated.setSsoSessionMaxLifespanRememberMe(seconds);
+ }
+
+ @Override
public int getOfflineSessionIdleTimeout() {
if (isUpdated()) return updated.getOfflineSessionIdleTimeout();
return cached.getOfflineSessionIdleTimeout();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 1e1670d..bf7b04f 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -470,6 +470,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
private void removeExpiredUserSessions(RealmModel realm) {
int expired = Time.currentTime() - realm.getSsoSessionMaxLifespan();
int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout() - SessionTimeoutHelper.PERIODIC_CLEANER_IDLE_TIMEOUT_WINDOW_SECONDS;
+ int expiredRememberMe = Time.currentTime() - (realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan());
+ int expiredRefreshRememberMe = Time.currentTime() - (realm.getSsoSessionIdleTimeoutRememberMe() > 0 ? realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout()) -
+ SessionTimeoutHelper.PERIODIC_CLEANER_IDLE_TIMEOUT_WINDOW_SECONDS;
FuturesHelper futures = new FuturesHelper();
@@ -484,7 +487,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
localCacheStoreIgnore
.entrySet()
.stream()
- .filter(UserSessionPredicate.create(realm.getId()).expired(expired, expiredRefresh))
+ .filter(UserSessionPredicate.create(realm.getId()).expired(expired, expiredRefresh, expiredRememberMe, expiredRefreshRememberMe))
.map(Mappers.userSessionEntity())
.forEach(new Consumer<UserSessionEntity>() {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
index 850d19e..7ec245c 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
@@ -47,6 +47,10 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
private Integer expiredRefresh;
+ private Integer expiredRememberMe;
+
+ private Integer expiredRefreshRememberMe;
+
private String brokerSessionId;
private String brokerUserId;
@@ -82,11 +86,18 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
}
public UserSessionPredicate expired(Integer expired, Integer expiredRefresh) {
+ return this.expired(expired, expiredRefresh, null, null);
+ }
+
+ public UserSessionPredicate expired(Integer expired, Integer expiredRefresh, Integer expiredRememberMe, Integer expiredRefreshRememberMe) {
this.expired = expired;
this.expiredRefresh = expiredRefresh;
+ this.expiredRememberMe = expiredRememberMe;
+ this.expiredRefreshRememberMe = expiredRefreshRememberMe;
return this;
}
+
public UserSessionPredicate brokerSessionId(String id) {
this.brokerSessionId = id;
return this;
@@ -121,14 +132,20 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return false;
}
- if (expired != null && expiredRefresh != null && entity.getStarted() > expired && entity.getLastSessionRefresh() > expiredRefresh) {
- return false;
+ if (entity.isRememberMe()) {
+ if (expiredRememberMe != null && expiredRefreshRememberMe != null && entity.getStarted() > expiredRefreshRememberMe && entity.getLastSessionRefresh() > expiredRefreshRememberMe) {
+ return false;
+ }
+ }
+ else {
+ if (expired != null && expiredRefresh != null && entity.getStarted() > expired && entity.getLastSessionRefresh() > expiredRefresh) {
+ return false;
+ }
}
if (expired == null && expiredRefresh != null && entity.getLastSessionRefresh() > expiredRefresh) {
return false;
}
-
return true;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 6fbf1c0..973b2a0 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -109,6 +109,10 @@ public class RealmEntity {
private int ssoSessionIdleTimeout;
@Column(name="SSO_MAX_LIFESPAN")
private int ssoSessionMaxLifespan;
+ @Column(name="SSO_IDLE_TIMEOUT_REMEMBER_ME")
+ private int ssoSessionIdleTimeoutRememberMe;
+ @Column(name="SSO_MAX_LIFESPAN_REMEMBER_ME")
+ private int ssoSessionMaxLifespanRememberMe;
@Column(name="OFFLINE_SESSION_IDLE_TIMEOUT")
private int offlineSessionIdleTimeout;
@Column(name="ACCESS_TOKEN_LIFESPAN")
@@ -370,6 +374,22 @@ public class RealmEntity {
this.ssoSessionMaxLifespan = ssoSessionMaxLifespan;
}
+ public int getSsoSessionIdleTimeoutRememberMe() {
+ return ssoSessionIdleTimeoutRememberMe;
+ }
+
+ public void setSsoSessionIdleTimeoutRememberMe(int ssoSessionIdleTimeoutRememberMe) {
+ this.ssoSessionIdleTimeoutRememberMe = ssoSessionIdleTimeoutRememberMe;
+ }
+
+ public int getSsoSessionMaxLifespanRememberMe() {
+ return ssoSessionMaxLifespanRememberMe;
+ }
+
+ public void setSsoSessionMaxLifespanRememberMe(int ssoSessionMaxLifespanRememberMe) {
+ this.ssoSessionMaxLifespanRememberMe = ssoSessionMaxLifespanRememberMe;
+ }
+
public int getOfflineSessionIdleTimeout() {
return offlineSessionIdleTimeout;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index c699ec5..f5e45cf 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -467,6 +467,26 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
@Override
+ public int getSsoSessionIdleTimeoutRememberMe() {
+ return realm.getSsoSessionIdleTimeoutRememberMe();
+ }
+
+ @Override
+ public void setSsoSessionIdleTimeoutRememberMe(int seconds){
+ realm.setSsoSessionIdleTimeoutRememberMe(seconds);
+ }
+
+ @Override
+ public int getSsoSessionMaxLifespanRememberMe() {
+ return realm.getSsoSessionMaxLifespanRememberMe();
+ }
+
+ @Override
+ public void setSsoSessionMaxLifespanRememberMe(int seconds) {
+ realm.setSsoSessionMaxLifespanRememberMe(seconds);
+ }
+
+ @Override
public int getOfflineSessionIdleTimeout() {
return realm.getOfflineSessionIdleTimeout();
}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-4.7.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.7.0.xml
new file mode 100644
index 0000000..f8c9693
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.7.0.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ ~ * and other contributors as indicated by the @author tags.
+ ~ *
+ ~ * Licensed under the Apache License, Version 2.0 (the "License");
+ ~ * you may not use this file except in compliance with the License.
+ ~ * You may obtain a copy of the License at
+ ~ *
+ ~ * http://www.apache.org/licenses/LICENSE-2.0
+ ~ *
+ ~ * Unless required by applicable law or agreed to in writing, software
+ ~ * distributed under the License is distributed on an "AS IS" BASIS,
+ ~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ * See the License for the specific language governing permissions and
+ ~ * limitations under the License.
+ -->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+
+ <changeSet author="sguilhen@redhat.com" id="4.7.0-KEYCLOAK-1267">
+ <addColumn tableName="REALM">
+ <column name="SSO_MAX_LIFESPAN_REMEMBER_ME" type="INT"/>
+ <column name="SSO_IDLE_TIMEOUT_REMEMBER_ME" type="INT"/>
+ </addColumn>
+ </changeSet>
+
+</databaseChangeLog>
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index 9a3e021..232a0ed 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -60,4 +60,5 @@
<include file="META-INF/jpa-changelog-4.2.0.xml"/>
<include file="META-INF/jpa-changelog-4.3.0.xml"/>
<include file="META-INF/jpa-changelog-4.6.0.xml"/>
+ <include file="META-INF/jpa-changelog-4.7.0.xml"/>
</databaseChangeLog>
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index c8d3d9b..33323fd 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -177,6 +177,12 @@ public interface RealmModel extends RoleContainerModel {
int getSsoSessionMaxLifespan();
void setSsoSessionMaxLifespan(int seconds);
+ int getSsoSessionIdleTimeoutRememberMe();
+ void setSsoSessionIdleTimeoutRememberMe(int seconds);
+
+ int getSsoSessionMaxLifespanRememberMe();
+ void setSsoSessionMaxLifespanRememberMe(int seconds);
+
int getOfflineSessionIdleTimeout();
void setOfflineSessionIdleTimeout(int seconds);
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 0697c38..33b026e 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -293,6 +293,8 @@ public class ModelToRepresentation {
rep.setAccessTokenLifespanForImplicitFlow(realm.getAccessTokenLifespanForImplicitFlow());
rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout());
rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
+ rep.setSsoSessionIdleTimeoutRememberMe(realm.getSsoSessionIdleTimeoutRememberMe());
+ rep.setSsoSessionMaxLifespanRememberMe(realm.getSsoSessionMaxLifespanRememberMe());
rep.setOfflineSessionIdleTimeout(realm.getOfflineSessionIdleTimeout());
// KEYCLOAK-7688 Offline Session Max for Offline Token
rep.setOfflineSessionMaxLifespanEnabled(realm.isOfflineSessionMaxLifespanEnabled());
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 1c7bcf3..e2b25fc 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -195,6 +195,8 @@ public class RepresentationToModel {
else newRealm.setSsoSessionIdleTimeout(1800);
if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
else newRealm.setSsoSessionMaxLifespan(36000);
+ if (rep.getSsoSessionMaxLifespanRememberMe() != null) newRealm.setSsoSessionMaxLifespanRememberMe(rep.getSsoSessionMaxLifespanRememberMe());
+ if (rep.getSsoSessionIdleTimeoutRememberMe() != null) newRealm.setSsoSessionIdleTimeoutRememberMe(rep.getSsoSessionIdleTimeoutRememberMe());
if (rep.getOfflineSessionIdleTimeout() != null)
newRealm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout());
else newRealm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
@@ -916,6 +918,8 @@ public class RepresentationToModel {
realm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow());
if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
+ if (rep.getSsoSessionIdleTimeoutRememberMe() != null) realm.setSsoSessionIdleTimeoutRememberMe(rep.getSsoSessionIdleTimeoutRememberMe());
+ if (rep.getSsoSessionMaxLifespanRememberMe() != null) realm.setSsoSessionMaxLifespanRememberMe(rep.getSsoSessionMaxLifespanRememberMe());
if (rep.getOfflineSessionIdleTimeout() != null)
realm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout());
// KEYCLOAK-7688 Offline Session Max for Offline Token
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
index f597d5c..dcb7ced 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
@@ -72,7 +72,8 @@ public class SessionsBean {
}
public Date getExpires() {
- int max = session.getStarted() + realm.getSsoSessionMaxLifespan();
+ int maxLifespan = session.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
+ int max = session.getStarted() + maxLifespan;
return Time.toDate(max);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index a007c71..fc3c584 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -656,13 +656,15 @@ public class TokenManager {
int expiration;
if (tokenLifespan == -1) {
- expiration = userSession.getStarted() + realm.getSsoSessionMaxLifespan();
+ expiration = userSession.getStarted() + (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
+ realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan());
} else {
expiration = Time.currentTime() + tokenLifespan;
}
if (!userSession.isOffline()) {
- int sessionExpires = userSession.getStarted() + realm.getSsoSessionMaxLifespan();
+ int sessionExpires = userSession.getStarted() + (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
+ realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan());
expiration = expiration <= sessionExpires ? expiration : sessionExpires;
}
@@ -756,8 +758,10 @@ public class TokenManager {
}
private int getRefreshExpiration() {
- int sessionExpires = userSession.getStarted() + realm.getSsoSessionMaxLifespan();
- int expiration = Time.currentTime() + realm.getSsoSessionIdleTimeout();
+ int sessionExpires = userSession.getStarted() + (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
+ realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan());
+ int expiration = Time.currentTime() + (userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0 ?
+ realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout());
return expiration <= sessionExpires ? expiration : sessionExpires;
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index d8866cc..eac4cb7 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -130,12 +130,16 @@ public class AuthenticationManager {
return false;
}
int currentTime = Time.currentTime();
- int max = userSession.getStarted() + realm.getSsoSessionMaxLifespan();
// Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed
- int maxIdle = realm.getSsoSessionIdleTimeout() + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
-
- return userSession.getLastSessionRefresh() + maxIdle > currentTime && max > currentTime;
+ int maxIdle = (userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0 ?
+ realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout()) + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
+ int maxLifespan = userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
+ realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
+
+ boolean sessionMaxOk = userSession.getStarted() + maxLifespan > currentTime;
+ boolean sessionIdleOk = userSession.getLastSessionRefresh() + maxIdle > currentTime;
+ return sessionIdleOk && sessionMaxOk;
}
public static boolean isOfflineSessionValid(RealmModel realm, UserSessionModel userSession) {
@@ -576,10 +580,14 @@ public class AuthenticationManager {
token.issuedNow();
token.subject(user.getId());
token.issuer(issuer);
+
if (session != null) {
token.setSessionState(session.getId());
}
- if (realm.getSsoSessionMaxLifespan() > 0) {
+
+ if (session != null && session.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0) {
+ token.expiration(Time.currentTime() + realm.getSsoSessionMaxLifespanRememberMe());
+ } else if (realm.getSsoSessionMaxLifespan() > 0) {
token.expiration(Time.currentTime() + realm.getSsoSessionMaxLifespan());
}
@@ -601,7 +609,7 @@ public class AuthenticationManager {
boolean secureOnly = realm.getSslRequired().isRequired(connection);
int maxAge = NewCookie.DEFAULT_MAX_AGE;
if (session != null && session.isRememberMe()) {
- maxAge = realm.getSsoSessionMaxLifespan();
+ maxAge = realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
}
logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge);
CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true);
@@ -613,7 +621,8 @@ public class AuthenticationManager {
}
// THIS SHOULD NOT BE A HTTPONLY COOKIE! It is used for OpenID Connect Iframe Session support!
// Max age should be set to the max lifespan of the session as it's used to invalidate old-sessions on re-login
- CookieHelper.addCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, realm.getSsoSessionMaxLifespan(), secureOnly, false);
+ int sessionCookieMaxAge = session.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
+ CookieHelper.addCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, sessionCookieMaxAge, secureOnly, false);
P3PHelper.addP3PHeader(keycloakSession);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 81bcd04..f7d8c55 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -227,12 +227,26 @@ public class OAuthClient {
return doLogin(user.getUsername(), getPasswordOf(user));
}
+ public AuthorizationEndpointResponse doRememberMeLogin(String username, String password) {
+ openLoginForm();
+ fillLoginForm(username, password, true);
+
+ return new AuthorizationEndpointResponse(this);
+ }
+
public void fillLoginForm(String username, String password) {
+ this.fillLoginForm(username, password, false);
+ }
+
+ public void fillLoginForm(String username, String password, boolean rememberMe) {
WaitUtils.waitForPageToLoad();
String src = driver.getPageSource();
try {
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
+ if (rememberMe) {
+ driver.findElement(By.id("rememberMe")).click();
+ }
driver.findElement(By.name("login")).click();
} catch (Throwable t) {
System.err.println(src);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
index f619666..40cf19c 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
@@ -364,6 +364,8 @@ public class RealmTest extends AbstractAdminTest {
RealmRepresentation rep = realm.toRepresentation();
rep.setSsoSessionIdleTimeout(123);
rep.setSsoSessionMaxLifespan(12);
+ rep.setSsoSessionIdleTimeoutRememberMe(33);
+ rep.setSsoSessionMaxLifespanRememberMe(34);
rep.setAccessCodeLifespanLogin(1234);
rep.setActionTokenGeneratedByAdminLifespan(2345);
rep.setActionTokenGeneratedByUserLifespan(3456);
@@ -379,6 +381,8 @@ public class RealmTest extends AbstractAdminTest {
assertEquals(123, rep.getSsoSessionIdleTimeout().intValue());
assertEquals(12, rep.getSsoSessionMaxLifespan().intValue());
+ assertEquals(33, rep.getSsoSessionIdleTimeoutRememberMe().intValue());
+ assertEquals(34, rep.getSsoSessionMaxLifespanRememberMe().intValue());
assertEquals(1234, rep.getAccessCodeLifespanLogin().intValue());
assertEquals(2345, rep.getActionTokenGeneratedByAdminLifespan().intValue());
assertEquals(3456, rep.getActionTokenGeneratedByUserLifespan().intValue());
@@ -571,6 +575,8 @@ public class RealmTest extends AbstractAdminTest {
if (realm.getAccessTokenLifespanForImplicitFlow() != null) assertEquals(realm.getAccessTokenLifespanForImplicitFlow(), storedRealm.getAccessTokenLifespanForImplicitFlow());
if (realm.getSsoSessionIdleTimeout() != null) assertEquals(realm.getSsoSessionIdleTimeout(), storedRealm.getSsoSessionIdleTimeout());
if (realm.getSsoSessionMaxLifespan() != null) assertEquals(realm.getSsoSessionMaxLifespan(), storedRealm.getSsoSessionMaxLifespan());
+ if (realm.getSsoSessionIdleTimeoutRememberMe() != null) Assert.assertEquals(realm.getSsoSessionIdleTimeoutRememberMe(), storedRealm.getSsoSessionIdleTimeoutRememberMe());
+ if (realm.getSsoSessionMaxLifespanRememberMe() != null) Assert.assertEquals(realm.getSsoSessionMaxLifespanRememberMe(), storedRealm.getSsoSessionMaxLifespanRememberMe());
if (realm.getRequiredCredentials() != null) {
assertNotNull(storedRealm.getRequiredCredentials());
for (String cred : realm.getRequiredCredentials()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
index 17cd9b6..e8ae1b7 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
@@ -82,6 +82,10 @@ public class ExportImportUtil {
Assert.assertTrue(realm.isVerifyEmail());
Assert.assertEquals((Integer)3600000, realm.getOfflineSessionIdleTimeout());
Assert.assertEquals((Integer)1500, realm.getAccessTokenLifespanForImplicitFlow());
+ Assert.assertEquals((Integer)1800, realm.getSsoSessionIdleTimeout());
+ Assert.assertEquals((Integer)36000, realm.getSsoSessionMaxLifespan());
+ Assert.assertEquals((Integer)3600, realm.getSsoSessionIdleTimeoutRememberMe());
+ Assert.assertEquals((Integer)172800, realm.getSsoSessionMaxLifespanRememberMe());
Set<String> creds = realm.getRequiredCredentials();
Assert.assertEquals(1, creds.size());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 7b946dd..17c5724 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -30,6 +30,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.Constants;
+import org.keycloak.models.utils.SessionTimeoutHelper;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
@@ -526,8 +527,14 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
}
private void setRememberMe(boolean enabled) {
+ this.setRememberMe(enabled, null, null);
+ }
+
+ private void setRememberMe(boolean enabled, Integer idleTimeout, Integer maxLifespan) {
RealmRepresentation rep = adminClient.realm("test").toRepresentation();
rep.setRememberMe(enabled);
+ rep.setSsoSessionIdleTimeoutRememberMe(idleTimeout);
+ rep.setSsoSessionMaxLifespanRememberMe(maxLifespan);
adminClient.realm("test").update(rep);
}
@@ -749,4 +756,62 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
}
+ @Test
+ public void loginRememberMeExpiredIdle() throws Exception {
+ setRememberMe(true, 1, null);
+
+ try {
+ // login form shown after redirect from app
+ oauth.clientId("test-app");
+ oauth.redirectUri(OAuthClient.APP_ROOT + "/auth");
+ oauth.openLoginForm();
+
+ assertTrue(loginPage.isCurrent());
+ loginPage.setRememberMe(true);
+ loginPage.login("test-user@localhost", "password");
+
+ // sucessful login - app page should be on display.
+ events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+ appPage.assertCurrent();
+
+ // expire idle timeout using the timeout window.
+ setTimeOffset(2 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
+
+ // trying to open the account page with an expired idle timeout should redirect back to the login page.
+ appPage.openAccount();
+ loginPage.assertCurrent();
+ } finally {
+ setRememberMe(false);
+ }
+ }
+
+ @Test
+ public void loginRememberMeExpiredMaxLifespan() throws Exception {
+ setRememberMe(true, null, 1);
+
+ try {
+ // login form shown after redirect from app
+ oauth.clientId("test-app");
+ oauth.redirectUri(OAuthClient.APP_ROOT + "/auth");
+ oauth.openLoginForm();
+
+ assertTrue(loginPage.isCurrent());
+ loginPage.setRememberMe(true);
+ loginPage.login("test-user@localhost", "password");
+
+ // sucessful login - app page should be on display.
+ events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+ appPage.assertCurrent();
+
+ // expire the max lifespan.
+ setTimeOffset(2);
+
+ // trying to open the account page with an expired lifespan should redirect back to the login page.
+ appPage.openAccount();
+ loginPage.assertCurrent();
+ } finally {
+ setRememberMe(false);
+ }
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index 8facb41..d74c985 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -593,6 +593,64 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
}
@Test
+ public void testUserSessionRefreshAndIdleRememberMe() throws Exception {
+ RealmResource testRealm = adminClient.realm("test");
+ RealmRepresentation testRealmRep = testRealm.toRepresentation();
+ Boolean previousRememberMe = testRealmRep.isRememberMe();
+ int originalIdleRememberMe = testRealmRep.getSsoSessionIdleTimeoutRememberMe();
+
+ try {
+ testRealmRep.setRememberMe(true);
+ testRealm.update(testRealmRep);
+
+ oauth.doRememberMeLogin("test-user@localhost", "password");
+
+ EventRepresentation loginEvent = events.expectLogin().assertEvent();
+
+ String sessionId = loginEvent.getSessionId();
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+ events.poll();
+
+ String refreshId = oauth.parseRefreshToken(tokenResponse.getRefreshToken()).getId();
+ int last = testingClient.testing().getLastSessionRefresh("test", sessionId, false);
+
+ setTimeOffset(2);
+ tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
+ oauth.verifyToken(tokenResponse.getAccessToken());
+ oauth.parseRefreshToken(tokenResponse.getRefreshToken());
+ assertEquals(200, tokenResponse.getStatusCode());
+
+ int next = testingClient.testing().getLastSessionRefresh("test", sessionId, false);
+ Assert.assertNotEquals(last, next);
+
+ testRealmRep.setSsoSessionIdleTimeoutRememberMe(1);
+ testRealm.update(testRealmRep);
+
+ events.clear();
+ // Needs to add some additional time due the tollerance allowed by IDLE_TIMEOUT_WINDOW_SECONDS
+ setTimeOffset(6 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
+ tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
+
+ // test idle remember me timeout
+ assertEquals(400, tokenResponse.getStatusCode());
+ assertNull(tokenResponse.getAccessToken());
+ assertNull(tokenResponse.getRefreshToken());
+
+ events.expectRefresh(refreshId, sessionId).error(Errors.INVALID_TOKEN);
+ events.clear();
+
+ } finally {
+ testRealmRep.setSsoSessionIdleTimeoutRememberMe(originalIdleRememberMe);
+ testRealmRep.setRememberMe(previousRememberMe);
+ testRealm.update(testRealmRep);
+ setTimeOffset(0);
+ }
+ }
+
+ @Test
public void refreshTokenUserSessionMaxLifespan() throws Exception {
oauth.doLogin("test-user@localhost", "password");
@@ -628,6 +686,57 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
setTimeOffset(0);
}
+ /**
+ * KEYCLOAK-1267
+ * @throws Exception
+ */
+ @Test
+ public void refreshTokenUserSessionMaxLifespanWithRememberMe() throws Exception {
+
+ RealmResource testRealm = adminClient.realm("test");
+ RealmRepresentation testRealmRep = testRealm.toRepresentation();
+ Boolean previousRememberMe = testRealmRep.isRememberMe();
+ int previousSsoMaxLifespanRememberMe = testRealmRep.getSsoSessionMaxLifespanRememberMe();
+
+ try {
+ testRealmRep.setRememberMe(true);
+ testRealm.update(testRealmRep);
+
+ oauth.doRememberMeLogin("test-user@localhost", "password");
+
+ EventRepresentation loginEvent = events.expectLogin().assertEvent();
+
+ String sessionId = loginEvent.getSessionId();
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+ events.poll();
+
+ String refreshId = oauth.parseRefreshToken(tokenResponse.getRefreshToken()).getId();
+
+ testRealmRep.setSsoSessionMaxLifespanRememberMe(1);
+ testRealm.update(testRealmRep);
+
+ setTimeOffset(2);
+
+ tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
+
+ assertEquals(400, tokenResponse.getStatusCode());
+ assertNull(tokenResponse.getAccessToken());
+ assertNull(tokenResponse.getRefreshToken());
+
+ events.expectRefresh(refreshId, sessionId).error(Errors.INVALID_TOKEN);
+ events.clear();
+
+ } finally {
+ testRealmRep.setSsoSessionMaxLifespanRememberMe(previousSsoMaxLifespanRememberMe);
+ testRealmRep.setRememberMe(previousRememberMe);
+ testRealm.update(testRealmRep);
+ setTimeOffset(0);
+ }
+ }
+
@Test
public void testCheckSsl() throws Exception {
Client client = ClientBuilder.newClient();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
index 50cdcba..cb5a0cf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
@@ -198,6 +198,16 @@ public class RealmBuilder {
return this;
}
+ public RealmBuilder ssoSessionIdleTimeoutRememberMe(int ssoSessionIdleTimeoutRememberMe){
+ rep.setSsoSessionIdleTimeoutRememberMe(ssoSessionIdleTimeoutRememberMe);
+ return this;
+ }
+
+ public RealmBuilder ssoSessionMaxLifespanRememberMe(int ssoSessionMaxLifespanRememberMe){
+ rep.setSsoSessionMaxLifespanRememberMe(ssoSessionMaxLifespanRememberMe);
+ return this;
+ }
+
public RealmBuilder accessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
rep.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction);
return this;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
index a15128c..0237bf2 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
@@ -3,6 +3,10 @@
"enabled": true,
"accessTokenLifespan": 6000,
"accessTokenLifespanForImplicitFlow": 1500,
+ "ssoSessionIdleTimeout": 1800,
+ "ssoSessionMaxLifespan": 36000,
+ "ssoSessionIdleTimeoutRememberMe": 3600,
+ "ssoSessionMaxLifespanRememberMe": 172800,
"accessCodeLifespan": 30,
"accessCodeLifespanUserAction": 600,
"offlineSessionIdleTimeout": 3600000,
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 175a5a1..2a9a396 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -109,6 +109,10 @@ days=Days
sso-session-max=SSO Session Max
sso-session-idle.tooltip=Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired.
sso-session-max.tooltip=Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired.
+sso-session-idle-remember-me=SSO Session Idle Remember Me
+sso-session-idle-remember-me.tooltip=Time a remember me session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired. If not set it uses the standard SSO Session Idle value.
+sso-session-max-remember-me=SSO Session Max Remember Me
+sso-session-max-remember-me.tooltip=Max time before a session is expired when the user has set the remember me option. Tokens and browser sessions are invalidated when a session is expired. If not set it uses the standard SSO Session Max value.
offline-session-idle=Offline Session Idle
offline-session-idle.tooltip=Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire.
realm-detail.hostname=Hostname
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 802f1ea..936a7c1 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1087,6 +1087,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit2.asUnit(realm.accessTokenLifespanForImplicitFlow);
$scope.realm.ssoSessionIdleTimeout = TimeUnit2.asUnit(realm.ssoSessionIdleTimeout);
$scope.realm.ssoSessionMaxLifespan = TimeUnit2.asUnit(realm.ssoSessionMaxLifespan);
+ $scope.realm.ssoSessionIdleTimeoutRememberMe = TimeUnit2.asUnit(realm.ssoSessionIdleTimeoutRememberMe);
+ $scope.realm.ssoSessionMaxLifespanRememberMe = TimeUnit2.asUnit(realm.ssoSessionMaxLifespanRememberMe);
$scope.realm.offlineSessionIdleTimeout = TimeUnit2.asUnit(realm.offlineSessionIdleTimeout);
// KEYCLOAK-7688 Offline Session Max for Offline Token
$scope.realm.offlineSessionMaxLifespan = TimeUnit2.asUnit(realm.offlineSessionMaxLifespan);
@@ -1140,6 +1142,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.realm.accessTokenLifespanForImplicitFlow = $scope.realm.accessTokenLifespanForImplicitFlow.toSeconds();
$scope.realm.ssoSessionIdleTimeout = $scope.realm.ssoSessionIdleTimeout.toSeconds();
$scope.realm.ssoSessionMaxLifespan = $scope.realm.ssoSessionMaxLifespan.toSeconds();
+ $scope.realm.ssoSessionIdleTimeoutRememberMe = $scope.realm.ssoSessionIdleTimeoutRememberMe.toSeconds();
+ $scope.realm.ssoSessionMaxLifespanRememberMe = $scope.realm.ssoSessionMaxLifespanRememberMe.toSeconds();
$scope.realm.offlineSessionIdleTimeout = $scope.realm.offlineSessionIdleTimeout.toSeconds();
// KEYCLOAK-7688 Offline Session Max for Offline Token
$scope.realm.offlineSessionMaxLifespan = $scope.realm.offlineSessionMaxLifespan.toSeconds();
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
index fbaa037..f31ca1b 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
@@ -72,6 +72,38 @@
</div>
<div class="form-group">
+ <label class="col-md-2 control-label" for="ssoSessionIdleTimeoutRememberMe">{{:: 'sso-session-idle-remember-me' | translate}}</label>
+
+ <div class="col-md-6 time-selector">
+ <input class="form-control" type="number" required min="0"
+ max="31536000" data-ng-model="realm.ssoSessionIdleTimeoutRememberMe.time"
+ id="ssoSessionIdleTimeoutRememberMe" name="ssoSessionIdleTimeoutRememberMe"/>
+ <select class="form-control" name="ssoSessionIdleTimeoutRememberMe" data-ng-model="realm.ssoSessionIdleTimeoutRememberMe.unit">
+ <option value="Minutes">{{:: 'minutes' | translate}}</option>
+ <option value="Hours">{{:: 'hours' | translate}}</option>
+ <option value="Days">{{:: 'days' | translate}}</option>
+ </select>
+ </div>
+ <kc-tooltip>{{:: 'sso-session-idle-remember-me.tooltip' | translate}}</kc-tooltip>
+ </div>
+
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="ssoSessionMaxLifespanRememberMe">{{:: 'sso-session-max-remember-me' | translate}}</label>
+
+ <div class="col-md-6 time-selector">
+ <input class="form-control" type="number" required min="0"
+ max="31536000" data-ng-model="realm.ssoSessionMaxLifespanRememberMe.time"
+ id="ssoSessionMaxLifespanRememberMe" name="ssoSessionMaxLifespanRememberMe"/>
+ <select class="form-control" name="ssoSessionMaxLifespanRememberMeUnit" data-ng-model="realm.ssoSessionMaxLifespanRememberMe.unit">
+ <option value="Minutes">{{:: 'minutes' | translate}}</option>
+ <option value="Hours">{{:: 'hours' | translate}}</option>
+ <option value="Days">{{:: 'days' | translate}}</option>
+ </select>
+ </div>
+ <kc-tooltip>{{:: 'sso-session-max-remember-me.tooltip' | translate}}</kc-tooltip>
+ </div>
+
+ <div class="form-group">
<label class="col-md-2 control-label" for="offlineSessionIdleTimeout">{{:: 'offline-session-idle' | translate}}</label>
<div class="col-md-6 time-selector">