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);
}