Protocol mapper and client scope for 'acr' claim

Closes #10161
This commit is contained in:
mposolda 2022-03-03 13:26:00 +01:00 committed by Marek Posolda
parent 8ee7ae24de
commit 9e12587181
19 changed files with 302 additions and 40 deletions

View file

@ -24,6 +24,7 @@ import org.jboss.logging.Logger;
import org.keycloak.common.Version; import org.keycloak.common.Version;
import org.keycloak.migration.migrators.MigrateTo12_0_0; import org.keycloak.migration.migrators.MigrateTo12_0_0;
import org.keycloak.migration.migrators.MigrateTo14_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_2_0;
import org.keycloak.migration.migrators.MigrateTo1_3_0; import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0; import org.keycloak.migration.migrators.MigrateTo1_4_0;
@ -96,7 +97,8 @@ public class MigrationModelManager {
new MigrateTo9_0_0(), new MigrateTo9_0_0(),
new MigrateTo9_0_4(), new MigrateTo9_0_4(),
new MigrateTo12_0_0(), new MigrateTo12_0_0(),
new MigrateTo14_0_0() new MigrateTo14_0_0(),
new MigrateTo18_0_0()
}; };
public static void migrate(KeycloakSession session) { public static void migrate(KeycloakSession session) {

View file

@ -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. * @return a reference to the {@code microprofile-jwt} client scope that was either created or already exists in the realm.
*/ */
ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel 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);
} }

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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);
}
}
}

View file

@ -18,6 +18,7 @@ package org.keycloak.protocol.oidc;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.common.Profile;
import org.keycloak.common.constants.KerberosConstants; import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
@ -32,6 +33,7 @@ import org.keycloak.models.utils.DefaultClientScopes;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.AbstractLoginProtocolFactory; import org.keycloak.protocol.AbstractLoginProtocolFactory;
import org.keycloak.protocol.LoginProtocol; 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.AddressMapper;
import org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper; import org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper;
import org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper; 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 CLIENT_ROLES = "client roles";
public static final String AUDIENCE_RESOLVE = "audience resolve"; public static final String AUDIENCE_RESOLVE = "audience resolve";
public static final String ALLOWED_WEB_ORIGINS = "allowed web origins"; public static final String ALLOWED_WEB_ORIGINS = "allowed web origins";
public static final String ACR = "acr loa level";
// microprofile-jwt claims // microprofile-jwt claims
public static final String UPN = "upn"; public static final String UPN = "upn";
public static final String GROUPS = "groups"; 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 ROLES_SCOPE = "roles";
public static final String WEB_ORIGINS_SCOPE = "web-origins"; public static final String WEB_ORIGINS_SCOPE = "web-origins";
public static final String MICROPROFILE_JWT_SCOPE = "microprofile-jwt"; 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 PROFILE_SCOPE_CONSENT_TEXT = "${profileScopeConsentText}";
public static final String EMAIL_SCOPE_CONSENT_TEXT = "${emailScopeConsentText}"; 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); model = UserRealmRoleMappingMapper.create(null, GROUPS, GROUPS, true, true, true);
builtins.put(GROUPS, model); 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) { private static void createUserAttributeMapper(String name, String attrName, String claimName, String type) {
@ -268,6 +277,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
addRolesClientScope(newRealm); addRolesClientScope(newRealm);
addWebOriginsClientScope(newRealm); addWebOriginsClientScope(newRealm);
addMicroprofileJWTClientScope(newRealm); addMicroprofileJWTClientScope(newRealm);
addAcrClientScope(newRealm);
} }
@ -339,6 +349,30 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
return microprofileScope; 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 @Override
protected void addDefaults(ClientModel client) { protected void addDefaults(ClientModel client) {
} }

View file

