mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-08 14:32:05 -03:30
Validate client session timeout and lifetime settings on realm settings edit
Closes #44910 Signed-off-by: Ruchika <Ruchika.Jha1@ibm.com> Signed-off-by: Ryan Emerson <remerson@ibm.com> Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com> Co-authored-by: Ryan Emerson <remerson@ibm.com> Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com>
This commit is contained in:
parent
adeb41e82b
commit
60b369c622
@ -12,14 +12,21 @@ Setups on Windows that previously relied on custom machine names or non-standard
|
||||
|
||||
=== Validation of client session timeouts
|
||||
|
||||
Previous versions did not validate client specific settings against the realm settings for SSO session idle and max lifetime, including the remember me settings of the realm.
|
||||
Previous versions did not validate client settings against the realm settings for SSO session idle and max lifetime, including the remember me settings of the realm.
|
||||
This leads to those settings either not being effective with unexpected results to administrators,
|
||||
or to refresh tokens issued where the user session might have already expired when the client was trying to refresh the token.
|
||||
|
||||
{project_name} now validates that a client specific settings for Client Session Idle and Client Session Max does not exceed the realm settings when a client is created or updated, and will show a validation error when the validation fails.
|
||||
It does currently not validate the client settings when the realm SSO settings or remember me changes, though this might change in future releases.
|
||||
{project_name} now validates when creating or updating realms that on the realm level the Client session settings do not exceed the SSO session settings.
|
||||
|
||||
You are only affected by the change if you have configured a client-specific Client Session Idle and Client Session Max setting in the Advanced tab of the client configuration that exceeds the realm settings.
|
||||
It also validates that a client specific settings for Client Session Idle and Client Session Max does not exceed the realm settings when a client is created or updated, and will show a validation error when the validation fails.
|
||||
It does currently not validate an individual's client settings when the realm SSO settings or remember me changes, though this might change in future releases.
|
||||
|
||||
You are only affected by the change:
|
||||
|
||||
* if you have configured a client-specific Client Session Idle and Client Session Max setting in the Advanced tab of the client configuration that exceeds the realm settings, or
|
||||
* if you configured on the realm level client session settings that exceed the realm session settings.
|
||||
|
||||
If you are affected by the change, you will see error messages the next time you update or import an affected client or realm.
|
||||
|
||||
// ------------------------ Notable changes ------------------------ //
|
||||
== Notable changes
|
||||
|
||||
@ -900,6 +900,7 @@ public class DefaultExportImportManager implements ExportImportManager {
|
||||
|
||||
updateCibaSettings(rep, realm);
|
||||
updateParSettings(rep, realm);
|
||||
validateClientAndRealmTimeouts(realm);
|
||||
session.clientPolicy().updateRealmModelFromRepresentation(realm, rep);
|
||||
|
||||
if (rep.getSmtpServer() != null) {
|
||||
@ -1733,4 +1734,24 @@ public class DefaultExportImportManager implements ExportImportManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateClientAndRealmTimeouts(RealmModel realm) {
|
||||
if (realm.isRememberMe()) {
|
||||
if (realm.getClientSessionIdleTimeout() > Math.max(realm.getSsoSessionIdleTimeout(), realm.getSsoSessionIdleTimeoutRememberMe())) {
|
||||
throw new ModelException("Client session idle timeout cannot exceed realm SSO session idle timeout and RememberMe idle timeout.");
|
||||
}
|
||||
|
||||
if (realm.getClientSessionMaxLifespan() > Math.max(realm.getSsoSessionMaxLifespan(), realm.getSsoSessionMaxLifespanRememberMe())) {
|
||||
throw new ModelException("Client session max lifespan cannot exceed realm SSO session max lifespan and RememberMe Max span.");
|
||||
}
|
||||
} else {
|
||||
if (realm.getClientSessionIdleTimeout() > realm.getSsoSessionIdleTimeout()) {
|
||||
throw new ModelException("Client Session Idle Timeout cannot be greater than Realm SSO Idle Timeout.");
|
||||
}
|
||||
|
||||
if (realm.getClientSessionMaxLifespan() > realm.getSsoSessionMaxLifespan()) {
|
||||
throw new ModelException("Client session max lifespan cannot exceed realm SSO session max lifespan.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,4 +288,63 @@ public class RealmUpdateTest extends AbstractRealmTest {
|
||||
assertEquals(expected.isAdminEventsEnabled(), actual.isAdminEventsEnabled());
|
||||
assertEquals(expected.isAdminEventsDetailsEnabled(), actual.isAdminEventsDetailsEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealmClientSessionTimeoutValidation() {
|
||||
RealmRepresentation rep = managedRealm.admin().toRepresentation();
|
||||
// Remember-Me Disabled
|
||||
rep.setRememberMe(false);
|
||||
rep.setSsoSessionIdleTimeout(300);
|
||||
rep.setSsoSessionMaxLifespan(600);
|
||||
|
||||
// Invalid: client idle > realm idle
|
||||
rep.setClientSessionIdleTimeout(400);
|
||||
rep.setClientSessionMaxLifespan(500);
|
||||
|
||||
try {
|
||||
managedRealm.admin().update(rep);
|
||||
Assertions.fail("Expected validation error for client idle timeout");
|
||||
} catch (Exception e) {
|
||||
assertEquals("HTTP 400 Bad Request", e.getMessage());
|
||||
}
|
||||
|
||||
// Fix idle, break max lifespan
|
||||
rep.setClientSessionIdleTimeout(200);
|
||||
rep.setClientSessionMaxLifespan(700);
|
||||
|
||||
try {
|
||||
managedRealm.admin().update(rep);
|
||||
Assertions.fail("Expected validation error for client max lifespan");
|
||||
} catch (Exception e) {
|
||||
assertEquals("HTTP 400 Bad Request", e.getMessage());
|
||||
}
|
||||
// Remember-Me Enabled
|
||||
rep = managedRealm.admin().toRepresentation();
|
||||
rep.setRememberMe(true);
|
||||
rep.setSsoSessionIdleTimeout(300);
|
||||
rep.setSsoSessionIdleTimeoutRememberMe(500);
|
||||
rep.setSsoSessionMaxLifespan(600);
|
||||
rep.setSsoSessionMaxLifespanRememberMe(900);
|
||||
|
||||
// Invalid: exceeds allowed remember-me idle
|
||||
rep.setClientSessionIdleTimeout(550);
|
||||
rep.setClientSessionMaxLifespan(800);
|
||||
|
||||
try {
|
||||
managedRealm.admin().update(rep);
|
||||
Assertions.fail("Expected validation error for remember-me idle timeout");
|
||||
} catch (Exception e) {
|
||||
assertEquals("HTTP 400 Bad Request", e.getMessage());
|
||||
}
|
||||
// Fix idle, break max lifespan
|
||||
rep.setClientSessionIdleTimeout(300);
|
||||
rep.setClientSessionMaxLifespan(950);
|
||||
|
||||
try {
|
||||
managedRealm.admin().update(rep);
|
||||
Assertions.fail("Expected validation error for remember-me max lifespan");
|
||||
} catch (Exception e) {
|
||||
assertEquals("HTTP 400 Bad Request", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1464,52 +1464,6 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenUserClientMaxLifespanGreaterThanSession() throws Exception {
|
||||
RealmResource realmResource = adminClient.realm("test");
|
||||
getTestingClient().testing().setTestingInfinispanTimeService();
|
||||
try (Closeable ignored = new RealmAttributeUpdater(realmResource)
|
||||
.updateWith(r -> {
|
||||
r.setSsoSessionMaxLifespan(3600);
|
||||
r.setSsoSessionIdleTimeout(7200);
|
||||
r.setClientSessionMaxLifespan(5000);
|
||||
r.setClientSessionIdleTimeout(7200);
|
||||
}).update()) {
|
||||
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
String code = oauth.parseLoginResponse().getCode();
|
||||
AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code);
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 3600);
|
||||
String clientSessionId = getClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
events.poll();
|
||||
|
||||
setTimeOffset(1800);
|
||||
String refreshId = oauth.parseRefreshToken(tokenResponse.getRefreshToken()).getId();
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken());
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 1800);
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
events.expectRefresh(refreshId, sessionId).assertEvent();
|
||||
|
||||
setTimeOffset(3700);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken());
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
events.expect(EventType.REFRESH_TOKEN).error(Errors.INVALID_TOKEN).user((String) null).assertEvent();
|
||||
assertEquals(0, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
} finally {
|
||||
getTestingClient().testing().revertTestingInfinispanTimeService();
|
||||
events.clear();
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenUserSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
||||
RealmResource realmResource = adminClient.realm("test");
|
||||
@ -1537,6 +1491,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
||||
|
||||
RealmRepresentation rep = realmResource.toRepresentation();
|
||||
rep.setSsoSessionMaxLifespan(3600);
|
||||
rep.setClientSessionMaxLifespan(3600);
|
||||
realmResource.update(rep);
|
||||
|
||||
setTimeOffset(3700);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user