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 200e4e1899..2ecbae4bff 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,9 @@ public class RealmRepresentation { protected Integer ssoSessionIdleTimeout; protected Integer ssoSessionMaxLifespan; protected Integer offlineSessionIdleTimeout; + // KEYCLOAK-7688 Offline Session Max for Offline Token + protected Boolean offlineSessionMaxLifespanEnabled; + protected Integer offlineSessionMaxLifespan; protected Integer accessCodeLifespan; protected Integer accessCodeLifespanUserAction; protected Integer accessCodeLifespanLogin; @@ -296,6 +299,23 @@ public class RealmRepresentation { this.offlineSessionIdleTimeout = offlineSessionIdleTimeout; } + // KEYCLOAK-7688 Offline Session Max for Offline Token + public Boolean getOfflineSessionMaxLifespanEnabled() { + return offlineSessionMaxLifespanEnabled; + } + + public void setOfflineSessionMaxLifespanEnabled(Boolean offlineSessionMaxLifespanEnabled) { + this.offlineSessionMaxLifespanEnabled = offlineSessionMaxLifespanEnabled; + } + + public Integer getOfflineSessionMaxLifespan() { + return offlineSessionMaxLifespan; + } + + public void setOfflineSessionMaxLifespan(Integer offlineSessionMaxLifespan) { + this.offlineSessionMaxLifespan = offlineSessionMaxLifespan; + } + public List getScopeMappings() { return scopeMappings; } 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 9a058b57ed..386d974e53 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,31 @@ public class RealmAdapter implements CachedRealmModel { updated.setOfflineSessionIdleTimeout(seconds); } + // KEYCLOAK-7688 Offline Session Max for Offline Token + @Override + public boolean isOfflineSessionMaxLifespanEnabled() { + if (isUpdated()) return updated.isOfflineSessionMaxLifespanEnabled(); + return cached.isOfflineSessionMaxLifespanEnabled(); + } + + @Override + public void setOfflineSessionMaxLifespanEnabled(boolean offlineSessionMaxLifespanEnabled) { + getDelegateForUpdate(); + updated.setOfflineSessionMaxLifespanEnabled(offlineSessionMaxLifespanEnabled); + } + + @Override + public int getOfflineSessionMaxLifespan() { + if (isUpdated()) return updated.getOfflineSessionMaxLifespan(); + return cached.getOfflineSessionMaxLifespan(); + } + + @Override + public void setOfflineSessionMaxLifespan(int seconds) { + getDelegateForUpdate(); + updated.setOfflineSessionMaxLifespan(seconds); + } + @Override public int getAccessTokenLifespan() { if (isUpdated()) return updated.getAccessTokenLifespan(); 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 7f9cbedf53..e16b09238e 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,9 @@ public class CachedRealm extends AbstractExtendableRevisioned { protected int ssoSessionIdleTimeout; protected int ssoSessionMaxLifespan; protected int offlineSessionIdleTimeout; + // KEYCLOAK-7688 Offline Session Max for Offline Token + protected boolean offlineSessionMaxLifespanEnabled; + protected int offlineSessionMaxLifespan; protected int accessTokenLifespan; protected int accessTokenLifespanForImplicitFlow; protected int accessCodeLifespan; @@ -181,6 +184,9 @@ public class CachedRealm extends AbstractExtendableRevisioned { ssoSessionIdleTimeout = model.getSsoSessionIdleTimeout(); ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan(); offlineSessionIdleTimeout = model.getOfflineSessionIdleTimeout(); + // KEYCLOAK-7688 Offline Session Max for Offline Token + offlineSessionMaxLifespanEnabled = model.isOfflineSessionMaxLifespanEnabled(); + offlineSessionMaxLifespan = model.getOfflineSessionMaxLifespan(); accessTokenLifespan = model.getAccessTokenLifespan(); accessTokenLifespanForImplicitFlow = model.getAccessTokenLifespanForImplicitFlow(); accessCodeLifespan = model.getAccessCodeLifespan(); @@ -405,6 +411,15 @@ public class CachedRealm extends AbstractExtendableRevisioned { return offlineSessionIdleTimeout; } + // KEYCLOAK-7688 Offline Session Max for Offline Token + public boolean isOfflineSessionMaxLifespanEnabled() { + return offlineSessionMaxLifespanEnabled; + } + + public int getOfflineSessionMaxLifespan() { + return offlineSessionMaxLifespan; + } + public int getAccessTokenLifespan() { return accessTokenLifespan; } 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 2795c39880..f006180e7b 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 @@ -466,6 +466,27 @@ public class RealmAdapter implements RealmModel, JpaModel { realm.setOfflineSessionIdleTimeout(seconds); } + // KEYCLOAK-7688 Offline Session Max for Offline Token + @Override + public boolean isOfflineSessionMaxLifespanEnabled() { + return getAttribute(RealmAttributes.OFFLINE_SESSION_MAX_LIFESPAN_ENABLED, false); + } + + @Override + public void setOfflineSessionMaxLifespanEnabled(boolean offlineSessionMaxLifespanEnabled) { + setAttribute(RealmAttributes.OFFLINE_SESSION_MAX_LIFESPAN_ENABLED, offlineSessionMaxLifespanEnabled); + } + + @Override + public int getOfflineSessionMaxLifespan() { + return getAttribute(RealmAttributes.OFFLINE_SESSION_MAX_LIFESPAN, Constants.DEFAULT_OFFLINE_SESSION_MAX_LIFESPAN); + } + + @Override + public void setOfflineSessionMaxLifespan(int seconds) { + setAttribute(RealmAttributes.OFFLINE_SESSION_MAX_LIFESPAN, seconds); + } + @Override public int getAccessCodeLifespan() { return realm.getAccessCodeLifespan(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java index 6ee1074c6c..59f5452050 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java @@ -30,4 +30,8 @@ public interface RealmAttributes { String ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN = "actionTokenGeneratedByUserLifespan"; + // KEYCLOAK-7688 Offline Session Max for Offline Token + String OFFLINE_SESSION_MAX_LIFESPAN_ENABLED = "offlineSessionMaxLifespanEnabled"; + + String OFFLINE_SESSION_MAX_LIFESPAN = "offlineSessionMaxLifespan"; } diff --git a/server-spi-private/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java index 8a682dcb6b..38e25c7809 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/Constants.java +++ b/server-spi-private/src/main/java/org/keycloak/models/Constants.java @@ -50,6 +50,9 @@ public interface Constants { int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900; // 30 days int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000; + // KEYCLOAK-7688 Offline Session Max for Offline Token + // 60 days + int DEFAULT_OFFLINE_SESSION_MAX_LIFESPAN = 5184000; String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY"; String VERIFY_EMAIL_CODE = "VERIFY_EMAIL_CODE"; 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 3fb75741a9..24abbe46a9 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 @@ -265,6 +265,9 @@ public class ModelToRepresentation { rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout()); rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan()); rep.setOfflineSessionIdleTimeout(realm.getOfflineSessionIdleTimeout()); + // KEYCLOAK-7688 Offline Session Max for Offline Token + rep.setOfflineSessionMaxLifespanEnabled(realm.isOfflineSessionMaxLifespanEnabled()); + rep.setOfflineSessionMaxLifespan(realm.getOfflineSessionMaxLifespan()); rep.setAccessCodeLifespan(realm.getAccessCodeLifespan()); rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction()); rep.setAccessCodeLifespanLogin(realm.getAccessCodeLifespanLogin()); 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 f3f57b8179..64e471968a 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 @@ -196,6 +196,14 @@ public class RepresentationToModel { newRealm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout()); else newRealm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT); + // KEYCLOAK-7688 Offline Session Max for Offline Token + if (rep.getOfflineSessionMaxLifespanEnabled() != null) newRealm.setOfflineSessionMaxLifespanEnabled(rep.getOfflineSessionMaxLifespanEnabled()); + else newRealm.setOfflineSessionMaxLifespanEnabled(false); + + if (rep.getOfflineSessionMaxLifespan() != null) + newRealm.setOfflineSessionMaxLifespan(rep.getOfflineSessionMaxLifespan()); + else newRealm.setOfflineSessionMaxLifespan(Constants.DEFAULT_OFFLINE_SESSION_MAX_LIFESPAN); + if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan()); else newRealm.setAccessCodeLifespan(60); @@ -906,6 +914,10 @@ public class RepresentationToModel { if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); if (rep.getOfflineSessionIdleTimeout() != null) realm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout()); + // KEYCLOAK-7688 Offline Session Max for Offline Token + if (rep.getOfflineSessionMaxLifespanEnabled() != null) realm.setOfflineSessionMaxLifespanEnabled(rep.getOfflineSessionMaxLifespanEnabled()); + if (rep.getOfflineSessionMaxLifespan() != null) + realm.setOfflineSessionMaxLifespan(rep.getOfflineSessionMaxLifespan()); if (rep.getRequiredCredentials() != null) { realm.updateRequiredCredentials(rep.getRequiredCredentials()); } 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 581b59b0c7..3a76c1f8dc 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -179,6 +179,13 @@ public interface RealmModel extends RoleContainerModel { int getAccessTokenLifespan(); + // KEYCLOAK-7688 Offline Session Max for Offline Token + boolean isOfflineSessionMaxLifespanEnabled(); + void setOfflineSessionMaxLifespanEnabled(boolean offlineSessionMaxLifespanEnabled); + + int getOfflineSessionMaxLifespan(); + void setOfflineSessionMaxLifespan(int seconds); + void setAccessTokenLifespan(int seconds); int getAccessTokenLifespanForImplicitFlow(); diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java index 7de8ee4622..344203b18e 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java @@ -76,6 +76,9 @@ public class ApplianceBootstrap { realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT); realm.setSsoSessionMaxLifespan(36000); realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT); + // KEYCLOAK-7688 Offline Session Max for Offline Token + realm.setOfflineSessionMaxLifespanEnabled(false); + realm.setOfflineSessionMaxLifespan(Constants.DEFAULT_OFFLINE_SESSION_MAX_LIFESPAN); realm.setAccessCodeLifespan(60); realm.setAccessCodeLifespanUserAction(300); realm.setAccessCodeLifespanLogin(1800); 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 3753bc2bc2..60ae93f886 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -118,11 +118,16 @@ public class AuthenticationManager { return false; } int currentTime = Time.currentTime(); - // 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.getOfflineSessionIdleTimeout() + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS; - return userSession.getLastSessionRefresh() + maxIdle > currentTime; + // KEYCLOAK-7688 Offline Session Max for Offline Token + if (realm.isOfflineSessionMaxLifespanEnabled()) { + int max = userSession.getStarted() + realm.getOfflineSessionMaxLifespan(); + return userSession.getLastSessionRefresh() + maxIdle > currentTime && max > currentTime; + } else { + return userSession.getLastSessionRefresh() + maxIdle > currentTime; + } } public static void expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java index 0b91453e7e..1ffc4ed444 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java @@ -30,11 +30,13 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RoleResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.common.constants.ServiceAccountConstants; +import org.keycloak.common.util.Time; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.models.AdminRoles; import org.keycloak.models.Constants; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.SessionTimeoutHelper; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; import org.keycloak.representations.AccessToken; import org.keycloak.representations.RefreshToken; @@ -661,4 +663,88 @@ public class OfflineTokenTest extends AbstractKeycloakTest { assertNotEquals(offlineToken.getSessionState(), offlineToken2.getSessionState()); } + // KEYCLOAK-7688 Offline Session Max for Offline Token + private int[] changeOfflineSessionSettings(boolean isEnabled, int sessionMax, int sessionIdle) { + int prev[] = new int[2]; + RealmRepresentation rep = adminClient.realm("test").toRepresentation(); + prev[0] = rep.getOfflineSessionMaxLifespan().intValue(); + prev[1] = rep.getOfflineSessionIdleTimeout().intValue(); + RealmBuilder realmBuilder = RealmBuilder.create(); + realmBuilder.offlineSessionMaxLifespanEnabled(isEnabled).offlineSessionMaxLifespan(sessionMax).offlineSessionIdleTimeout(sessionIdle); + adminClient.realm("test").update(realmBuilder.build()); + return prev; + } + + @Test + public void offlineTokenBrowserFlowMaxLifespanExpired() throws Exception { + // expect that offline session expired by max lifespan + final int MAX_LIFESPAN = 3600; + final int IDLE_LIFESPAN = 6000; + testOfflineSessionExpiration(IDLE_LIFESPAN, MAX_LIFESPAN, MAX_LIFESPAN + 60); + } + + @Test + public void offlineTokenBrowserFlowIdleTimeExpired() throws Exception { + // expect that offline session expired by idle time + final int MAX_LIFESPAN = 3000; + final int IDLE_LIFESPAN = 600; + // Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed + testOfflineSessionExpiration(IDLE_LIFESPAN, MAX_LIFESPAN, IDLE_LIFESPAN + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS + 60); + } + + private void testOfflineSessionExpiration(int idleTime, int maxLifespan, int offset) { + int prev[] = null; + try { + prev = changeOfflineSessionSettings(true, maxLifespan, idleTime); + + oauth.scope(OAuth2Constants.OFFLINE_ACCESS); + oauth.clientId("offline-client"); + oauth.redirectUri(offlineClientAppUri); + oauth.doLogin("test-user@localhost", "password"); + + EventRepresentation loginEvent = events.expectLogin() + .client("offline-client") + .detail(Details.REDIRECT_URI, offlineClientAppUri) + .assertEvent(); + + final String sessionId = loginEvent.getSessionId(); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "secret1"); + String offlineTokenString = tokenResponse.getRefreshToken(); + RefreshToken offlineToken = oauth.verifyRefreshToken(offlineTokenString); + + assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType()); + + tokenResponse = oauth.doRefreshTokenRequest(offlineTokenString, "secret1"); + AccessToken refreshedToken = oauth.verifyToken(tokenResponse.getAccessToken()); + offlineTokenString = tokenResponse.getRefreshToken(); + offlineToken = oauth.verifyRefreshToken(offlineTokenString); + + Assert.assertEquals(200, tokenResponse.getStatusCode()); + Assert.assertEquals(sessionId, refreshedToken.getSessionState()); + + // wait to expire + setTimeOffset(offset); + + tokenResponse = oauth.doRefreshTokenRequest(offlineTokenString, "secret1"); + + Assert.assertEquals(400, tokenResponse.getStatusCode()); + assertEquals("invalid_grant", tokenResponse.getError()); + + // Assert userSession expired + testingClient.testing().removeExpired("test"); + try { + testingClient.testing().removeUserSession("test", sessionId); + } catch (NotFoundException nfe) { + // Ignore + } + + setTimeOffset(0); + + } finally { + changeOfflineSessionSettings(false, prev[0], prev[1]); + } + } } 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 243b648063..50cdcbad60 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 @@ -225,4 +225,20 @@ public class RealmBuilder { rep.getGroups().add(group); return this; } + + // KEYCLOAK-7688 Offline Session Max for Offline Token + public RealmBuilder offlineSessionIdleTimeout(int offlineSessionIdleTimeout) { + rep.setOfflineSessionIdleTimeout(offlineSessionIdleTimeout); + return this; + } + + public RealmBuilder offlineSessionMaxLifespan(int offlineSessionMaxLifespan) { + rep.setOfflineSessionMaxLifespan(offlineSessionMaxLifespan); + return this; + } + + public RealmBuilder offlineSessionMaxLifespanEnabled(boolean offlineSessionMaxLifespanEnabled) { + rep.setOfflineSessionMaxLifespanEnabled(offlineSessionMaxLifespanEnabled); + return this; + } } 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 dd7d013c90..c13aecdc23 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 @@ -108,6 +108,13 @@ sso-session-idle.tooltip=Time a session is allowed to be idle before it expires. sso-session-max.tooltip=Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired. 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. + +## KEYCLOAK-7688 Offline Session Max for Offline Token +offline-session-max-limited=Offline Session Max Limited +offline-session-max-limited.tooltip=Enable Offline Session Max. +offline-session-max=Offline Session Max +offline-session-max.tooltip=Max time before an offline session is expired regardless of activity. + access-token-lifespan=Access Token Lifespan access-token-lifespan.tooltip=Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout. access-token-lifespan-for-implicit-flow=Access Token Lifespan For Implicit Flow 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 eee2f445ba..44a74a555c 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 @@ -1090,6 +1090,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $scope.realm.ssoSessionIdleTimeout = TimeUnit2.asUnit(realm.ssoSessionIdleTimeout); $scope.realm.ssoSessionMaxLifespan = TimeUnit2.asUnit(realm.ssoSessionMaxLifespan); $scope.realm.offlineSessionIdleTimeout = TimeUnit2.asUnit(realm.offlineSessionIdleTimeout); + // KEYCLOAK-7688 Offline Session Max for Offline Token + $scope.realm.offlineSessionMaxLifespan = TimeUnit2.asUnit(realm.offlineSessionMaxLifespan); $scope.realm.accessCodeLifespan = TimeUnit2.asUnit(realm.accessCodeLifespan); $scope.realm.accessCodeLifespanLogin = TimeUnit2.asUnit(realm.accessCodeLifespanLogin); $scope.realm.accessCodeLifespanUserAction = TimeUnit2.asUnit(realm.accessCodeLifespanUserAction); @@ -1137,6 +1139,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $scope.realm.ssoSessionIdleTimeout = $scope.realm.ssoSessionIdleTimeout.toSeconds(); $scope.realm.ssoSessionMaxLifespan = $scope.realm.ssoSessionMaxLifespan.toSeconds(); $scope.realm.offlineSessionIdleTimeout = $scope.realm.offlineSessionIdleTimeout.toSeconds(); + // KEYCLOAK-7688 Offline Session Max for Offline Token + $scope.realm.offlineSessionMaxLifespan = $scope.realm.offlineSessionMaxLifespan.toSeconds(); $scope.realm.accessCodeLifespan = $scope.realm.accessCodeLifespan.toSeconds(); $scope.realm.accessCodeLifespanUserAction = $scope.realm.accessCodeLifespanUserAction.toSeconds(); $scope.realm.accessCodeLifespanLogin = $scope.realm.accessCodeLifespanLogin.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 3b05fdba1c..3887583826 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 @@ -74,6 +74,32 @@ {{:: 'offline-session-idle.tooltip' | translate}} + +
+ +
+ +
+ {{:: 'offline-session-max-limited.tooltip' | translate}} +
+
+ +
+ + +
+ {{:: 'offline-session-max.tooltip' | translate}} +
+