mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -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
|
=== 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,
|
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.
|
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.
|
{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.
|
||||||
It does currently not validate the 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.
|
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 ------------------------ //
|
||||||
== Notable changes
|
== Notable changes
|
||||||
|
|||||||
@ -900,6 +900,7 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||||||
|
|
||||||
updateCibaSettings(rep, realm);
|
updateCibaSettings(rep, realm);
|
||||||
updateParSettings(rep, realm);
|
updateParSettings(rep, realm);
|
||||||
|
validateClientAndRealmTimeouts(realm);
|
||||||
session.clientPolicy().updateRealmModelFromRepresentation(realm, rep);
|
session.clientPolicy().updateRealmModelFromRepresentation(realm, rep);
|
||||||
|
|
||||||
if (rep.getSmtpServer() != null) {
|
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.isAdminEventsEnabled(), actual.isAdminEventsEnabled());
|
||||||
assertEquals(expected.isAdminEventsDetailsEnabled(), actual.isAdminEventsDetailsEnabled());
|
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
|
@Test
|
||||||
public void refreshTokenUserSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
public void refreshTokenUserSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
||||||
RealmResource realmResource = adminClient.realm("test");
|
RealmResource realmResource = adminClient.realm("test");
|
||||||
@ -1537,6 +1491,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||||||
|
|
||||||
RealmRepresentation rep = realmResource.toRepresentation();
|
RealmRepresentation rep = realmResource.toRepresentation();
|
||||||
rep.setSsoSessionMaxLifespan(3600);
|
rep.setSsoSessionMaxLifespan(3600);
|
||||||
|
rep.setClientSessionMaxLifespan(3600);
|
||||||
realmResource.update(rep);
|
realmResource.update(rep);
|
||||||
|
|
||||||
setTimeOffset(3700);
|
setTimeOffset(3700);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user