Remove option Nerver Expires for tokens in Advanced OIDC client configuration

Closes https://github.com/keycloak/keycloak/issues/21927
This commit is contained in:
rmartinc 2023-07-28 16:12:48 +02:00 committed by Marek Posolda
parent 4dc929abb3
commit 05bac4ff0e
6 changed files with 76 additions and 38 deletions

View file

@ -0,0 +1,3 @@
= Never expires option removed from client advanced settings combos
The option `Never expires` is now removed from all the combos of the Advanced Settings client tab. This option was misleading because the different lifespans or idle timeouts were never infinite, but limited by the general user session or realm values. Therefore, this option is removed in favor of the other two remaining options: `Inherits from the realm settings` (the client uses general realm timeouts) and `Expires in` (the value is overriden for the client). Internally the `Never expires` was represented by `-1`. Now that value is shown with a warning in the Admin Console and cannot be set directly by the administrator.

View file

@ -4,6 +4,10 @@
include::changes-23_0_0.adoc[leveloffset=3] include::changes-23_0_0.adoc[leveloffset=3]
=== Migrating to 22.0.2
include::changes-22_0_2.adoc[leveloffset=3]
=== Migrating to 22.0.0 === Migrating to 22.0.0
include::changes-22_0_0.adoc[leveloffset=3] include::changes-22_0_0.adoc[leveloffset=3]

View file