@ -884,10 +884,9 @@ public class TokenManager {
token.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class)); token.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class));
token.setScope(clientSessionCtx.getScopeString()); token.setScope(clientSessionCtx.getScopeString());
if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { // Backwards compatibility behaviour prior step-up authentication was introduced
token.setAcr(getAcr(clientSession)); // Protocol mapper is supposed to set this in case "step_up_authentication" feature enabled
} else { if (!Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) {
// Backwards compatibility behaviour prior step-up authentication was introduced
String acr = AuthenticationManager.isSSOAuthentication(clientSession) ? "0" : "1"; String acr = AuthenticationManager.isSSOAuthentication(clientSession) ? "0" : "1";
token.setAcr(acr); token.setAcr(acr);
} }
@ -907,31 +906,6 @@ public class TokenManager {
return token; return token;
} }
private String getAcr(AuthenticatedClientSessionModel clientSession) {
int loa = LoAUtil.getCurrentLevelOfAuthentication(clientSession);
if (loa < Constants.MINIMUM_LOA) {
loa = AuthenticationManager.isSSOAuthentication(clientSession) ? 0 : 1;
}
Map<String, Integer> 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, private int getTokenExpiration(RealmModel realm, ClientModel client, UserSessionModel userSession,
AuthenticatedClientSessionModel clientSession, boolean offlineTokenRequested) { AuthenticatedClientSessionModel clientSession, boolean offlineTokenRequested) {
boolean implicitFlow = false; boolean implicitFlow = false;
@ -1191,7 +1165,12 @@ public class TokenManager {
idToken.setAuthTime(accessToken.getAuthTime()); idToken.setAuthTime(accessToken.getAuthTime());
idToken.setSessionState(accessToken.getSessionState()); idToken.setSessionState(accessToken.getSessionState());
idToken.expiration(accessToken.getExpiration()); 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) { if (isIdTokenAsDetachedSignature == false) {
transformIDToken(session, idToken, userSession, clientSessionCtx); transformIDToken(session, idToken, userSession, clientSessionCtx);
} }

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AcrProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, EnvironmentDependentProviderFactory {
private static final Logger logger = Logger.getLogger(AcrProtocolMapper.class);
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AcrProtocolMapper.class);
}
public static final String PROVIDER_ID = "oidc-acr-mapper";
public List<ProviderConfigProperty> 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<String, String> 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<String, Integer> 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);
}
}

View file

@ -89,6 +89,9 @@ public class OIDCAttributeMapperHelper {
tmpToken.put("azp", (claim, mapperName, token, value) -> { tmpToken.put("azp", (claim, mapperName, token, value) -> {
token.issuedFor(value.toString()); token.issuedFor(value.toString());
}); });
tmpToken.put(IDToken.ACR, (claim, mapperName, token, value) -> {
token.setAcr(value.toString());
});
tmpToken.put("aud", (claim, mapperName, token, value) -> { tmpToken.put("aud", (claim, mapperName, token, value) -> {
if (value instanceof Collection) { if (value instanceof Collection) {
String[] audiences = ((Collection<?>) value).stream().map(Object::toString).toArray(String[]::new); String[] audiences = ((Collection<?>) value).stream().map(Object::toString).toArray(String[]::new);
@ -108,7 +111,6 @@ public class OIDCAttributeMapperHelper {
tmpToken.put("iss", notAllowedInToken); tmpToken.put("iss", notAllowedInToken);
tmpToken.put("scope", notAllowedInToken); tmpToken.put("scope", notAllowedInToken);
tmpToken.put(IDToken.NONCE, notAllowedInToken); tmpToken.put(IDToken.NONCE, notAllowedInToken);
tmpToken.put(IDToken.ACR, notAllowedInToken);
tmpToken.put(IDToken.AUTH_TIME, notAllowedInToken); tmpToken.put(IDToken.AUTH_TIME, notAllowedInToken);
tmpToken.put(IDToken.SESSION_STATE, notAllowedInToken); tmpToken.put(IDToken.SESSION_STATE, notAllowedInToken);
tokenPropertySetters = Collections.unmodifiableMap(tmpToken); tokenPropertySetters = Collections.unmodifiableMap(tmpToken);

View file

@ -101,6 +101,11 @@ public class DefaultMigrationProvider implements MigrationProvider {
return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm); return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm);
} }
@Override
public void addOIDCAcrClientScope(RealmModel realm) {
OIDCLoginProtocolFactory.addAcrClientScope(realm);
}
@Override @Override
public void close() { public void close() {
} }

View file

@ -27,6 +27,7 @@ org.keycloak.protocol.oidc.mappers.GroupMembershipMapper
org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper
org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper
org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper
org.keycloak.protocol.oidc.mappers.AcrProtocolMapper
org.keycloak.protocol.saml.mappers.RoleListMapper org.keycloak.protocol.saml.mappers.RoleListMapper
org.keycloak.protocol.saml.mappers.RoleNameMapper org.keycloak.protocol.saml.mappers.RoleNameMapper
org.keycloak.protocol.saml.mappers.HardcodedRole org.keycloak.protocol.saml.mappers.HardcodedRole

View file

@ -720,6 +720,7 @@ public class ExportImportUtil {
OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.ROLES_SCOPE,
OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE,
OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE,
OIDCLoginProtocolFactory.ACR_SCOPE,
SamlProtocolFactory.SCOPE_ROLE_LIST SamlProtocolFactory.SCOPE_ROLE_LIST
)); ));
@ -740,7 +741,8 @@ public class ExportImportUtil {
OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_PROFILE,
OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_EMAIL,
OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.ROLES_SCOPE,
OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE,
OIDCLoginProtocolFactory.ACR_SCOPE
)); ));
Set<String> optionalClientScopes = realm.getDefaultOptionalClientScopes() Set<String> optionalClientScopes = realm.getDefaultOptionalClientScopes()

