KEYCLOAK-12406 Add "Client Session Max" and "Client Session Idle" for OIDC

This commit is contained in:
Yoshiyuki Tabata 2019-12-09 17:09:33 +09:00 committed by Stian Thorgersen
parent 5b017e930d
commit 874642fe9e
19 changed files with 379 additions and 7 deletions

View file

@ -50,6 +50,8 @@ public class RealmRepresentation {
// KEYCLOAK-7688 Offline Session Max for Offline Token
protected Boolean offlineSessionMaxLifespanEnabled;
protected Integer offlineSessionMaxLifespan;
protected Integer clientSessionIdleTimeout;
protected Integer clientSessionMaxLifespan;
protected Integer accessCodeLifespan;
protected Integer accessCodeLifespanUserAction;
protected Integer accessCodeLifespanLogin;
@ -369,6 +371,22 @@ public class RealmRepresentation {
this.offlineSessionMaxLifespan = offlineSessionMaxLifespan;
}
public Integer getClientSessionIdleTimeout() {
return clientSessionIdleTimeout;
}
public void setClientSessionIdleTimeout(Integer clientSessionIdleTimeout) {
this.clientSessionIdleTimeout = clientSessionIdleTimeout;
}
public Integer getClientSessionMaxLifespan() {
return clientSessionMaxLifespan;
}
public void setClientSessionMaxLifespan(Integer clientSessionMaxLifespan) {
this.clientSessionMaxLifespan = clientSessionMaxLifespan;
}
public List<ScopeMappingRepresentation> getScopeMappings() {
return scopeMappings;
}

View file

@ -480,6 +480,32 @@ public class RealmAdapter implements CachedRealmModel {
updated.setOfflineSessionMaxLifespan(seconds);
}
@Override
public int getClientSessionIdleTimeout() {
if (isUpdated())
return updated.getClientSessionIdleTimeout();
return cached.getClientSessionIdleTimeout();
}
@Override
public void setClientSessionIdleTimeout(int seconds) {
getDelegateForUpdate();
updated.setClientSessionIdleTimeout(seconds);
}
@Override
public int getClientSessionMaxLifespan() {
if (isUpdated())
return updated.getClientSessionMaxLifespan();
return cached.getClientSessionMaxLifespan();
}
@Override
public void setClientSessionMaxLifespan(int seconds) {
getDelegateForUpdate();
updated.setClientSessionMaxLifespan(seconds);
}
@Override
public int getAccessTokenLifespan() {
if (isUpdated()) return updated.getAccessTokenLifespan();

View file

@ -86,6 +86,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
// KEYCLOAK-7688 Offline Session Max for Offline Token
protected boolean offlineSessionMaxLifespanEnabled;
protected int offlineSessionMaxLifespan;
protected int clientSessionIdleTimeout;
protected int clientSessionMaxLifespan;
protected int accessTokenLifespan;
protected int accessTokenLifespanForImplicitFlow;
protected int accessCodeLifespan;
@ -197,6 +199,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
// KEYCLOAK-7688 Offline Session Max for Offline Token
offlineSessionMaxLifespanEnabled = model.isOfflineSessionMaxLifespanEnabled();
offlineSessionMaxLifespan = model.getOfflineSessionMaxLifespan();
clientSessionIdleTimeout = model.getClientSessionIdleTimeout();
clientSessionMaxLifespan = model.getClientSessionMaxLifespan();
accessTokenLifespan = model.getAccessTokenLifespan();
accessTokenLifespanForImplicitFlow = model.getAccessTokenLifespanForImplicitFlow();
accessCodeLifespan = model.getAccessCodeLifespan();
@ -447,6 +451,14 @@ public class CachedRealm extends AbstractExtendableRevisioned {
return offlineSessionMaxLifespan;
}
public int getClientSessionIdleTimeout() {
return clientSessionIdleTimeout;
}
public int getClientSessionMaxLifespan() {
return clientSessionMaxLifespan;
}
public int getAccessTokenLifespan() {
return accessTokenLifespan;
}

View file

@ -523,6 +523,26 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
setAttribute(RealmAttributes.OFFLINE_SESSION_MAX_LIFESPAN, seconds);
}
@Override
public int getClientSessionIdleTimeout() {
return getAttribute(RealmAttributes.CLIENT_SESSION_IDLE_TIMEOUT, 0);
}
@Override
public void setClientSessionIdleTimeout(int seconds) {
setAttribute(RealmAttributes.CLIENT_SESSION_IDLE_TIMEOUT, seconds);
}
@Override
public int getClientSessionMaxLifespan() {
return getAttribute(RealmAttributes.CLIENT_SESSION_MAX_LIFESPAN, 0);
}
@Override
public void setClientSessionMaxLifespan(int seconds) {
setAttribute(RealmAttributes.CLIENT_SESSION_MAX_LIFESPAN, seconds);
}
@Override
public int getAccessCodeLifespan() {
return realm.getAccessCodeLifespan();

View file

@ -34,7 +34,8 @@ public interface RealmAttributes {
String OFFLINE_SESSION_MAX_LIFESPAN_ENABLED = "offlineSessionMaxLifespanEnabled";
String OFFLINE_SESSION_MAX_LIFESPAN = "offlineSessionMaxLifespan";
String CLIENT_SESSION_IDLE_TIMEOUT = "clientSessionIdleTimeout";
String CLIENT_SESSION_MAX_LIFESPAN = "clientSessionMaxLifespan";
String WEBAUTHN_POLICY_RP_ENTITY_NAME = "webAuthnPolicyRpEntityName";
String WEBAUTHN_POLICY_SIGNATURE_ALGORITHMS = "webAuthnPolicySignatureAlgorithms";

View file

@ -364,6 +364,8 @@ public class ModelToRepresentation {
// KEYCLOAK-7688 Offline Session Max for Offline Token
rep.setOfflineSessionMaxLifespanEnabled(realm.isOfflineSessionMaxLifespanEnabled());
rep.setOfflineSessionMaxLifespan(realm.getOfflineSessionMaxLifespan());
rep.setClientSessionIdleTimeout(realm.getClientSessionIdleTimeout());
rep.setClientSessionMaxLifespan(realm.getClientSessionMaxLifespan());
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
rep.setAccessCodeLifespanLogin(realm.getAccessCodeLifespanLogin());

View file

@ -220,6 +220,11 @@ public class RepresentationToModel {
newRealm.setOfflineSessionMaxLifespan(rep.getOfflineSessionMaxLifespan());
else newRealm.setOfflineSessionMaxLifespan(Constants.DEFAULT_OFFLINE_SESSION_MAX_LIFESPAN);
if (rep.getClientSessionIdleTimeout() != null)
newRealm.setClientSessionIdleTimeout(rep.getClientSessionIdleTimeout());
if (rep.getClientSessionMaxLifespan() != null)
newRealm.setClientSessionMaxLifespan(rep.getClientSessionMaxLifespan());
if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
else newRealm.setAccessCodeLifespan(60);
@ -1088,6 +1093,10 @@ public class RepresentationToModel {
if (rep.getOfflineSessionMaxLifespanEnabled() != null) realm.setOfflineSessionMaxLifespanEnabled(rep.getOfflineSessionMaxLifespanEnabled());
if (rep.getOfflineSessionMaxLifespan() != null)
realm.setOfflineSessionMaxLifespan(rep.getOfflineSessionMaxLifespan());
if (rep.getClientSessionIdleTimeout() != null)
realm.setClientSessionIdleTimeout(rep.getClientSessionIdleTimeout());
if (rep.getClientSessionMaxLifespan() != null)
realm.setClientSessionMaxLifespan(rep.getClientSessionMaxLifespan());
if (rep.getRequiredCredentials() != null) {
realm.updateRequiredCredentials(rep.getRequiredCredentials());
}

View file

@ -196,6 +196,12 @@ public interface RealmModel extends RoleContainerModel {
int getOfflineSessionMaxLifespan();
void setOfflineSessionMaxLifespan(int seconds);
int getClientSessionIdleTimeout();
void setClientSessionIdleTimeout(int seconds);
int getClientSessionMaxLifespan();
void setClientSessionMaxLifespan(int seconds);
void setAccessTokenLifespan(int seconds);
int getAccessTokenLifespanForImplicitFlow();

View file

@ -44,7 +44,8 @@ public final class OIDCConfigAttributes {
public static final String ACCESS_TOKEN_SIGNED_RESPONSE_ALG = "access.token.signed.response.alg";
public static final String ACCESS_TOKEN_LIFESPAN = "access.token.lifespan";
public static final String CLIENT_SESSION_IDLE_TIMEOUT = "client.session.idle.timeout";
public static final String CLIENT_SESSION_MAX_LIFESPAN = "client.session.max.lifespan";
public static final String PKCE_CODE_CHALLENGE_METHOD = "pkce.code.challenge.method";
private OIDCConfigAttributes() {

View file

@ -687,6 +687,19 @@ public class TokenManager {
expiration = expiration <= sessionExpires ? expiration : sessionExpires;
}
int clientSessionMaxLifespan;
String clientSessionMaxLifespanPerClient = client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN);
if (clientSessionMaxLifespanPerClient != null && !clientSessionMaxLifespanPerClient.trim().isEmpty()) {
clientSessionMaxLifespan = Integer.parseInt(clientSessionMaxLifespanPerClient);
} else {
clientSessionMaxLifespan = realm.getClientSessionMaxLifespan();
}
if (clientSessionMaxLifespan > 0) {
int clientSessionExpiration = userSession.getStarted() + clientSessionMaxLifespan;
return expiration < clientSessionExpiration ? expiration : clientSessionExpiration;
}
return expiration;
}
@ -777,10 +790,41 @@ public class TokenManager {
}
private int getRefreshExpiration() {
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());
int sessionExpires = userSession.getStarted()
+ (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0
? realm.getSsoSessionMaxLifespanRememberMe()
: realm.getSsoSessionMaxLifespan());
int clientSessionMaxLifespan;
String clientSessionMaxLifespanPerClient = client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN);
if (clientSessionMaxLifespanPerClient != null && !clientSessionMaxLifespanPerClient.trim().isEmpty()) {
clientSessionMaxLifespan = Integer.parseInt(clientSessionMaxLifespanPerClient);
} else {
clientSessionMaxLifespan = realm.getClientSessionMaxLifespan();
}
if (clientSessionMaxLifespan > 0) {
int clientSessionMaxExpiration = userSession.getStarted() + clientSessionMaxLifespan;
sessionExpires = sessionExpires < clientSessionMaxExpiration ? sessionExpires : clientSessionMaxExpiration;
}
int expiration = Time.currentTime() + (userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0
? realm.getSsoSessionIdleTimeoutRememberMe()
: realm.getSsoSessionIdleTimeout());
int clientSessionIdleTimeout;
String clientSessionIdleTimeoutPerClient = client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT);
if (clientSessionIdleTimeoutPerClient != null && !clientSessionIdleTimeoutPerClient.trim().isEmpty()) {
clientSessionIdleTimeout = Integer.parseInt(clientSessionIdleTimeoutPerClient);
} else {
clientSessionIdleTimeout = realm.getClientSessionIdleTimeout();
}
if (clientSessionIdleTimeout > 0) {
int clientSessionIdleExpiration = Time.currentTime() + clientSessionIdleTimeout;
expiration = expiration < clientSessionIdleExpiration ? expiration : clientSessionIdleExpiration;
}
return expiration <= sessionExpires ? expiration : sessionExpires;
}

View file

@ -620,6 +620,10 @@ public class RealmTest extends AbstractAdminTest {
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.getClientSessionIdleTimeout() != null)
Assert.assertEquals(realm.getClientSessionIdleTimeout(), storedRealm.getClientSessionIdleTimeout());
if (realm.getClientSessionMaxLifespan() != null)
Assert.assertEquals(realm.getClientSessionMaxLifespan(), storedRealm.getClientSessionMaxLifespan());
if (realm.getRequiredCredentials() != null) {
assertNotNull(storedRealm.getRequiredCredentials());
for (String cred : realm.getRequiredCredentials()) {

View file

@ -1121,6 +1121,47 @@ public class AccessTokenTest extends AbstractKeycloakTest {
}
}
@Test
public void testClientSessionMaxLifespan() throws Exception {
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
ClientRepresentation clientRepresentation = client.toRepresentation();
RealmResource realm = adminClient.realm("test");
RealmRepresentation rep = realm.toRepresentation();
int accessTokenLifespan = rep.getAccessTokenLifespan();
Integer originalClientSessionMaxLifespan = rep.getClientSessionMaxLifespan();
try {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getExpiresIn(), accessTokenLifespan);
rep.setClientSessionMaxLifespan(accessTokenLifespan - 100);
realm.update(rep);
String refreshToken = response.getRefreshToken();
response = oauth.doRefreshTokenRequest(refreshToken, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getExpiresIn(), accessTokenLifespan - 100);
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN,
Integer.toString(accessTokenLifespan - 200));
client.update(clientRepresentation);
refreshToken = response.getRefreshToken();
response = oauth.doRefreshTokenRequest(refreshToken, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getExpiresIn(), accessTokenLifespan - 200);
} finally {
rep.setClientSessionMaxLifespan(originalClientSessionMaxLifespan);
realm.update(rep);
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN, null);
client.update(clientRepresentation);
}
}
@Test
public void accessTokenRequest_ClientPS384_RealmRS256() throws Exception {
conductAccessTokenRequest(Algorithm.HS256, Algorithm.PS384, Algorithm.RS256);

View file

@ -24,6 +24,7 @@ import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.enums.SslRequired;
@ -35,10 +36,12 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.SessionTimeoutHelper;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
@ -77,6 +80,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.Assert.assertExpiration;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.AUTH_SERVER_SSL_REQUIRED;
@ -1046,6 +1050,93 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
assertNotNull(response.getRefreshToken());
}
@Test
public void testClientSessionMaxLifespan() throws Exception {
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
ClientRepresentation clientRepresentation = client.toRepresentation();
RealmResource realm = adminClient.realm("test");
RealmRepresentation rep = realm.toRepresentation();
Integer originalSsoSessionMaxLifespan = rep.getSsoSessionMaxLifespan();
int ssoSessionMaxLifespan = rep.getSsoSessionIdleTimeout() - 100;
Integer originalClientSessionMaxLifespan = rep.getClientSessionMaxLifespan();
try {
rep.setSsoSessionMaxLifespan(ssoSessionMaxLifespan);
realm.update(rep);
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getRefreshExpiresIn(), ssoSessionMaxLifespan);
rep.setClientSessionMaxLifespan(ssoSessionMaxLifespan - 100);
realm.update(rep);
String refreshToken = response.getRefreshToken();
response = oauth.doRefreshTokenRequest(refreshToken, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getRefreshExpiresIn(), ssoSessionMaxLifespan - 100);
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN,
Integer.toString(ssoSessionMaxLifespan - 200));
client.update(clientRepresentation);
refreshToken = response.getRefreshToken();
response = oauth.doRefreshTokenRequest(refreshToken, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getRefreshExpiresIn(), ssoSessionMaxLifespan - 200);
} finally {
rep.setSsoSessionMaxLifespan(originalSsoSessionMaxLifespan);
rep.setClientSessionMaxLifespan(originalClientSessionMaxLifespan);
realm.update(rep);
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN, null);
client.update(clientRepresentation);
}
}
@Test
public void testClientSessionIdleTimeout() throws Exception {
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
ClientRepresentation clientRepresentation = client.toRepresentation();
RealmResource realm = adminClient.realm("test");
RealmRepresentation rep = realm.toRepresentation();
int ssoSessionIdleTimeout = rep.getSsoSessionIdleTimeout();
Integer originalClientSessionIdleTimeout = rep.getClientSessionIdleTimeout();
try {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getRefreshExpiresIn(), ssoSessionIdleTimeout);
rep.setClientSessionIdleTimeout(ssoSessionIdleTimeout - 100);
realm.update(rep);
String refreshToken = response.getRefreshToken();
response = oauth.doRefreshTokenRequest(refreshToken, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getRefreshExpiresIn(), ssoSessionIdleTimeout - 100);
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT,
Integer.toString(ssoSessionIdleTimeout - 200));
client.update(clientRepresentation);
refreshToken = response.getRefreshToken();
response = oauth.doRefreshTokenRequest(refreshToken, "password");
assertEquals(200, response.getStatusCode());
assertExpiration(response.getRefreshExpiresIn(), ssoSessionIdleTimeout - 200);
} finally {
rep.setClientSessionIdleTimeout(originalClientSessionIdleTimeout);
realm.update(rep);
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT, null);
client.update(clientRepresentation);
}
}
@Test
public void tokenRefreshRequest_ClientRS384_RealmRS384() throws Exception {
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.RS384, Algorithm.RS384);

