From 30bb6e3930d5019fedc4ff2c50163e434159515d Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 14 Oct 2024 19:06:03 +0200 Subject: [PATCH] Enable enforcement of a minimum ACR at the client level (#16884) Signed-off-by: Simon Levermann --- .../images/client-oidc-map-acr-to-loa.png | Bin 29662 -> 29629 bytes .../clients/oidc/con-advanced-settings.adoc | 5 +- .../admin/messages/messages_en.properties | 2 + .../src/clients/advanced/AdvancedSettings.tsx | 8 +- .../java/org/keycloak/models/Constants.java | 1 + .../oidc/OIDCAdvancedConfigWrapper.java | 10 +- .../oidc/endpoints/AuthorizationEndpoint.java | 9 ++ .../protocol/oidc/utils/AcrUtils.java | 65 +++++++- .../DefaultClientValidationProvider.java | 25 ++- .../forms/LevelOfAssuranceFlowTest.java | 147 ++++++++++++++++++ 10 files changed, 262 insertions(+), 10 deletions(-) diff --git a/docs/documentation/server_admin/images/client-oidc-map-acr-to-loa.png b/docs/documentation/server_admin/images/client-oidc-map-acr-to-loa.png index fbce25023efdeffffd72393b3c3fab1677c7dabc..9aa56208af4a2dcf758fa21160df3fdc39b64ef6 100644 GIT binary patch literal 29629 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 fySnv;Oi8&tY4*OW7L>Iu diff --git a/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc b/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc index 0bfb1a88e01d..fc830e4d9d6a 100644 --- a/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc +++ b/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc @@ -186,8 +186,11 @@ a `claims` parameter that has an `acr` claim attached. See https://openid.net/sp WARNING: Note that default ACR values are used as the default level, however it cannot be reliably used to enforce login with the particular level. For example, assume that you configure the `Default ACR Values` to level 2. Then by default, users will be required to authenticate with level 2. -However when the user explicitly attaches the parameter into login request such as `acr_values=1`, then the level 1 will be used. As a result, if the client +However, when the user explicitly attaches the parameter into login request such as `acr_values=1`, then the level 1 will be used. As a result, if the client really requires level 2, the client is encouraged to check the presence of the `acr` claim inside ID Token and double-check that it contains the requested level 2. +To actually enforce the usage of a certain ACR on the {project_name} side, use the `Minimum ACR Value` setting. +This allows administrators to enforce ACRs even on applications that are not able to validate the requested `acr` claim inside the token. + image:images/client-oidc-map-acr-to-loa.png[alt="ACR to LoA mapping"] diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index aad98e85ab50..119b0c7fc585 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -1358,6 +1358,7 @@ authorizationEncryptedResponseAlgHelp=JWA Algorithm used for key management in e deleteConfirmGroup_other=Are you sure you want to delete these groups. scopePermissions.users.manage-description=Policies that decide if an administrator can manage all users in the realm defaultACRValuesHelp=Default values to be used as voluntary ACR in case that there is no explicit ACR requested by 'claims' or 'acr_values' parameter in the OIDC request. +minimumACRValueHelp=Minimum ACR to be enforced by keycloak. Overrides lower ACRs explicitly requested via 'acr_values' or 'claims', unless marked they are essential membershipAttributeType=Membership attribute type eventTypes.PUSHED_AUTHORIZATION_REQUEST.name=Pushed authorization request included.client.audience.tooltip=The Client ID of the specified audience client will be included in audience (aud) field of the token. If there are existing audiences in the token, the specified value is just added to them. It won't override existing audiences. @@ -1373,6 +1374,7 @@ otpPolicyDigitsHelp=How many digits should the OTP have? clientAuthentications.client_secret_post=Client secret sent as post prompts.select_account=Select account defaultACRValues=Default ACR Values +minimumACRValue=Minimum ACR Value valueError=A value must be provided. noConsents=No consents orderChangeSuccessUserFed=Successfully changed the priority order of user federation providers diff --git a/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx b/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx index 7da2fcb80ad7..04ce773d4a6a 100644 --- a/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx +++ b/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx @@ -1,4 +1,4 @@ -import { HelpItem } from "@keycloak/keycloak-ui-shared"; +import { HelpItem, TextControl } from "@keycloak/keycloak-ui-shared"; import { ActionGroup, Button, @@ -249,6 +249,12 @@ export const AdvancedSettings = ({ stringify /> + )} diff --git a/server-spi-private/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java index fb2b29faf858..eccfb03e7917 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/Constants.java +++ b/server-spi-private/src/main/java/org/keycloak/models/Constants.java @@ -163,6 +163,7 @@ public final class Constants { public static final String FORCE_LEVEL_OF_AUTHENTICATION = "force-level-of-authentication"; public static final String ACR_LOA_MAP = "acr.loa.map"; public static final String DEFAULT_ACR_VALUES = "default.acr.values"; + public static final String MINIMUM_ACR_VALUE = "minimum.acr.value"; public static final int MINIMUM_LOA = 0; public static final int NO_LOA = -1; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java index 3dc0ff2f8de4..08a9c8e88551 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java @@ -21,6 +21,7 @@ import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator; import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.util.DPoPUtil; import org.keycloak.utils.StringUtil; @@ -106,7 +107,7 @@ public void setRequestObjectEncryptionEnc(String algorithm) { public String getRequestObjectRequired() { return getAttribute(OIDCConfigAttributes.REQUEST_OBJECT_REQUIRED); } - + public void setRequestObjectRequired(String requestObjectRequired) { setAttribute(OIDCConfigAttributes.REQUEST_OBJECT_REQUIRED, requestObjectRequired); } @@ -413,4 +414,11 @@ public void setPostLogoutRedirectUris(List postLogoutRedirectUris) { setAttributeMultivalued(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, postLogoutRedirectUris); } + public String getMinimumAcrValue() { + return getAttribute(Constants.MINIMUM_ACR_VALUE); + } + + public void setMinimumAcrValue(String minimumAcrValue) { + setAttribute(Constants.MINIMUM_ACR_VALUE, minimumAcrValue); + } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 34fa16c3f1c8..2679291dc7d1 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -62,6 +62,7 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -317,6 +318,14 @@ private void updateAuthenticationSession() { if (acrValues.isEmpty()) { acrValues = AcrUtils.getAcrValues(request.getClaims(), request.getAcr(), authenticationSession.getClient()); } else { + List minimizedAcrValues = AcrUtils.enforceMinimumAcr(acrValues, client); + // If enforcing a minimum here changes the list, the client has an essential claim that is too low + if (!minimizedAcrValues.equals(acrValues)) { + logger.errorf("Requested essential acr value list contains values lower than the client minimum. Please doublecheck the client configuration or correct ACR passed in the 'claims' parameter."); + event.detail(Details.REASON, "Invalid requested essential acr value"); + event.error(Errors.INVALID_REQUEST); + throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.CLAIMS_PARAM); + } authenticationSession.setClientNote(Constants.FORCE_LEVEL_OF_AUTHENTICATION, "true"); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/AcrUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/AcrUtils.java index 8611093622e4..9b54ad27026c 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/AcrUtils.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/AcrUtils.java @@ -23,9 +23,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; + import org.jboss.logging.Logger; +import org.keycloak.authentication.authenticators.util.LoAUtil; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.RealmModel; @@ -42,14 +46,61 @@ public static List getRequiredAcrValues(String claimsParam) { return getAcrValues(claimsParam, null, true); } + public static List getAcrValues(String claimsParam, String acrValuesParam, ClientModel client) { - List fromParams = getAcrValues(claimsParam, acrValuesParam, false); - if (!fromParams.isEmpty()) { - return fromParams; + List acrValues = getAcrValues(claimsParam, acrValuesParam, false); + + if (acrValues.isEmpty()) { + // Fallback to default ACR values of client (if configured) + acrValues = getDefaultAcrValues(client); } + return enforceMinimumAcr(acrValues, client); + } + + public static List enforceMinimumAcr(List acrValues, ClientModel client) { + String minimumAcr = getMinimumAcrValue(client); - // Fallback to default ACR values of client (if configured) - return getDefaultAcrValues(client); + // If a minimum is set, we need to validate the client didn't request a lower ACR + if (minimumAcr != null) { + List acrCopy = new ArrayList<>(acrValues); + Map acrMap = getAcrLoaMap(client); + Integer minimumLoa = getLoaForAcr(minimumAcr, acrMap, client); + if (minimumLoa == null) { + LOGGER.warnf("ACR '%s' can not be mapped to a LoA value.", minimumAcr); + } else { + // Remove all ACRs lower than the minimum + Iterator iterator = acrCopy.iterator(); + while (iterator.hasNext()) { + String acrValue = iterator.next(); + Integer loa = getLoaForAcr(acrValue, acrMap, client); + if (loa == null) { + LOGGER.warnf("ACR '%s' can not be mapped to a LoA value.", acrValue); + iterator.remove(); + } else if (loa < minimumLoa) { + iterator.remove(); + } + } + // All ACRs lower than the minimum are gone, if we have none left, add our minimum + if (acrCopy.isEmpty()) { + acrCopy.add(minimumAcr); + } + } + return acrCopy; + } + return acrValues; + } + + private static Integer getLoaForAcr(String acr, Map acrMap, ClientModel client) { + Integer loa = acrMap.get(acr); + if (loa == null) { + Optional loaFromFlows = LoAUtil.getLoAConfiguredInRealmBrowserFlow(client.getRealm()) + .filter(l -> acr.equals(String.valueOf(l))) + .findFirst(); + if (loaFromFlows.isPresent()) { + loa = loaFromFlows.get(); + } + } + return loa; } private static List getAcrValues(String claimsParam, String acrValuesParam, boolean essential) { @@ -152,4 +203,8 @@ public static String mapLoaToAcr(int loa, Map acrLoaMap, Collec public static List getDefaultAcrValues(ClientModel client) { return OIDCAdvancedConfigWrapper.fromClientModel(client).getAttributeMultivalued(Constants.DEFAULT_ACR_VALUES); } + + public static String getMinimumAcrValue(ClientModel client) { + return OIDCAdvancedConfigWrapper.fromClientModel(client).getMinimumAcrValue(); + } } diff --git a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java index 0ce32eb71bfd..f67b225b6078 100644 --- a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java +++ b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java @@ -31,6 +31,7 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.services.util.ResolveRelative; +import org.keycloak.services.validation.Validation; import java.net.MalformedURLException; import java.net.URI; @@ -185,6 +186,7 @@ public ValidationResult validate(ValidationContext context) { new CibaClientValidation(context).validate(); validateJwks(context); validateDefaultAcrValues(context); + validateMinimumAcrValue(context); return context.toResult(); } @@ -195,6 +197,7 @@ public ValidationResult validate(ClientValidationContext.OIDCContext context) { validatePairwiseInOIDCClient(context); new CibaClientValidation(context).validate(); validateDefaultAcrValues(context); + validateMinimumAcrValue(context); return context.toResult(); } @@ -379,10 +382,28 @@ private void validateDefaultAcrValues(ValidationContext context) { } for (String configuredAcr : defaultAcrValues) { if (acrToLoaMap.containsKey(configuredAcr)) continue; - if (!LoAUtil.getLoAConfiguredInRealmBrowserFlow(client.getRealm()) - .anyMatch(level -> configuredAcr.equals(String.valueOf(level)))) { + if (LoAUtil.getLoAConfiguredInRealmBrowserFlow(client.getRealm()) + .noneMatch(level -> configuredAcr.equals(String.valueOf(level)))) { context.addError("defaultAcrValues", "Default ACR values need to contain values specified in the ACR-To-Loa mapping or number levels from set realm browser flow"); } } } + + private void validateMinimumAcrValue(ValidationContext context) { + ClientModel client = context.getObjectToValidate(); + String minimumAcrValue = AcrUtils.getMinimumAcrValue(client); + if (minimumAcrValue != null) { + Map acrToLoaMap = AcrUtils.getAcrLoaMap(client); + if (acrToLoaMap.isEmpty()) { + acrToLoaMap = AcrUtils.getAcrLoaMap(client.getRealm()); + } + + if(!acrToLoaMap.containsKey(minimumAcrValue)) { + if (LoAUtil.getLoAConfiguredInRealmBrowserFlow(client.getRealm()) + .noneMatch(level -> minimumAcrValue.equals(String.valueOf(level)))) { + context.addError("minimumAcrValue", "Minimum ACR value needs to be value specified in the ACR-To-Loa mapping or number level from set realm browser flow"); + } + } + } + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java index b25859253ec0..cac0e98228c2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java @@ -524,6 +524,41 @@ public void testClientDefaultAcrValuesValidation() throws IOException { testRealm().update(realmRep); } + @Test + public void testClientMinimumAcrValueValidation() throws IOException { + // Setup realm acr-to-loa mapping + RealmRepresentation realmRep = testRealm().toRepresentation(); + Map acrLoaMap = new HashMap<>(); + acrLoaMap.put("realm:copper", 0); + acrLoaMap.put("realm:silver", 1); + realmRep.getAttributes().put(Constants.ACR_LOA_MAP, JsonSerialization.writeValueAsString(acrLoaMap)); + testRealm().update(realmRep); + + // Value "foo" not used in any ACR-To-Loa mapping + ClientResource testClient = ApiUtil.findClientByClientId(testRealm(), "test-app"); + ClientRepresentation testClientRep = testClient.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("foo"); + Assert.assertThrows(BadRequestException.class, () -> { + testClient.update(testClientRep); + }); + + // Realm value should not be considered either + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("realm:silver"); + Assert.assertThrows(BadRequestException.class, () -> { + testClient.update(testClientRep); + }); + + // Value from client map should be OK + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("silver"); + testClient.update(testClientRep); + + // Cleanup + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue(null); + testClient.update(testClientRep); + realmRep.getAttributes().remove(Constants.ACR_LOA_MAP); + testRealm().update(realmRep); + } + // After initial authentication with "acr=2", there will be further re-authentication requests sent in different intervals // without "acr" parameter. User should be always re-authenticated due SSO, but with different acr levels due their gradual expirations @Test @@ -893,6 +928,118 @@ public void testWithOTPAndRecoveryCodesAtLevel2() { } } + @Test + public void testLoginWithMinimumAcrWithoutAcrValues() { + ClientResource testClient = ApiUtil.findClientByClientId(testRealm(), "test-app"); + ClientRepresentation testClientRep = testClient.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("gold"); + testClient.update(testClientRep); + + // Should request client to authenticate with gold + oauth.openLoginForm(); + authenticateWithUsernamePassword(); + authenticateWithTotp(); + assertLoggedInWithAcr("gold"); + + // Revert + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue(null); + testClient.update(testClientRep); + } + + @Test + public void testLoginWithMinimumAcrWithLowerAcrValues() { + ClientResource testClient = ApiUtil.findClientByClientId(testRealm(), "test-app"); + ClientRepresentation testClientRep = testClient.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("gold"); + testClient.update(testClientRep); + + // Should request client to authenticate with gold, even if the client sends silver + driver.navigate().to(UriBuilder.fromUri(oauth.getLoginFormUrl()) + .queryParam("acr_values", "silver") + .build().toString()); + authenticateWithUsernamePassword(); + authenticateWithTotp(); + assertLoggedInWithAcr("gold"); + + // Revert + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue(null); + testClient.update(testClientRep); + } + + @Test + public void testLoginWithMinimumAcrWithHigherAcrValues() { + ClientResource testClient = ApiUtil.findClientByClientId(testRealm(), "test-app"); + ClientRepresentation testClientRep = testClient.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("gold"); + testClient.update(testClientRep); + + // Should request client to authenticate with gold, even if the client sends silver + driver.navigate().to(UriBuilder.fromUri(oauth.getLoginFormUrl()) + .queryParam("acr_values", "3") + .build().toString()); + authenticateWithUsernamePassword(); + authenticateWithTotp(); + authenticateWithButton(); + assertLoggedInWithAcr("3"); + + // Revert + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue(null); + testClient.update(testClientRep); + } + + @Test + public void testEssentialAcrMinimumOk() { + ClientResource testClient = ApiUtil.findClientByClientId(testRealm(), "test-app"); + ClientRepresentation testClientRep = testClient.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("gold"); + testClient.update(testClientRep); + + // username, password input and finally push button for gold + openLoginFormWithAcrClaim(true, "gold"); + + authenticateWithUsernamePassword(); + authenticateWithTotp(); + assertLoggedInWithAcr("gold"); + + // Revert + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue(null); + testClient.update(testClientRep); + } + + @Test + public void testEssentialAcrMinimumTooLow() { + ClientResource testClient = ApiUtil.findClientByClientId(testRealm(), "test-app"); + ClientRepresentation testClientRep = testClient.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("gold"); + testClient.update(testClientRep); + + // requesting a too low essential acr should fail + openLoginFormWithAcrClaim(true, "silver"); + assertErrorPage("Invalid parameter: claims"); + + // Revert + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue(null); + testClient.update(testClientRep); + } + + @Test + public void testNonEssentialAcrMinimumUpgrade() { + ClientResource testClient = ApiUtil.findClientByClientId(testRealm(), "test-app"); + ClientRepresentation testClientRep = testClient.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue("gold"); + testClient.update(testClientRep); + + // requesting a too low non-essential ACR should be upgraded + openLoginFormWithAcrClaim(false, "silver"); + authenticateWithUsernamePassword(); + authenticateWithTotp(); + assertLoggedInWithAcr("gold"); + + // Revert + OIDCAdvancedConfigWrapper.fromClientRepresentation(testClientRep).setMinimumAcrValue(null); + testClient.update(testClientRep); + } + private String getCredentialIdByLabel(String credentialLabel) { return ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost").credentials() .stream()