View file

@ -31,8 +31,8 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource; 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.OTPFormAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory; import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticator; 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.models.utils.TimeBasedOTP;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.representations.ClaimsRepresentation; import org.keycloak.representations.ClaimsRepresentation;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; 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.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil; 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.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory; import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.client.KeycloakTestingClient;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginTotpPage; import org.keycloak.testsuite.pages.LoginTotpPage;
import org.keycloak.testsuite.pages.PushTheButtonPage; import org.keycloak.testsuite.pages.PushTheButtonPage;
import org.keycloak.testsuite.util.FlowUtil; import org.keycloak.testsuite.util.FlowUtil;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RealmRepUtil; import org.keycloak.testsuite.util.RealmRepUtil;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.util.WaitUtils;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE; 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")); 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) { public void openLoginFormWithAcrClaim(boolean essential, String... acrValues) {
openLoginFormWithAcrClaim(oauth, essential, acrValues); openLoginFormWithAcrClaim(oauth, essential, acrValues);

View file

@ -310,6 +310,11 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testSamlAttributes(migrationRealm); testSamlAttributes(migrationRealm);
} }
protected void testMigrationTo18_0_0() {
// check that all expected scopes exist in the migrated realm.
testRealmDefaultClientScopes(migrationRealm);
}
protected void testDeleteAccount(RealmResource realm) { protected void testDeleteAccount(RealmResource realm) {
ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0); ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
ClientResource accountResource = realm.clients().get(accountClient.getId()); ClientResource accountResource = realm.clients().get(accountClient.getId());
@ -931,6 +936,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMigrationTo14_0_0(); testMigrationTo14_0_0();
} }
protected void testMigrationTo18_x() {
testMigrationTo18_0_0();
}
protected void testMigrationTo7_x(boolean supportedAuthzServices) { protected void testMigrationTo7_x(boolean supportedAuthzServices) {
if (supportedAuthzServices) { if (supportedAuthzServices) {
testDecisionStrategySetOnResourceServer(); testDecisionStrategySetOnResourceServer();

View file

@ -69,6 +69,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(false); testMigrationTo12_x(false);
testMigrationTo18_x();
} }
@Override @Override

View file

@ -71,6 +71,7 @@ public class JsonFileImport255MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(false); testMigrationTo12_x(false);
testMigrationTo18_x();
} }
} }

View file

@ -66,6 +66,7 @@ public class JsonFileImport343MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(true); testMigrationTo12_x(true);
testMigrationTo18_x();
} }
} }

View file

@ -59,6 +59,7 @@ public class JsonFileImport483MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(true); testMigrationTo12_x(true);
testMigrationTo18_x();
} }
} }

View file

@ -52,6 +52,7 @@ public class JsonFileImport903MigrationTest extends AbstractJsonFileImportMigrat
public void migration9_0_3Test() throws Exception { public void migration9_0_3Test() throws Exception {
checkRealmsImported(); checkRealmsImported();
testMigrationTo12_x(true); testMigrationTo12_x(true);
testMigrationTo18_x();
} }
} }

View file

@ -64,6 +64,7 @@ public class MigrationTest extends AbstractMigrationTest {
public void migration9_xTest() throws Exception { public void migration9_xTest() throws Exception {
testMigratedData(false); testMigratedData(false);
testMigrationTo12_x(true); testMigrationTo12_x(true);
testMigrationTo18_x();
// Always test offline-token login during migration test // Always test offline-token login during migration test
testOfflineTokenLogin(); testOfflineTokenLogin();
@ -80,6 +81,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(true); testMigrationTo12_x(true);
testMigrationTo18_x();
// Always test offline-token login during migration test // Always test offline-token login during migration test
testOfflineTokenLogin(); testOfflineTokenLogin();
@ -97,6 +99,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(true); testMigrationTo12_x(true);
testMigrationTo18_x();
// Always test offline-token login during migration test // Always test offline-token login during migration test
testOfflineTokenLogin(); testOfflineTokenLogin();
@ -122,6 +125,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(false); testMigrationTo12_x(false);
testMigrationTo18_x();
// Always test offline-token login during migration test // Always test offline-token login during migration test
testOfflineTokenLogin(); testOfflineTokenLogin();
@ -140,6 +144,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo8_x(); testMigrationTo8_x();
testMigrationTo9_x(); testMigrationTo9_x();
testMigrationTo12_x(false); testMigrationTo12_x(false);
testMigrationTo18_x();
// Always test offline-token login during migration test // Always test offline-token login during migration test
testOfflineTokenLogin(); testOfflineTokenLogin();

View file

@ -384,7 +384,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
private void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) { private void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) {
Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS, 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); OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE);
} }