View file

@ -272,4 +272,14 @@ public class RealmBuilder {
rep.setOfflineSessionMaxLifespanEnabled(offlineSessionMaxLifespanEnabled);
return this;
}
public RealmBuilder clientSessionIdleTimeout(int clientSessionIdleTimeout) {
rep.setClientSessionIdleTimeout(clientSessionIdleTimeout);
return this;
}
public RealmBuilder clientSessionMaxLifespan(int clientSessionMaxLifespan) {
rep.setClientSessionMaxLifespan(clientSessionMaxLifespan);
return this;
}
}

View file

@ -128,7 +128,10 @@ 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.
client-session-idle=Client Session Idle
client-session-idle.tooltip=Time a client session is allowed to be idle before it expires. Tokens are invalidated when a client session is expired. If not set it uses the standard SSO Session Idle value.
client-session-max=Client Session Max
client-session-max.tooltip=Max time before a client session is expired. Tokens are invalidated when a client session is expired. If not set, it uses the standard SSO Session Max value.
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

View file

@ -1090,6 +1090,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
$scope.accessTokenLifespan = TimeUnit2.asUnit(client.attributes['access.token.lifespan']);
$scope.samlAssertionLifespan = TimeUnit2.asUnit(client.attributes['saml.assertion.lifespan']);
$scope.clientSessionIdleTimeout = TimeUnit2.asUnit(client.attributes['client.session.idle.timeout']);
$scope.clientSessionMaxLifespan = TimeUnit2.asUnit(client.attributes['client.session.max.lifespan']);
if(client.origin) {
if ($scope.access.viewRealm) {
@ -1424,6 +1426,22 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
}
}
$scope.updateClientSessionIdleTimeout = function() {
if ($scope.clientSessionIdleTimeout.time) {
$scope.clientEdit.attributes['client.session.idle.timeout'] = $scope.clientSessionIdleTimeout.toSeconds();
} else {
$scope.clientEdit.attributes['client.session.idle.timeout'] = null;
}
}
$scope.updateClientSessionMaxLifespan = function() {
if ($scope.clientSessionMaxLifespan.time) {
$scope.clientEdit.attributes['client.session.max.lifespan'] = $scope.clientSessionMaxLifespan.toSeconds();
} else {
$scope.clientEdit.attributes['client.session.max.lifespan'] = null;
}
}
function configureAuthorizationServices() {
if ($scope.clientEdit.authorizationServicesEnabled) {
if ($scope.accessType == 'public') {

View file

@ -1160,6 +1160,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.realm.offlineSessionIdleTimeout = TimeUnit2.asUnit(realm.offlineSessionIdleTimeout);
// KEYCLOAK-7688 Offline Session Max for Offline Token
$scope.realm.offlineSessionMaxLifespan = TimeUnit2.asUnit(realm.offlineSessionMaxLifespan);
$scope.realm.clientSessionIdleTimeout = TimeUnit2.asUnit(realm.clientSessionIdleTimeout);
$scope.realm.clientSessionMaxLifespan = TimeUnit2.asUnit(realm.clientSessionMaxLifespan);
$scope.realm.accessCodeLifespan = TimeUnit2.asUnit(realm.accessCodeLifespan);
$scope.realm.accessCodeLifespanLogin = TimeUnit2.asUnit(realm.accessCodeLifespanLogin);
$scope.realm.accessCodeLifespanUserAction = TimeUnit2.asUnit(realm.accessCodeLifespanUserAction);
@ -1215,6 +1217,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.realm.offlineSessionIdleTimeout = $scope.realm.offlineSessionIdleTimeout.toSeconds();
// KEYCLOAK-7688 Offline Session Max for Offline Token
$scope.realm.offlineSessionMaxLifespan = $scope.realm.offlineSessionMaxLifespan.toSeconds();
$scope.realm.clientSessionIdleTimeout = $scope.realm.clientSessionIdleTimeout.toSeconds();
$scope.realm.clientSessionMaxLifespan = $scope.realm.clientSessionMaxLifespan.toSeconds();
$scope.realm.accessCodeLifespan = $scope.realm.accessCodeLifespan.toSeconds();
$scope.realm.accessCodeLifespanUserAction = $scope.realm.accessCodeLifespanUserAction.toSeconds();
$scope.realm.accessCodeLifespanLogin = $scope.realm.accessCodeLifespanLogin.toSeconds();

View file

@ -547,6 +547,38 @@
<kc-tooltip>{{:: 'saml-assertion-lifespan.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect'">
<label class="col-md-2 control-label" for="clientSessionIdleTimeout">{{:: 'client-session-idle' | translate}}</label>
<div class="col-md-6 time-selector">
<input class="form-control" type="number" min="0"
max="31536000" data-ng-model="clientSessionIdleTimeout.time"
id="clientSessionIdleTimeout" name="clientSessionIdleTimeout"
data-ng-change="updateClientSessionIdleTimeout()"/>
<select class="form-control" name="clientSessionIdleTimeoutUnit" data-ng-model="clientSessionIdleTimeout.unit" data-ng-change="updateClientSessionIdleTimeout()">
<option value="Minutes">{{:: 'minutes' | translate}}</option>
<option value="Hours">{{:: 'hours' | translate}}</option>
<option value="Days">{{:: 'days' | translate}}</option>
</select>
</div>
<kc-tooltip>{{:: 'client-session-idle.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect'">
<label class="col-md-2 control-label" for="clientSessionMaxLifespan">{{:: 'client-session-max' | translate}}</label>
<div class="col-md-6 time-selector">
<input class="form-control" type="number" min="0"
max="31536000" data-ng-model="clientSessionMaxLifespan.time"
id="clientSessionMaxLifespan" name="clientSessionMaxLifespan"
data-ng-change="updateClientSessionMaxLifespan()"/>
<select class="form-control" name="clientSessionMaxLifespanUnit" data-ng-model="clientSessionMaxLifespan.unit" data-ng-change="updateClientSessionMaxLifespan()">
<option value="Minutes">{{:: 'minutes' | translate}}</option>
<option value="Hours">{{:: 'hours' | translate}}</option>
<option value="Days">{{:: 'days' | translate}}</option>
</select>
</div>
<kc-tooltip>{{:: 'client-session-max.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block" data-ng-show="protocol == 'openid-connect'">
<label class="col-md-2 control-label" for="tlsClientCertificateBoundAccessTokens">{{:: 'tls-client-certificate-bound-access-tokens' | translate}}</label>
<div class="col-sm-6">

View file

@ -145,6 +145,36 @@
<kc-tooltip>{{:: 'offline-session-max.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="clientSessionIdleTimeout">{{:: 'client-session-idle' | translate}}</label>
<div class="col-md-6 time-selector">
<input class="form-control" type="number" required min="0"
max="31536000" data-ng-model="realm.clientSessionIdleTimeout.time"
id="clientSessionIdleTimeout" name="clientSessionIdleTimeout"/>
<select class="form-control" name="clientSessionIdleTimeoutUnit" data-ng-model="realm.clientSessionIdleTimeout.unit">
<option value="Minutes">{{:: 'minutes' | translate}}</option>
<option value="Hours">{{:: 'hours' | translate}}</option>
<option value="Days">{{:: 'days' | translate}}</option>
</select>
</div>
<kc-tooltip>{{:: 'client-session-idle.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="clientSessionMaxLifespan">{{:: 'client-session-max' | translate}}</label>
<div class="col-md-6 time-selector">
<input class="form-control" type="number" required min="0"
max="31536000" data-ng-model="realm.clientSessionMaxLifespan.time"
id="clientSessionMaxLifespan" name="clientSessionMaxLifespan"/>
<select class="form-control" name="clientSessionMaxLifespanUnit" data-ng-model="realm.clientSessionMaxLifespan.unit">
<option value="Minutes">{{:: 'minutes' | translate}}</option>
<option value="Hours">{{:: 'hours' | translate}}</option>
<option value="Days">{{:: 'days' | translate}}</option>
</select>
</div>
<kc-tooltip>{{:: 'client-session-max.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="accessTokenLifespan">{{:: 'access-token-lifespan' | translate}}</label>