parent
8ee7ae24de
commit
9e12587181
19 changed files with 302 additions and 40 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue