KEYCLOAK-14145 OIDC support for Client "offline" session lifespan
This commit is contained in:
parent
82cfb8e821
commit
f03ee2ec98
19 changed files with 413 additions and 12 deletions
|
@ -52,6 +52,8 @@ public class RealmRepresentation {
|
|||
protected Integer offlineSessionMaxLifespan;
|
||||
protected Integer clientSessionIdleTimeout;
|
||||
protected Integer clientSessionMaxLifespan;
|
||||
protected Integer clientOfflineSessionIdleTimeout;
|
||||
protected Integer clientOfflineSessionMaxLifespan;
|
||||
protected Integer accessCodeLifespan;
|
||||
protected Integer accessCodeLifespanUserAction;
|
||||
protected Integer accessCodeLifespanLogin;
|
||||
|
@ -387,6 +389,22 @@ public class RealmRepresentation {
|
|||
this.clientSessionMaxLifespan = clientSessionMaxLifespan;
|
||||
}
|
||||
|
||||
public Integer getClientOfflineSessionIdleTimeout() {
|
||||
return clientOfflineSessionIdleTimeout;
|
||||
}
|
||||
|
||||
public void setClientOfflineSessionIdleTimeout(Integer clientOfflineSessionIdleTimeout) {
|
||||
this.clientOfflineSessionIdleTimeout = clientOfflineSessionIdleTimeout;
|
||||
}
|
||||
|
||||
public Integer getClientOfflineSessionMaxLifespan() {
|
||||
return clientOfflineSessionMaxLifespan;
|
||||
}
|
||||
|
||||
public void setClientOfflineSessionMaxLifespan(Integer clientOfflineSessionMaxLifespan) {
|
||||
this.clientOfflineSessionMaxLifespan = clientOfflineSessionMaxLifespan;
|
||||
}
|
||||
|
||||
public List<ScopeMappingRepresentation> getScopeMappings() {
|
||||
return scopeMappings;
|
||||
}
|
||||
|
|
|
@ -506,6 +506,32 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
updated.setClientSessionMaxLifespan(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientOfflineSessionIdleTimeout() {
|
||||
if (isUpdated())
|
||||
return updated.getClientOfflineSessionIdleTimeout();
|
||||
return cached.getClientOfflineSessionIdleTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientOfflineSessionIdleTimeout(int seconds) {
|
||||
getDelegateForUpdate();
|
||||
updated.setClientOfflineSessionIdleTimeout(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientOfflineSessionMaxLifespan() {
|
||||
if (isUpdated())
|
||||
return updated.getClientOfflineSessionMaxLifespan();
|
||||
return cached.getClientOfflineSessionMaxLifespan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientOfflineSessionMaxLifespan(int seconds) {
|
||||
getDelegateForUpdate();
|
||||
updated.setClientOfflineSessionMaxLifespan(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccessTokenLifespan() {
|
||||
if (isUpdated()) return updated.getAccessTokenLifespan();
|
||||
|
|
|
@ -88,6 +88,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
protected int offlineSessionMaxLifespan;
|
||||
protected int clientSessionIdleTimeout;
|
||||
protected int clientSessionMaxLifespan;
|
||||
protected int clientOfflineSessionIdleTimeout;
|
||||
protected int clientOfflineSessionMaxLifespan;
|
||||
protected int accessTokenLifespan;
|
||||
protected int accessTokenLifespanForImplicitFlow;
|
||||
protected int accessCodeLifespan;
|
||||
|
@ -201,6 +203,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
offlineSessionMaxLifespan = model.getOfflineSessionMaxLifespan();
|
||||
clientSessionIdleTimeout = model.getClientSessionIdleTimeout();
|
||||
clientSessionMaxLifespan = model.getClientSessionMaxLifespan();
|
||||
clientOfflineSessionIdleTimeout = model.getClientOfflineSessionIdleTimeout();
|
||||
clientOfflineSessionMaxLifespan = model.getClientOfflineSessionMaxLifespan();
|
||||
accessTokenLifespan = model.getAccessTokenLifespan();
|
||||
accessTokenLifespanForImplicitFlow = model.getAccessTokenLifespanForImplicitFlow();
|
||||
accessCodeLifespan = model.getAccessCodeLifespan();
|
||||
|
@ -459,6 +463,14 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
return clientSessionMaxLifespan;
|
||||
}
|
||||
|
||||
public int getClientOfflineSessionIdleTimeout() {
|
||||
return clientOfflineSessionIdleTimeout;
|
||||
}
|
||||
|
||||
public int getClientOfflineSessionMaxLifespan() {
|
||||
return clientOfflineSessionMaxLifespan;
|
||||
}
|
||||
|
||||
public int getAccessTokenLifespan() {
|
||||
return accessTokenLifespan;
|
||||
}
|
||||
|
|
|
@ -543,6 +543,26 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
setAttribute(RealmAttributes.CLIENT_SESSION_MAX_LIFESPAN, seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientOfflineSessionIdleTimeout() {
|
||||
return getAttribute(RealmAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientOfflineSessionIdleTimeout(int seconds) {
|
||||
setAttribute(RealmAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientOfflineSessionMaxLifespan() {
|
||||
return getAttribute(RealmAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientOfflineSessionMaxLifespan(int seconds) {
|
||||
setAttribute(RealmAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccessCodeLifespan() {
|
||||
return realm.getAccessCodeLifespan();
|
||||
|
|
|
@ -36,6 +36,8 @@ public interface RealmAttributes {
|
|||
String OFFLINE_SESSION_MAX_LIFESPAN = "offlineSessionMaxLifespan";
|
||||
String CLIENT_SESSION_IDLE_TIMEOUT = "clientSessionIdleTimeout";
|
||||
String CLIENT_SESSION_MAX_LIFESPAN = "clientSessionMaxLifespan";
|
||||
String CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT = "clientOfflineSessionIdleTimeout";
|
||||
String CLIENT_OFFLINE_SESSION_MAX_LIFESPAN = "clientOfflineSessionMaxLifespan";
|
||||
String WEBAUTHN_POLICY_RP_ENTITY_NAME = "webAuthnPolicyRpEntityName";
|
||||
String WEBAUTHN_POLICY_SIGNATURE_ALGORITHMS = "webAuthnPolicySignatureAlgorithms";
|
||||
|
||||
|
|
|
@ -366,6 +366,8 @@ public class ModelToRepresentation {
|
|||
rep.setOfflineSessionMaxLifespan(realm.getOfflineSessionMaxLifespan());
|
||||
rep.setClientSessionIdleTimeout(realm.getClientSessionIdleTimeout());
|
||||
rep.setClientSessionMaxLifespan(realm.getClientSessionMaxLifespan());
|
||||
rep.setClientOfflineSessionIdleTimeout(realm.getClientOfflineSessionIdleTimeout());
|
||||
rep.setClientOfflineSessionMaxLifespan(realm.getClientOfflineSessionMaxLifespan());
|
||||
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
|
||||
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
||||
rep.setAccessCodeLifespanLogin(realm.getAccessCodeLifespanLogin());
|
||||
|
|
|
@ -225,6 +225,11 @@ public class RepresentationToModel {
|
|||
if (rep.getClientSessionMaxLifespan() != null)
|
||||
newRealm.setClientSessionMaxLifespan(rep.getClientSessionMaxLifespan());
|
||||
|
||||
if (rep.getClientOfflineSessionIdleTimeout() != null)
|
||||
newRealm.setClientOfflineSessionIdleTimeout(rep.getClientOfflineSessionIdleTimeout());
|
||||
if (rep.getClientOfflineSessionMaxLifespan() != null)
|
||||
newRealm.setClientOfflineSessionMaxLifespan(rep.getClientOfflineSessionMaxLifespan());
|
||||
|
||||
if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
|
||||
else newRealm.setAccessCodeLifespan(60);
|
||||
|
||||
|
@ -1097,6 +1102,10 @@ public class RepresentationToModel {
|
|||
realm.setClientSessionIdleTimeout(rep.getClientSessionIdleTimeout());
|
||||
if (rep.getClientSessionMaxLifespan() != null)
|
||||
realm.setClientSessionMaxLifespan(rep.getClientSessionMaxLifespan());
|
||||
if (rep.getClientOfflineSessionIdleTimeout() != null)
|
||||
realm.setClientOfflineSessionIdleTimeout(rep.getClientOfflineSessionIdleTimeout());
|
||||
if (rep.getClientOfflineSessionMaxLifespan() != null)
|
||||
realm.setClientOfflineSessionMaxLifespan(rep.getClientOfflineSessionMaxLifespan());
|
||||
if (rep.getRequiredCredentials() != null) {
|
||||
realm.updateRequiredCredentials(rep.getRequiredCredentials());
|
||||
}
|
||||
|
|
|
@ -202,6 +202,12 @@ public interface RealmModel extends RoleContainerModel {
|
|||
int getClientSessionMaxLifespan();
|
||||
void setClientSessionMaxLifespan(int seconds);
|
||||
|
||||
int getClientOfflineSessionIdleTimeout();
|
||||
void setClientOfflineSessionIdleTimeout(int seconds);
|
||||
|
||||
int getClientOfflineSessionMaxLifespan();
|
||||
void setClientOfflineSessionMaxLifespan(int seconds);
|
||||
|
||||
void setAccessTokenLifespan(int seconds);
|
||||
|
||||
int getAccessTokenLifespanForImplicitFlow();
|
||||
|
|
|
@ -46,6 +46,8 @@ public final class OIDCConfigAttributes {
|
|||
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 CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT = "client.offline.session.idle.timeout";
|
||||
public static final String CLIENT_OFFLINE_SESSION_MAX_LIFESPAN = "client.offline.session.max.lifespan";
|
||||
public static final String PKCE_CODE_CHALLENGE_METHOD = "pkce.code.challenge.method";
|
||||
|
||||
public static final String TOKEN_ENDPOINT_AUTH_SIGNING_ALG = "token.endpoint.auth.signing.alg";
|
||||
|
|
|
@ -689,6 +689,21 @@ public class TokenManager {
|
|||
if (realm.isOfflineSessionMaxLifespanEnabled()) {
|
||||
int sessionExpires = userSession.getStarted() + realm.getOfflineSessionMaxLifespan();
|
||||
expiration = expiration <= sessionExpires ? expiration : sessionExpires;
|
||||
|
||||
int clientOfflineSessionMaxLifespan;
|
||||
String clientOfflineSessionMaxLifespanPerClient = client
|
||||
.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN);
|
||||
if (clientOfflineSessionMaxLifespanPerClient != null
|
||||
&& !clientOfflineSessionMaxLifespanPerClient.trim().isEmpty()) {
|
||||
clientOfflineSessionMaxLifespan = Integer.parseInt(clientOfflineSessionMaxLifespanPerClient);
|
||||
} else {
|
||||
clientOfflineSessionMaxLifespan = realm.getClientOfflineSessionMaxLifespan();
|
||||
}
|
||||
|
||||
if (clientOfflineSessionMaxLifespan > 0) {
|
||||
int clientOfflineSessionExpiration = userSession.getStarted() + clientOfflineSessionMaxLifespan;
|
||||
return expiration < clientOfflineSessionExpiration ? expiration : clientOfflineSessionExpiration;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int sessionExpires = userSession.getStarted()
|
||||
|
@ -696,19 +711,19 @@ public class TokenManager {
|
|||
? realm.getSsoSessionMaxLifespanRememberMe()
|
||||
: realm.getSsoSessionMaxLifespan());
|
||||
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();
|
||||
}
|
||||
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;
|
||||
if (clientSessionMaxLifespan > 0) {
|
||||
int clientSessionExpiration = userSession.getStarted() + clientSessionMaxLifespan;
|
||||
return expiration < clientSessionExpiration ? expiration : clientSessionExpiration;
|
||||
}
|
||||
}
|
||||
|
||||
return expiration;
|
||||
|
@ -842,9 +857,41 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
private int getOfflineExpiration() {
|
||||
int expiration = Time.currentTime() + realm.getOfflineSessionIdleTimeout();
|
||||
int sessionExpires = userSession.getStarted() + realm.getOfflineSessionMaxLifespan();
|
||||
|
||||
int clientOfflineSessionMaxLifespan;
|
||||
String clientOfflineSessionMaxLifespanPerClient = client
|
||||
.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN);
|
||||
if (clientOfflineSessionMaxLifespanPerClient != null
|
||||
&& !clientOfflineSessionMaxLifespanPerClient.trim().isEmpty()) {
|
||||
clientOfflineSessionMaxLifespan = Integer.parseInt(clientOfflineSessionMaxLifespanPerClient);
|
||||
} else {
|
||||
clientOfflineSessionMaxLifespan = realm.getClientOfflineSessionMaxLifespan();
|
||||
}
|
||||
|
||||
if (clientOfflineSessionMaxLifespan > 0) {
|
||||
int clientOfflineSessionMaxExpiration = userSession.getStarted() + clientOfflineSessionMaxLifespan;
|
||||
sessionExpires = sessionExpires < clientOfflineSessionMaxExpiration ? sessionExpires
|
||||
: clientOfflineSessionMaxExpiration;
|
||||
}
|
||||
|
||||
int expiration = Time.currentTime() + realm.getOfflineSessionIdleTimeout();
|
||||
|
||||
int clientOfflineSessionIdleTimeout;
|
||||
String clientOfflineSessionIdleTimeoutPerClient = client
|
||||
.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT);
|
||||
if (clientOfflineSessionIdleTimeoutPerClient != null
|
||||
&& !clientOfflineSessionIdleTimeoutPerClient.trim().isEmpty()) {
|
||||
clientOfflineSessionIdleTimeout = Integer.parseInt(clientOfflineSessionIdleTimeoutPerClient);
|
||||
} else {
|
||||
clientOfflineSessionIdleTimeout = realm.getClientOfflineSessionIdleTimeout();
|
||||
}
|
||||
|
||||
if (clientOfflineSessionIdleTimeout > 0) {
|
||||
int clientOfflineSessionIdleExpiration = Time.currentTime() + clientOfflineSessionIdleTimeout;
|
||||
expiration = expiration < clientOfflineSessionIdleExpiration ? expiration : clientOfflineSessionIdleExpiration;
|
||||
}
|
||||
|
||||
return expiration <= sessionExpires ? expiration : sessionExpires;
|
||||
}
|
||||
|
||||
|
|
|
@ -624,6 +624,10 @@ public class RealmTest extends AbstractAdminTest {
|
|||
Assert.assertEquals(realm.getClientSessionIdleTimeout(), storedRealm.getClientSessionIdleTimeout());
|
||||
if (realm.getClientSessionMaxLifespan() != null)
|
||||
Assert.assertEquals(realm.getClientSessionMaxLifespan(), storedRealm.getClientSessionMaxLifespan());
|
||||
if (realm.getClientOfflineSessionIdleTimeout() != null)
|
||||
Assert.assertEquals(realm.getClientOfflineSessionIdleTimeout(), storedRealm.getClientOfflineSessionIdleTimeout());
|
||||
if (realm.getClientOfflineSessionMaxLifespan() != null)
|
||||
Assert.assertEquals(realm.getClientOfflineSessionMaxLifespan(), storedRealm.getClientOfflineSessionMaxLifespan());
|
||||
if (realm.getRequiredCredentials() != null) {
|
||||
assertNotNull(storedRealm.getRequiredCredentials());
|
||||
for (String cred : realm.getRequiredCredentials()) {
|
||||
|
|
|
@ -1162,6 +1162,53 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientOfflineSessionMaxLifespan() 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();
|
||||
Boolean originalOfflineSessionMaxLifespanEnabled = rep.getOfflineSessionMaxLifespanEnabled();
|
||||
Integer originalClientOfflineSessionMaxLifespan = rep.getClientOfflineSessionMaxLifespan();
|
||||
|
||||
try {
|
||||
rep.setOfflineSessionMaxLifespanEnabled(true);
|
||||
realm.update(rep);
|
||||
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
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.setClientOfflineSessionMaxLifespan(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_OFFLINE_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.setOfflineSessionMaxLifespanEnabled(originalOfflineSessionMaxLifespanEnabled);
|
||||
rep.setClientOfflineSessionMaxLifespan(originalClientOfflineSessionMaxLifespan);
|
||||
realm.update(rep);
|
||||
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, null);
|
||||
client.update(clientRepresentation);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientPS384_RealmRS256() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.PS384, Algorithm.RS256);
|
||||
|
|
|
@ -39,6 +39,7 @@ 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.OIDCConfigAttributes;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
@ -78,6 +79,7 @@ import static org.junit.Assert.assertNotEquals;
|
|||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.keycloak.testsuite.Assert.assertExpiration;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findRealmRoleByName;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
|
||||
|
@ -881,4 +883,104 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientOfflineSessionMaxLifespan() throws Exception {
|
||||
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client");
|
||||
ClientRepresentation clientRepresentation = client.toRepresentation();
|
||||
|
||||
RealmResource realm = adminClient.realm("test");
|
||||
RealmRepresentation rep = realm.toRepresentation();
|
||||
Boolean originalOfflineSessionMaxLifespanEnabled = rep.getOfflineSessionMaxLifespanEnabled();
|
||||
Integer originalOfflineSessionMaxLifespan = rep.getOfflineSessionMaxLifespan();
|
||||
int offlineSessionMaxLifespan = rep.getOfflineSessionIdleTimeout() - 100;
|
||||
Integer originalClientOfflineSessionMaxLifespan = rep.getClientOfflineSessionMaxLifespan();
|
||||
|
||||
try {
|
||||
rep.setOfflineSessionMaxLifespanEnabled(true);
|
||||
rep.setOfflineSessionMaxLifespan(offlineSessionMaxLifespan);
|
||||
realm.update(rep);
|
||||
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
oauth.clientId("offline-client");
|
||||
oauth.redirectUri(offlineClientAppUri);
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "secret1");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
assertExpiration(response.getRefreshExpiresIn(), offlineSessionMaxLifespan);
|
||||
|
||||
rep.setClientOfflineSessionMaxLifespan(offlineSessionMaxLifespan - 100);
|
||||
realm.update(rep);
|
||||
|
||||
String refreshToken = response.getRefreshToken();
|
||||
response = oauth.doRefreshTokenRequest(refreshToken, "secret1");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
assertExpiration(response.getRefreshExpiresIn(), offlineSessionMaxLifespan - 100);
|
||||
|
||||
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN,
|
||||
Integer.toString(offlineSessionMaxLifespan - 200));
|
||||
client.update(clientRepresentation);
|
||||
|
||||
refreshToken = response.getRefreshToken();
|
||||
response = oauth.doRefreshTokenRequest(refreshToken, "secret1");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
assertExpiration(response.getRefreshExpiresIn(), offlineSessionMaxLifespan - 200);
|
||||
} finally {
|
||||
rep.setOfflineSessionMaxLifespanEnabled(originalOfflineSessionMaxLifespanEnabled);
|
||||
rep.setOfflineSessionMaxLifespan(originalOfflineSessionMaxLifespan);
|
||||
rep.setClientOfflineSessionMaxLifespan(originalClientOfflineSessionMaxLifespan);
|
||||
realm.update(rep);
|
||||
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, null);
|
||||
client.update(clientRepresentation);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientOfflineSessionIdleTimeout() throws Exception {
|
||||
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client");
|
||||
ClientRepresentation clientRepresentation = client.toRepresentation();
|
||||
|
||||
RealmResource realm = adminClient.realm("test");
|
||||
RealmRepresentation rep = realm.toRepresentation();
|
||||
Boolean originalOfflineSessionMaxLifespanEnabled = rep.getOfflineSessionMaxLifespanEnabled();
|
||||
int offlineSessionIdleTimeout = rep.getOfflineSessionIdleTimeout();
|
||||
Integer originalClientOfflineSessionIdleTimeout = rep.getClientOfflineSessionIdleTimeout();
|
||||
|
||||
try {
|
||||
rep.setOfflineSessionMaxLifespanEnabled(true);
|
||||
realm.update(rep);
|
||||
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
oauth.clientId("offline-client");
|
||||
oauth.redirectUri(offlineClientAppUri);
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "secret1");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
assertExpiration(response.getRefreshExpiresIn(), offlineSessionIdleTimeout);
|
||||
|
||||
rep.setClientOfflineSessionIdleTimeout(offlineSessionIdleTimeout - 100);
|
||||
realm.update(rep);
|
||||
|
||||
String refreshToken = response.getRefreshToken();
|
||||
response = oauth.doRefreshTokenRequest(refreshToken, "secret1");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
assertExpiration(response.getRefreshExpiresIn(), offlineSessionIdleTimeout - 100);
|
||||
|
||||
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT,
|
||||
Integer.toString(offlineSessionIdleTimeout - 200));
|
||||
client.update(clientRepresentation);
|
||||
|
||||
refreshToken = response.getRefreshToken();
|
||||
response = oauth.doRefreshTokenRequest(refreshToken, "secret1");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
assertExpiration(response.getRefreshExpiresIn(), offlineSessionIdleTimeout - 200);
|
||||
} finally {
|
||||
rep.setOfflineSessionMaxLifespanEnabled(originalOfflineSessionMaxLifespanEnabled);
|
||||
rep.setClientOfflineSessionIdleTimeout(originalClientOfflineSessionIdleTimeout);
|
||||
realm.update(rep);
|
||||
clientRepresentation.getAttributes().put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, null);
|
||||
client.update(clientRepresentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,4 +282,14 @@ public class RealmBuilder {
|
|||
rep.setClientSessionMaxLifespan(clientSessionMaxLifespan);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RealmBuilder clientOfflineSessionIdleTimeout(int clientOfflineSessionIdleTimeout) {
|
||||
rep.setClientOfflineSessionIdleTimeout(clientOfflineSessionIdleTimeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RealmBuilder clientOfflineSessionMaxLifespan(int clientOfflineSessionMaxLifespan) {
|
||||
rep.setClientOfflineSessionMaxLifespan(clientOfflineSessionMaxLifespan);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,6 +132,10 @@ 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.
|
||||
client-offline-session-idle=Client Offline Session Idle
|
||||
client-offline-session-idle.tooltip=Time a client offline session is allowed to be idle before it expires. Offline tokens are invalidated when a client offline session is expired. If not set it uses the Offline Session Idle value.
|
||||
client-offline-session-max=Client Offline Session Max
|
||||
client-offline-session-max.tooltip=Max time before a client offline session is expired. Offline tokens are invalidated when a client offline session is expired. If not set, it uses the Offline 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
|
||||
|
|
|
@ -1114,6 +1114,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
|
|||
$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']);
|
||||
$scope.clientOfflineSessionIdleTimeout = TimeUnit2.asUnit(client.attributes['client.offline.session.idle.timeout']);
|
||||
$scope.clientOfflineSessionMaxLifespan = TimeUnit2.asUnit(client.attributes['client.offline.session.max.lifespan']);
|
||||
|
||||
if(client.origin) {
|
||||
if ($scope.access.viewRealm) {
|
||||
|
@ -1464,6 +1466,22 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
|
|||
}
|
||||
}
|
||||
|
||||
$scope.updateClientOfflineSessionIdleTimeout = function() {
|
||||
if ($scope.clientOfflineSessionIdleTimeout.time) {
|
||||
$scope.clientEdit.attributes['client.offline.session.idle.timeout'] = $scope.clientOfflineSessionIdleTimeout.toSeconds();
|
||||
} else {
|
||||
$scope.clientEdit.attributes['client.offline.session.idle.timeout'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.updateClientOfflineSessionMaxLifespan = function() {
|
||||
if ($scope.clientOfflineSessionMaxLifespan.time) {
|
||||
$scope.clientEdit.attributes['client.offline.session.max.lifespan'] = $scope.clientOfflineSessionMaxLifespan.toSeconds();
|
||||
} else {
|
||||
$scope.clientEdit.attributes['client.offline.session.max.lifespan'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
function configureAuthorizationServices() {
|
||||
if ($scope.clientEdit.authorizationServicesEnabled) {
|
||||
if ($scope.accessType == 'public') {
|
||||
|
|
|
@ -1162,6 +1162,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
|
|||
$scope.realm.offlineSessionMaxLifespan = TimeUnit2.asUnit(realm.offlineSessionMaxLifespan);
|
||||
$scope.realm.clientSessionIdleTimeout = TimeUnit2.asUnit(realm.clientSessionIdleTimeout);
|
||||
$scope.realm.clientSessionMaxLifespan = TimeUnit2.asUnit(realm.clientSessionMaxLifespan);
|
||||
$scope.realm.clientOfflineSessionIdleTimeout = TimeUnit2.asUnit(realm.clientOfflineSessionIdleTimeout);
|
||||
$scope.realm.clientOfflineSessionMaxLifespan = TimeUnit2.asUnit(realm.clientOfflineSessionMaxLifespan);
|
||||
$scope.realm.accessCodeLifespan = TimeUnit2.asUnit(realm.accessCodeLifespan);
|
||||
$scope.realm.accessCodeLifespanLogin = TimeUnit2.asUnit(realm.accessCodeLifespanLogin);
|
||||
$scope.realm.accessCodeLifespanUserAction = TimeUnit2.asUnit(realm.accessCodeLifespanUserAction);
|
||||
|
@ -1219,6 +1221,8 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
|
|||
$scope.realm.offlineSessionMaxLifespan = $scope.realm.offlineSessionMaxLifespan.toSeconds();
|
||||
$scope.realm.clientSessionIdleTimeout = $scope.realm.clientSessionIdleTimeout.toSeconds();
|
||||
$scope.realm.clientSessionMaxLifespan = $scope.realm.clientSessionMaxLifespan.toSeconds();
|
||||
$scope.realm.clientOfflineSessionIdleTimeout = $scope.realm.clientOfflineSessionIdleTimeout.toSeconds();
|
||||
$scope.realm.clientOfflineSessionMaxLifespan = $scope.realm.clientOfflineSessionMaxLifespan.toSeconds();
|
||||
$scope.realm.accessCodeLifespan = $scope.realm.accessCodeLifespan.toSeconds();
|
||||
$scope.realm.accessCodeLifespanUserAction = $scope.realm.accessCodeLifespanUserAction.toSeconds();
|
||||
$scope.realm.accessCodeLifespanLogin = $scope.realm.accessCodeLifespanLogin.toSeconds();
|
||||
|
|
|
@ -579,6 +579,42 @@
|
|||
<kc-tooltip>{{:: 'client-session-max.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-ng-show="protocol == 'openid-connect'">
|
||||
<label class="col-md-2 control-label" for="clientOfflineSessionIdleTimeout">{{::
|
||||
'client-offline-session-idle' | translate}}</label>
|
||||
<div class="col-md-6 time-selector">
|
||||
<input class="form-control" type="number" min="0" max="31536000"
|
||||
data-ng-model="clientOfflineSessionIdleTimeout.time" id="clientOfflineSessionIdleTimeout"
|
||||
name="clientOfflineSessionIdleTimeout" data-ng-change="updateClientOfflineSessionIdleTimeout()" /> <select
|
||||
class="form-control" name="clientOfflineSessionIdleTimeoutUnit"
|
||||
data-ng-model="clientOfflineSessionIdleTimeout.unit"
|
||||
data-ng-change="updateClientOfflineSessionIdleTimeout()">
|
||||
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||
<option value="Days">{{:: 'days' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'client-offline-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="clientOfflineSessionMaxLifespan">{{::
|
||||
'client-offline-session-max' | translate}}</label>
|
||||
<div class="col-md-6 time-selector">
|
||||
<input class="form-control" type="number" min="0" max="31536000"
|
||||
data-ng-model="clientOfflineSessionMaxLifespan.time" id="clientOfflineSessionMaxLifespan"
|
||||
name="clientOfflineSessionMaxLifespan" data-ng-change="updateClientOfflineSessionMaxLifespan()" /> <select
|
||||
class="form-control" name="clientOfflineSessionMaxLifespanUnit"
|
||||
data-ng-model="clientOfflineSessionMaxLifespan.unit"
|
||||
data-ng-change="updateClientOfflineSessionMaxLifespan()">
|
||||
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||
<option value="Days">{{:: 'days' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'client-offline-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">
|
||||
|
|
|
@ -145,6 +145,38 @@
|
|||
<kc-tooltip>{{:: 'offline-session-max.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-ng-show="realm.offlineSessionMaxLifespanEnabled">
|
||||
<label class="col-md-2 control-label" for="clientOfflineSessionIdleTimeout">{{::
|
||||
'client-offline-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.clientOfflineSessionIdleTimeout.time" id="clientOfflineSessionIdleTimeout"
|
||||
name="clientOfflineSessionIdleTimeout" /> <select class="form-control"
|
||||
name="clientOfflineSessionIdleTimeoutUnit" data-ng-model="realm.clientOfflineSessionIdleTimeout.unit">
|
||||
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||
<option value="Days">{{:: 'days' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'client-offline-session-idle.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-ng-show="realm.offlineSessionMaxLifespanEnabled">
|
||||
<label class="col-md-2 control-label" for="clientOfflineSessionMaxLifespan">{{::
|
||||
'client-offline-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.clientOfflineSessionMaxLifespan.time" id="clientOfflineSessionMaxLifespan"
|
||||
name="clientOfflineSessionMaxLifespan" /> <select class="form-control"
|
||||
name="clientOfflineSessionMaxLifespanUnit" data-ng-model="realm.clientOfflineSessionMaxLifespan.unit">
|
||||
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||
<option value="Days">{{:: 'days' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'client-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">
|
||||
|
|
Loading…
Reference in a new issue