@ -132,7 +132,11 @@ export const AdvancedSettings = ({
name={convertAttributeNameToForm( name={convertAttributeNameToForm(
"attributes.client.offline.session.max.lifespan", "attributes.client.offline.session.max.lifespan",
)} )}
defaultValue={realm?.offlineSessionMaxLifespan} defaultValue={
realm?.offlineSessionMaxLifespanEnabled
? realm?.offlineSessionMaxLifespan
: undefined
}
units={["minute", "day", "hour"]} units={["minute", "day", "hour"]}
/> />

View file

@ -24,7 +24,6 @@ type TokenLifespanProps = {
}; };
const inherited = "tokenLifespan.inherited"; const inherited = "tokenLifespan.inherited";
const never = "tokenLifespan.never";
const expires = "tokenLifespan.expires"; const expires = "tokenLifespan.expires";
export const TokenLifespan = ({ export const TokenLifespan = ({
@ -42,8 +41,8 @@ export const TokenLifespan = ({
const { control } = useFormContext(); const { control } = useFormContext();
const isExpireSet = (value: string | number) => const isExpireSet = (value: string | number) =>
(typeof value === "number" && value !== -1) || typeof value === "number" ||
(typeof value === "string" && value !== "" && value !== "-1") || (typeof value === "string" && value !== "") ||
focused; focused;
return ( return (
@ -73,30 +72,28 @@ export const TokenLifespan = ({
setOpen(false); setOpen(false);
}} }}
selections={[ selections={[
isExpireSet(field.value) isExpireSet(field.value) ? t(expires) : t(inherited),
? t(expires)
: field.value === ""
? t(inherited)
: t(never),
]} ]}
> >
<SelectOption value="">{t(inherited)}</SelectOption> <SelectOption value="">{t(inherited)}</SelectOption>
<SelectOption value={-1}>{t(never)}</SelectOption>
<SelectOption value={60}>{t(expires)}</SelectOption> <SelectOption value={60}>{t(expires)}</SelectOption>
</Select> </Select>
</SplitItem> </SplitItem>
<SplitItem> <SplitItem>
{field.value !== "-1" && field.value !== -1 && ( <TimeSelector
<TimeSelector validated={
units={units} isExpireSet(field.value) && field.value! < 1
value={field.value === "" ? defaultValue : field.value} ? "warning"
onChange={field.onChange} : "default"
onFocus={onFocus} }
onBlur={onBlur} units={units}
min={1} value={field.value === "" ? defaultValue : field.value}
isDisabled={field.value === ""} onChange={field.onChange}
/> onFocus={onFocus}
)} onBlur={onBlur}
min={1}
isDisabled={!isExpireSet(field.value)}
/>
</SplitItem> </SplitItem>
</Split> </Split>
)} )}

View file

@ -21,6 +21,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.utils.StringUtil;
/** /**
* <p>Shared methods to calculate the session expiration and idle.</p> * <p>Shared methods to calculate the session expiration and idle.</p>
@ -92,30 +93,29 @@ public class SessionExpirationUtils {
long clientSessionCreated, long userSessionCreated, RealmModel realm, ClientModel client) { long clientSessionCreated, long userSessionCreated, RealmModel realm, ClientModel client) {
long timestamp = -1; long timestamp = -1;
if (offline) { if (offline) {
if (realm.isOfflineSessionMaxLifespanEnabled()) { long clientOfflineSessionMaxLifespan = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN);
long clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(getOfflineSessionMaxLifespan(realm)); if (realm.isOfflineSessionMaxLifespanEnabled() || clientOfflineSessionMaxLifespan > 0) {
if (clientOfflineSessionMaxLifespan > 0) {
String clientOfflineSessionMaxLifespanPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN); clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(clientOfflineSessionMaxLifespan);
if (clientOfflineSessionMaxLifespanPerClient != null && !clientOfflineSessionMaxLifespanPerClient.trim().isEmpty()) {
clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(Long.parseLong(clientOfflineSessionMaxLifespanPerClient));
} else if (realm.getClientOfflineSessionMaxLifespan() > 0) { } else if (realm.getClientOfflineSessionMaxLifespan() > 0) {
clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(realm.getClientOfflineSessionMaxLifespan()); clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(realm.getClientOfflineSessionMaxLifespan());
} else {
clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(getOfflineSessionMaxLifespan(realm));
} }
timestamp = clientSessionCreated + clientOfflineSessionMaxLifespan; timestamp = clientSessionCreated + clientOfflineSessionMaxLifespan;
long userSessionExpires = calculateUserSessionMaxLifespanTimestamp(offline, isRememberMe, userSessionCreated, realm); long userSessionExpires = calculateUserSessionMaxLifespanTimestamp(offline, isRememberMe, userSessionCreated, realm);
timestamp = Math.min(timestamp, userSessionExpires); timestamp = userSessionExpires > 0? Math.min(timestamp, userSessionExpires) : timestamp;
} }
} else { } else {
long clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(getSsoSessionMaxLifespan(realm)); long clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(getSsoSessionMaxLifespan(realm));
if (isRememberMe) { if (isRememberMe) {
clientSessionMaxLifespan = Math.max(clientSessionMaxLifespan, TimeUnit.SECONDS.toMillis(realm.getSsoSessionMaxLifespanRememberMe())); clientSessionMaxLifespan = Math.max(clientSessionMaxLifespan, TimeUnit.SECONDS.toMillis(realm.getSsoSessionMaxLifespanRememberMe()));
} }
String clientSessionMaxLifespanPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN); long clientSessionMaxLifespanPerClient = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN);
if (clientSessionMaxLifespanPerClient != null && !clientSessionMaxLifespanPerClient.trim().isEmpty()) { if (clientSessionMaxLifespanPerClient > 0) {
clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(Long.parseLong(clientSessionMaxLifespanPerClient)); clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(clientSessionMaxLifespanPerClient);
} else if (realm.getClientSessionMaxLifespan() > 0) { } else if (realm.getClientSessionMaxLifespan() > 0) {
clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(realm.getClientSessionMaxLifespan()); clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(realm.getClientSessionMaxLifespan());
} }
@ -144,9 +144,9 @@ public class SessionExpirationUtils {
long timestamp; long timestamp;
if (offline) { if (offline) {
long clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(getOfflineSessionIdleTimeout(realm)); long clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(getOfflineSessionIdleTimeout(realm));
String clientOfflineSessionIdleTimeoutPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT); long clientOfflineSessionIdleTimeoutPerClient = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT);
if (clientOfflineSessionIdleTimeoutPerClient != null && !clientOfflineSessionIdleTimeoutPerClient.trim().isEmpty()) { if (clientOfflineSessionIdleTimeoutPerClient > 0) {
clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(Long.parseLong(clientOfflineSessionIdleTimeoutPerClient)); clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(clientOfflineSessionIdleTimeoutPerClient);
} else if (realm.getClientOfflineSessionIdleTimeout() > 0) { } else if (realm.getClientOfflineSessionIdleTimeout() > 0) {
clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(realm.getClientOfflineSessionIdleTimeout()); clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(realm.getClientOfflineSessionIdleTimeout());
} }
@ -157,9 +157,9 @@ public class SessionExpirationUtils {
if (isRememberMe) { if (isRememberMe) {
clientSessionIdleTimeout = Math.max(clientSessionIdleTimeout, TimeUnit.SECONDS.toMillis(realm.getSsoSessionIdleTimeoutRememberMe())); clientSessionIdleTimeout = Math.max(clientSessionIdleTimeout, TimeUnit.SECONDS.toMillis(realm.getSsoSessionIdleTimeoutRememberMe()));
} }
String clientSessionIdleTimeoutPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT); long clientSessionIdleTimeoutPerClient = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT);
if (clientSessionIdleTimeoutPerClient != null && !clientSessionIdleTimeoutPerClient.trim().isEmpty()) { if (clientSessionIdleTimeoutPerClient > 0) {
clientSessionIdleTimeout = TimeUnit.SECONDS.toMillis(Long.parseLong(clientSessionIdleTimeoutPerClient)); clientSessionIdleTimeout = TimeUnit.SECONDS.toMillis(clientSessionIdleTimeoutPerClient);
} else if (realm.getClientSessionIdleTimeout() > 0){ } else if (realm.getClientSessionIdleTimeout() > 0){
clientSessionIdleTimeout = TimeUnit.SECONDS.toMillis(realm.getClientSessionIdleTimeout()); clientSessionIdleTimeout = TimeUnit.SECONDS.toMillis(realm.getClientSessionIdleTimeout());
} }
@ -200,4 +200,18 @@ public class SessionExpirationUtils {
} }
return idle; return idle;
} }
private static long getClientAttributeTimeout(ClientModel client, String attr) {
if (client != null) {
final String value = client.getAttribute(attr);
if (StringUtil.isNotBlank(value)) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
// no-op
}
}
}
return -1;
}
} }

View file

@ -180,6 +180,9 @@ public class SessionExpirationUtilsTest {
realmMap.put("getSsoSessionMaxLifespan", 1000); realmMap.put("getSsoSessionMaxLifespan", 1000);
realmMap.put("getSsoSessionMaxLifespanRememberMe", 2000); realmMap.put("getSsoSessionMaxLifespanRememberMe", 2000);
Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(false, true, t, t, realm, client) - t); Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(false, true, t, t, realm, client) - t);
// set -1 in the client and should be not taken into account
clientMap.put(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN, "-1");
Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(false, true, t, t, realm, client) - t);
} }
@Test @Test
@ -207,6 +210,13 @@ public class SessionExpirationUtilsTest {
long t2 = t - 100; long t2 = t - 100;
realmMap.put("getOfflineSessionMaxLifespan", 2000); realmMap.put("getOfflineSessionMaxLifespan", 2000);
Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(true, false, t, t2, realm, client) - t2); Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(true, false, t, t2, realm, client) - t2);
// set -1 in the client and should be not taken into account
clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, "-1");
Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(true, false, t, t, realm, client) - t);
// set no expiration at realm but set expiration at client level
realmMap.put("isOfflineSessionMaxLifespanEnabled", false);
clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, "2000");
Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(true, false, t, t, realm, client) - t);
} }
@Test @Test
@ -230,6 +240,9 @@ public class SessionExpirationUtilsTest {
// override value in client // override value in client
clientMap.put(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT, "3000"); clientMap.put(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT, "3000");
Assert.assertEquals(3000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(false, false, t, realm, client) - t); Assert.assertEquals(3000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(false, false, t, realm, client) - t);
// set -1 in the client and should be not taken into account
clientMap.put(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT, "-1");
Assert.assertEquals(4000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(false, false, t, realm, client) - t);
} }
@Test @Test
@ -253,5 +266,8 @@ public class SessionExpirationUtilsTest {
// override value in client // override value in client
clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, "3000"); clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, "3000");
Assert.assertEquals(3000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(true, false, t, realm, client) - t); Assert.assertEquals(3000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(true, false, t, realm, client) - t);
// set -1 in the client and should be not taken into account
clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, "-1");
Assert.assertEquals(4000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(true, false, t, realm, client) - t);
} }
} }