From 9e12587181917d67130eeb6d643913ffd87fa382 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 3 Mar 2022 13:26:00 +0100 Subject: [PATCH] Protocol mapper and client scope for 'acr' claim Closes #10161 --- .../migration/MigrationModelManager.java | 4 +- .../keycloak/migration/MigrationProvider.java | 8 ++ .../migration/migrators/MigrateTo18_0_0.java | 64 +++++++++ .../oidc/OIDCLoginProtocolFactory.java | 34 +++++ .../keycloak/protocol/oidc/TokenManager.java | 39 ++---- .../oidc/mappers/AcrProtocolMapper.java | 132 ++++++++++++++++++ .../mappers/OIDCAttributeMapperHelper.java | 4 +- .../migration/DefaultMigrationProvider.java | 5 + .../org.keycloak.protocol.ProtocolMapper | 1 + .../exportimport/ExportImportUtil.java | 4 +- .../forms/LevelOfAssuranceFlowTest.java | 26 +++- .../migration/AbstractMigrationTest.java | 9 ++ .../JsonFileImport198MigrationTest.java | 1 + .../JsonFileImport255MigrationTest.java | 1 + .../JsonFileImport343MigrationTest.java | 1 + .../JsonFileImport483MigrationTest.java | 1 + .../JsonFileImport903MigrationTest.java | 1 + .../testsuite/migration/MigrationTest.java | 5 + .../oidc/OIDCWellKnownProviderTest.java | 2 +- 19 files changed, 302 insertions(+), 40 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo18_0_0.java create mode 100644 services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java index c0a8e30fe7..eb9eedb8a4 100755 --- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java +++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java @@ -24,6 +24,7 @@ import org.jboss.logging.Logger; import org.keycloak.common.Version; import org.keycloak.migration.migrators.MigrateTo12_0_0; import org.keycloak.migration.migrators.MigrateTo14_0_0; +import org.keycloak.migration.migrators.MigrateTo18_0_0; import org.keycloak.migration.migrators.MigrateTo1_2_0; import org.keycloak.migration.migrators.MigrateTo1_3_0; import org.keycloak.migration.migrators.MigrateTo1_4_0; @@ -96,7 +97,8 @@ public class MigrationModelManager { new MigrateTo9_0_0(), new MigrateTo9_0_4(), new MigrateTo12_0_0(), - new MigrateTo14_0_0() + new MigrateTo14_0_0(), + new MigrateTo18_0_0() }; public static void migrate(KeycloakSession session) { diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java index 98f8840738..b86704865c 100755 --- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java @@ -69,4 +69,12 @@ public interface MigrationProvider extends Provider { * @return a reference to the {@code microprofile-jwt} client scope that was either created or already exists in the realm. */ ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm); + + /** + * Add 'acr' client scope or return it if already exists + * + * @param realm + * @return created or already existing client scope 'acr' + */ + void addOIDCAcrClientScope(RealmModel realm); } diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo18_0_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo18_0_0.java new file mode 100644 index 0000000000..5f73dfdfa2 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo18_0_0.java @@ -0,0 +1,64 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.migration.migrators; + +import org.jboss.logging.Logger; +import org.keycloak.common.Profile; +import org.keycloak.migration.MigrationProvider; +import org.keycloak.migration.ModelVersion; +import org.keycloak.models.ClientScopeModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.RealmRepresentation; + +/** + * @author Marek Posolda + */ +public class MigrateTo18_0_0 implements Migration { + + public static final ModelVersion VERSION = new ModelVersion("18.0.0"); + + private static final Logger LOG = Logger.getLogger(MigrateTo18_0_0.class); + + @Override + public ModelVersion getVersion() { + return VERSION; + } + + @Override + public void migrate(KeycloakSession session) { + if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { + session.realms().getRealmsStream().forEach(realm -> migrateRealm(session, realm)); + } + } + + @Override + public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) { + migrateRealm(session, realm); + } + + protected void migrateRealm(KeycloakSession session, RealmModel realm) { + if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { + MigrationProvider migrationProvider = session.getProvider(MigrationProvider.class); + + // create 'acr' default client scope in the realm. + migrationProvider.addOIDCAcrClientScope(realm); + } + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java index dfaaa97617..e5b89db6aa 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -18,6 +18,7 @@ package org.keycloak.protocol.oidc; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; +import org.keycloak.common.Profile; import org.keycloak.common.constants.KerberosConstants; import org.keycloak.common.util.UriUtils; import org.keycloak.events.EventBuilder; @@ -32,6 +33,7 @@ import org.keycloak.models.utils.DefaultClientScopes; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.AbstractLoginProtocolFactory; import org.keycloak.protocol.LoginProtocol; +import org.keycloak.protocol.oidc.mappers.AcrProtocolMapper; import org.keycloak.protocol.oidc.mappers.AddressMapper; import org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper; import org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper; @@ -83,6 +85,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { public static final String CLIENT_ROLES = "client roles"; public static final String AUDIENCE_RESOLVE = "audience resolve"; public static final String ALLOWED_WEB_ORIGINS = "allowed web origins"; + public static final String ACR = "acr loa level"; // microprofile-jwt claims public static final String UPN = "upn"; public static final String GROUPS = "groups"; @@ -90,6 +93,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { public static final String ROLES_SCOPE = "roles"; public static final String WEB_ORIGINS_SCOPE = "web-origins"; public static final String MICROPROFILE_JWT_SCOPE = "microprofile-jwt"; + public static final String ACR_SCOPE = "acr"; public static final String PROFILE_SCOPE_CONSENT_TEXT = "${profileScopeConsentText}"; public static final String EMAIL_SCOPE_CONSENT_TEXT = "${emailScopeConsentText}"; @@ -191,6 +195,11 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { model = UserRealmRoleMappingMapper.create(null, GROUPS, GROUPS, true, true, true); builtins.put(GROUPS, model); + + if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { + model = AcrProtocolMapper.create(ACR, true, true); + builtins.put(ACR, model); + } } private static void createUserAttributeMapper(String name, String attrName, String claimName, String type) { @@ -268,6 +277,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { addRolesClientScope(newRealm); addWebOriginsClientScope(newRealm); addMicroprofileJWTClientScope(newRealm); + addAcrClientScope(newRealm); } @@ -339,6 +349,30 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { return microprofileScope; } + + public static void addAcrClientScope(RealmModel newRealm) { + if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { + ClientScopeModel acrScope = KeycloakModelUtils.getClientScopeByName(newRealm, ACR_SCOPE); + if (acrScope == null) { + acrScope = newRealm.addClientScope(ACR_SCOPE); + acrScope.setDescription("OpenID Connect scope for add acr (authentication context class reference) to the token"); + acrScope.setDisplayOnConsentScreen(false); + acrScope.setIncludeInTokenScope(false); + acrScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + acrScope.addProtocolMapper(builtins.get(ACR)); + + // acr will be realm 'default' client scope + newRealm.addDefaultClientScope(acrScope, true); + + logger.debugf("Client scope '%s' created in the realm '%s'.", ACR_SCOPE, newRealm.getName()); + } else { + logger.debugf("Client scope '%s' already exists in realm '%s'. Skip creating it.", ACR_SCOPE, newRealm.getName()); + } + } else { + logger.debugf("Skip creating client scope '%s' in the realm '%s' due the step-up authentication feature is disabled.", ACR_SCOPE, newRealm.getName()); + } + } + @Override protected void addDefaults(ClientModel client) { } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 7e458626a0..9a0fe0cf22 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -884,10 +884,9 @@ public class TokenManager { token.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class)); token.setScope(clientSessionCtx.getScopeString()); - if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { - token.setAcr(getAcr(clientSession)); - } else { - // Backwards compatibility behaviour prior step-up authentication was introduced + // Backwards compatibility behaviour prior step-up authentication was introduced + // Protocol mapper is supposed to set this in case "step_up_authentication" feature enabled + if (!Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { String acr = AuthenticationManager.isSSOAuthentication(clientSession) ? "0" : "1"; token.setAcr(acr); } @@ -907,31 +906,6 @@ public class TokenManager { return token; } - private String getAcr(AuthenticatedClientSessionModel clientSession) { - int loa = LoAUtil.getCurrentLevelOfAuthentication(clientSession); - if (loa < Constants.MINIMUM_LOA) { - loa = AuthenticationManager.isSSOAuthentication(clientSession) ? 0 : 1; - } - - Map acrLoaMap = AcrUtils.getAcrLoaMap(clientSession.getClient()); - String acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, AcrUtils.getRequiredAcrValues( - clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM))); - if (acr == null) { - acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, AcrUtils.getAcrValues( - clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM), - clientSession.getNote(OIDCLoginProtocol.ACR_PARAM), clientSession.getClient())); - if (acr == null) { - acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, acrLoaMap.keySet()); - if (acr == null) { - acr = String.valueOf(loa); - } - } - } - - logger.tracef("Level sent in the token to client %s: %s. Original loa from the authentication: %d", clientSession.getClient().getClientId(), acr, loa); - return acr; - } - private int getTokenExpiration(RealmModel realm, ClientModel client, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offlineTokenRequested) { boolean implicitFlow = false; @@ -1191,7 +1165,12 @@ public class TokenManager { idToken.setAuthTime(accessToken.getAuthTime()); idToken.setSessionState(accessToken.getSessionState()); idToken.expiration(accessToken.getExpiration()); - idToken.setAcr(accessToken.getAcr()); + + // Protocol mapper is supposed to set this in case "step_up_authentication" feature enabled + if (!Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { + idToken.setAcr(accessToken.getAcr()); + } + if (isIdTokenAsDetachedSignature == false) { transformIDToken(session, idToken, userSession, clientSessionCtx); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java new file mode 100644 index 0000000000..3da2a5823d --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java @@ -0,0 +1,132 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.protocol.oidc.mappers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jboss.logging.Logger; +import org.keycloak.authentication.authenticators.util.LoAUtil; +import org.keycloak.common.Profile; +import org.keycloak.models.AuthenticatedClientSessionModel; +import org.keycloak.models.ClientSessionContext; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.utils.AcrUtils; +import org.keycloak.provider.EnvironmentDependentProviderFactory; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.IDToken; +import org.keycloak.services.managers.AuthenticationManager; + +/** + * @author Marek Posolda + */ +public class AcrProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, EnvironmentDependentProviderFactory { + + private static final Logger logger = Logger.getLogger(AcrProtocolMapper.class); + + private static final List configProperties = new ArrayList<>(); + + static { + OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AcrProtocolMapper.class); + } + + public static final String PROVIDER_ID = "oidc-acr-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "Authentication Context Class Reference (ACR)"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Maps the achieved LoA (Level of Authentication) to the 'acr' claim of the token"; + } + + @Override + protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, + ClientSessionContext clientSessionCtx) { + AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession(); + String acr = getAcr(clientSession); + token.setAcr(acr); + } + + public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken) { + ProtocolMapperModel mapper = new ProtocolMapperModel(); + mapper.setName(name); + mapper.setProtocolMapper(PROVIDER_ID); + mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + Map config = new HashMap<>(); + if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + mapper.setConfig(config); + return mapper; + } + + protected String getAcr(AuthenticatedClientSessionModel clientSession) { + int loa = LoAUtil.getCurrentLevelOfAuthentication(clientSession); + logger.tracef("Loa level when authenticated to client %s: %d", clientSession.getClient().getClientId(), loa); + if (loa < Constants.MINIMUM_LOA) { + loa = AuthenticationManager.isSSOAuthentication(clientSession) ? 0 : 1; + } + + Map acrLoaMap = AcrUtils.getAcrLoaMap(clientSession.getClient()); + String acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, AcrUtils.getRequiredAcrValues( + clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM))); + if (acr == null) { + acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, AcrUtils.getAcrValues( + clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM), + clientSession.getNote(OIDCLoginProtocol.ACR_PARAM), clientSession.getClient())); + if (acr == null) { + acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, acrLoaMap.keySet()); + if (acr == null) { + acr = String.valueOf(loa); + } + } + } + + logger.tracef("Level sent in the token to client %s: %s. Original loa from the authentication: %d", clientSession.getClient().getClientId(), acr, loa); + return acr; + } + + @Override + public boolean isSupported() { + return Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION); + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java index ab1c271f1e..666b3ec8a8 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java @@ -89,6 +89,9 @@ public class OIDCAttributeMapperHelper { tmpToken.put("azp", (claim, mapperName, token, value) -> { token.issuedFor(value.toString()); }); + tmpToken.put(IDToken.ACR, (claim, mapperName, token, value) -> { + token.setAcr(value.toString()); + }); tmpToken.put("aud", (claim, mapperName, token, value) -> { if (value instanceof Collection) { String[] audiences = ((Collection) value).stream().map(Object::toString).toArray(String[]::new); @@ -108,7 +111,6 @@ public class OIDCAttributeMapperHelper { tmpToken.put("iss", notAllowedInToken); tmpToken.put("scope", notAllowedInToken); tmpToken.put(IDToken.NONCE, notAllowedInToken); - tmpToken.put(IDToken.ACR, notAllowedInToken); tmpToken.put(IDToken.AUTH_TIME, notAllowedInToken); tmpToken.put(IDToken.SESSION_STATE, notAllowedInToken); tokenPropertySetters = Collections.unmodifiableMap(tmpToken); diff --git a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java index 619fbdecf7..f16afcf4af 100755 --- a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java +++ b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java @@ -101,6 +101,11 @@ public class DefaultMigrationProvider implements MigrationProvider { return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm); } + @Override + public void addOIDCAcrClientScope(RealmModel realm) { + OIDCLoginProtocolFactory.addAcrClientScope(realm); + } + @Override public void close() { } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index f92209ad4f..81b35c8b75 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -27,6 +27,7 @@ org.keycloak.protocol.oidc.mappers.GroupMembershipMapper org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper +org.keycloak.protocol.oidc.mappers.AcrProtocolMapper org.keycloak.protocol.saml.mappers.RoleListMapper org.keycloak.protocol.saml.mappers.RoleNameMapper org.keycloak.protocol.saml.mappers.HardcodedRole diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java index be93373290..da609d03a4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java @@ -720,6 +720,7 @@ public class ExportImportUtil { OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE, + OIDCLoginProtocolFactory.ACR_SCOPE, SamlProtocolFactory.SCOPE_ROLE_LIST )); @@ -740,7 +741,8 @@ public class ExportImportUtil { OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OIDCLoginProtocolFactory.ROLES_SCOPE, - OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE + OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, + OIDCLoginProtocolFactory.ACR_SCOPE )); Set optionalClientScopes = realm.getDefaultOptionalClientScopes() 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 6239a1c08e..c52029b680 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 @@ -31,8 +31,8 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory; import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory; import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticator; @@ -44,9 +44,9 @@ import org.keycloak.models.Constants; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; import org.keycloak.representations.ClaimsRepresentation; import org.keycloak.representations.IDToken; -import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -54,25 +54,22 @@ import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; -import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory; import org.keycloak.testsuite.client.KeycloakTestingClient; -import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginTotpPage; import org.keycloak.testsuite.pages.PushTheButtonPage; import org.keycloak.testsuite.util.FlowUtil; import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmRepUtil; import org.keycloak.testsuite.util.UserBuilder; -import org.keycloak.testsuite.util.WaitUtils; import org.keycloak.util.JsonSerialization; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE; /** @@ -643,6 +640,23 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest { testingClient.server(TEST_REALM_NAME).run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow("browser - Level of Authentication FLow")); } + @Test + @DisableFeature(value = Profile.Feature.STEP_UP_AUTHENTICATION, skipRestart = true) + public void testDisableStepupFeatureInNewRealm() { + RealmRepresentation rep = new RealmRepresentation(); + rep.setRealm("new-realm"); + adminClient.realms().create(rep); + RealmResource newRealm = adminClient.realms().realm("new-realm"); + try { + // Test client scope was not created in the new realm when feature is disabled + boolean acrScopeExists = newRealm.clientScopes().findAll().stream() + .anyMatch(clientScope -> OIDCLoginProtocolFactory.ACR_SCOPE.equals(clientScope.getName())); + Assert.assertThat(false, is(acrScopeExists)); + } finally { + newRealm.remove(); + } + } + public void openLoginFormWithAcrClaim(boolean essential, String... acrValues) { openLoginFormWithAcrClaim(oauth, essential, acrValues); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java index f0f0887ffb..f8d8ff9b0a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java @@ -310,6 +310,11 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { testSamlAttributes(migrationRealm); } + protected void testMigrationTo18_0_0() { + // check that all expected scopes exist in the migrated realm. + testRealmDefaultClientScopes(migrationRealm); + } + protected void testDeleteAccount(RealmResource realm) { ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0); ClientResource accountResource = realm.clients().get(accountClient.getId()); @@ -931,6 +936,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { testMigrationTo14_0_0(); } + protected void testMigrationTo18_x() { + testMigrationTo18_0_0(); + } + protected void testMigrationTo7_x(boolean supportedAuthzServices) { if (supportedAuthzServices) { testDecisionStrategySetOnResourceServer(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java index 72835dcab8..2145a7b7bb 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java @@ -69,6 +69,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(false); + testMigrationTo18_x(); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java index 1303e6e3f0..e7e729f95a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java @@ -71,6 +71,7 @@ public class JsonFileImport255MigrationTest extends AbstractJsonFileImportMigrat testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(false); + testMigrationTo18_x(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java index 83d29c4ad3..d1ce95b118 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java @@ -66,6 +66,7 @@ public class JsonFileImport343MigrationTest extends AbstractJsonFileImportMigrat testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(true); + testMigrationTo18_x(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java index f2f7f5e358..00b5411d74 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java @@ -59,6 +59,7 @@ public class JsonFileImport483MigrationTest extends AbstractJsonFileImportMigrat testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(true); + testMigrationTo18_x(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport903MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport903MigrationTest.java index 0fb0365c4f..c9b09c3145 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport903MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport903MigrationTest.java @@ -52,6 +52,7 @@ public class JsonFileImport903MigrationTest extends AbstractJsonFileImportMigrat public void migration9_0_3Test() throws Exception { checkRealmsImported(); testMigrationTo12_x(true); + testMigrationTo18_x(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java index 5db611b1f6..7d549d8a7f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java @@ -64,6 +64,7 @@ public class MigrationTest extends AbstractMigrationTest { public void migration9_xTest() throws Exception { testMigratedData(false); testMigrationTo12_x(true); + testMigrationTo18_x(); // Always test offline-token login during migration test testOfflineTokenLogin(); @@ -80,6 +81,7 @@ public class MigrationTest extends AbstractMigrationTest { testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(true); + testMigrationTo18_x(); // Always test offline-token login during migration test testOfflineTokenLogin(); @@ -97,6 +99,7 @@ public class MigrationTest extends AbstractMigrationTest { testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(true); + testMigrationTo18_x(); // Always test offline-token login during migration test testOfflineTokenLogin(); @@ -122,6 +125,7 @@ public class MigrationTest extends AbstractMigrationTest { testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(false); + testMigrationTo18_x(); // Always test offline-token login during migration test testOfflineTokenLogin(); @@ -140,6 +144,7 @@ public class MigrationTest extends AbstractMigrationTest { testMigrationTo8_x(); testMigrationTo9_x(); testMigrationTo12_x(false); + testMigrationTo18_x(); // Always test offline-token login during migration test testOfflineTokenLogin(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java index 4fb8bb32f5..68edb1aea3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java @@ -384,7 +384,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { private void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) { Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS, - OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, + OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE, OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE); }