Fix authenticatorConfig for javascript providers

Closes #20005
This commit is contained in:
mposolda 2023-07-27 14:15:31 +02:00 committed by Marek Posolda
parent b2e11735ed
commit 6f6b5e8e84
18 changed files with 500 additions and 69 deletions

View file

@ -75,7 +75,7 @@ public class ExportUtils {
public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options, boolean internal) { public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options, boolean internal) {
RealmRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, internal); RealmRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, internal);
ModelToRepresentation.exportAuthenticationFlows(realm, rep); ModelToRepresentation.exportAuthenticationFlows(session, realm, rep);
ModelToRepresentation.exportRequiredActions(realm, rep); ModelToRepresentation.exportRequiredActions(realm, rep);
// Project/product version // Project/product version

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.deployment.DeployedConfigurationsManager;
import org.keycloak.exportimport.ExportAdapter; import org.keycloak.exportimport.ExportAdapter;
import org.keycloak.exportimport.ExportOptions; import org.keycloak.exportimport.ExportOptions;
import org.keycloak.exportimport.util.ExportUtils; import org.keycloak.exportimport.util.ExportUtils;
@ -307,7 +308,7 @@ public class LegacyExportImportManager implements ExportImportManager {
updateParSettings(rep, newRealm); updateParSettings(rep, newRealm);
Map<String, String> mappedFlows = importAuthenticationFlows(newRealm, rep); Map<String, String> mappedFlows = importAuthenticationFlows(session, newRealm, rep);
if (rep.getRequiredActions() != null) { if (rep.getRequiredActions() != null) {
for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
RequiredActionProviderModel model = toModel(action); RequiredActionProviderModel model = toModel(action);
@ -1257,7 +1258,7 @@ public class LegacyExportImportManager implements ExportImportManager {
return webAuthnPolicy; return webAuthnPolicy;
} }
public static Map<String, String> importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) { public static Map<String, String> importAuthenticationFlows(KeycloakSession session, RealmModel newRealm, RealmRepresentation rep) {
Map<String, String> mappedFlows = new HashMap<>(); Map<String, String> mappedFlows = new HashMap<>();
if (rep.getAuthenticationFlows() == null) { if (rep.getAuthenticationFlows() == null) {
// assume this is an old version being imported // assume this is an old version being imported
@ -1285,7 +1286,7 @@ public class LegacyExportImportManager implements ExportImportManager {
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias()); AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias());
for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) { for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) {
AuthenticationExecutionModel execution = toModel(newRealm, model, exeRep); AuthenticationExecutionModel execution = toModel(session, newRealm, model, exeRep);
newRealm.addAuthenticatorExecution(execution); newRealm.addAuthenticatorExecution(execution);
} }
} }
@ -1371,10 +1372,10 @@ public class LegacyExportImportManager implements ExportImportManager {
return mappedFlows; return mappedFlows;
} }
private static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) { private static AuthenticationExecutionModel toModel(KeycloakSession session, RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) {
AuthenticationExecutionModel model = new AuthenticationExecutionModel(); AuthenticationExecutionModel model = new AuthenticationExecutionModel();
if (rep.getAuthenticatorConfig() != null) { if (rep.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig()); AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfigByAlias(realm, rep.getAuthenticatorConfig());
model.setAuthenticatorConfig(config.getId()); model.setAuthenticatorConfig(config.getId());
} }
model.setAuthenticator(rep.getAuthenticator()); model.setAuthenticator(rep.getAuthenticator());

View file

@ -24,6 +24,7 @@ import org.keycloak.Config;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.deployment.DeployedConfigurationsManager;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientProvider; import org.keycloak.models.ClientProvider;
import org.keycloak.models.ClientScopeProvider; import org.keycloak.models.ClientScopeProvider;
@ -314,7 +315,7 @@ public class MapExportImportManager implements ExportImportManager {
updateParSettings(rep, newRealm); updateParSettings(rep, newRealm);
Map<String, String> mappedFlows = importAuthenticationFlows(newRealm, rep); Map<String, String> mappedFlows = importAuthenticationFlows(session, newRealm, rep);
if (rep.getRequiredActions() != null) { if (rep.getRequiredActions() != null) {
for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
RequiredActionProviderModel model = toModel(action); RequiredActionProviderModel model = toModel(action);
@ -1461,7 +1462,7 @@ public class MapExportImportManager implements ExportImportManager {
return webAuthnPolicy; return webAuthnPolicy;
} }
public static Map<String, String> importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) { public static Map<String, String> importAuthenticationFlows(KeycloakSession session, RealmModel newRealm, RealmRepresentation rep) {
Map<String, String> mappedFlows = new HashMap<>(); Map<String, String> mappedFlows = new HashMap<>();
if (rep.getAuthenticationFlows() == null) { if (rep.getAuthenticationFlows() == null) {
// assume this is an old version being imported // assume this is an old version being imported
@ -1489,7 +1490,7 @@ public class MapExportImportManager implements ExportImportManager {
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias()); AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias());
for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) { for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) {
AuthenticationExecutionModel execution = toModel(newRealm, model, exeRep); AuthenticationExecutionModel execution = toModel(session, newRealm, model, exeRep);
newRealm.addAuthenticatorExecution(execution); newRealm.addAuthenticatorExecution(execution);
} }
} }
@ -1575,10 +1576,10 @@ public class MapExportImportManager implements ExportImportManager {
return mappedFlows; return mappedFlows;
} }
private static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) { private static AuthenticationExecutionModel toModel(KeycloakSession session, RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) {
AuthenticationExecutionModel model = new AuthenticationExecutionModel(); AuthenticationExecutionModel model = new AuthenticationExecutionModel();
if (rep.getAuthenticatorConfig() != null) { if (rep.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig()); AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfigByAlias(realm, rep.getAuthenticatorConfig());
model.setAuthenticatorConfig(config.getId()); model.setAuthenticatorConfig(config.getId());
} }
model.setAuthenticator(rep.getAuthenticator()); model.setAuthenticator(rep.getAuthenticator());

View file

@ -0,0 +1,78 @@
/*
* Copyright 2023 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.deployment;
import org.jboss.logging.Logger;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
/**
* Allows to CRUD for configurations (like Authenticator configs). Those are typically saved in the store (realm), but can be also
* deployed and hence not saved in the DB
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DeployedConfigurationsManager {
private static final Logger log = Logger.getLogger(DeployedConfigurationsManager.class);
private final KeycloakSession session;
public DeployedConfigurationsManager(KeycloakSession session) {
this.session = session;
}
public void registerDeployedAuthenticatorConfig(AuthenticatorConfigModel model) {
log.debugf("Register deployed authenticator config: %s", model.getId());
session.getProvider(DeployedConfigurationsProvider.class).registerDeployedAuthenticatorConfig(model);
}
public AuthenticatorConfigModel getDeployedAuthenticatorConfig(String configId) {
return session.getProvider(DeployedConfigurationsProvider.class).getDeployedAuthenticatorConfigs()
.filter(config -> configId.equals(config.getId()))
.findFirst().orElse(null);
}
public AuthenticatorConfigModel getAuthenticatorConfig(RealmModel realm, String configId) {
AuthenticatorConfigModel cfgModel = getDeployedAuthenticatorConfig(configId);
if (cfgModel != null) {
log.tracef("Found deployed configuration by id: %s", configId);
return cfgModel;
} else {
return realm.getAuthenticatorConfigById(configId);
}
}
public AuthenticatorConfigModel getAuthenticatorConfigByAlias(RealmModel realm, String alias) {
if (alias == null) return null;
AuthenticatorConfigModel cfgModel = session.getProvider(DeployedConfigurationsProvider.class).getDeployedAuthenticatorConfigs()
.filter(config -> alias.equals(config.getAlias()))
.findFirst().orElse(null);
if (cfgModel != null) {
log.debugf("Found deployed configuration by alias: %s", alias);
return cfgModel;
} else {
return realm.getAuthenticatorConfigByAlias(alias);
}
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2023 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.deployment;
import java.util.stream.Stream;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.provider.Provider;
/**
* Allows to register "deployed configurations", which are retrieved in runtime from deployed providers and hence are not saved in the DB
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface DeployedConfigurationsProvider extends Provider {
void registerDeployedAuthenticatorConfig(AuthenticatorConfigModel model);
Stream<AuthenticatorConfigModel> getDeployedAuthenticatorConfigs();
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2023 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.deployment;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface DeployedConfigurationsProviderFactory extends ProviderFactory<DeployedConfigurationsProvider> {
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2023 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.deployment;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DeployedConfigurationsSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "deployed-configurations";
}
@Override
public Class<? extends Provider> getProviderClass() {
return DeployedConfigurationsProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return DeployedConfigurationsProviderFactory.class;
}
}

View file

@ -35,6 +35,7 @@ import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialMetadata; import org.keycloak.credential.CredentialMetadata;
import org.keycloak.credential.CredentialModel; import org.keycloak.credential.CredentialModel;
import org.keycloak.deployment.DeployedConfigurationsManager;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.AuthDetails; import org.keycloak.events.admin.AuthDetails;
@ -547,7 +548,7 @@ public class ModelToRepresentation {
rep.setSupportedLocales(realm.getSupportedLocalesStream().collect(Collectors.toSet())); rep.setSupportedLocales(realm.getSupportedLocalesStream().collect(Collectors.toSet()));
rep.setDefaultLocale(realm.getDefaultLocale()); rep.setDefaultLocale(realm.getDefaultLocale());
if (internal) { if (internal) {
exportAuthenticationFlows(realm, rep); exportAuthenticationFlows(session, realm, rep);
exportRequiredActions(realm, rep); exportRequiredActions(realm, rep);
exportGroups(realm, rep); exportGroups(realm, rep);
} }
@ -586,10 +587,10 @@ public class ModelToRepresentation {
rep.setGroups(toGroupHierarchy(realm, true).collect(Collectors.toList())); rep.setGroups(toGroupHierarchy(realm, true).collect(Collectors.toList()));
} }
public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) { public static void exportAuthenticationFlows(KeycloakSession session, RealmModel realm, RealmRepresentation rep) {
List<AuthenticationFlowRepresentation> authenticationFlows = realm.getAuthenticationFlowsStream() List<AuthenticationFlowRepresentation> authenticationFlows = realm.getAuthenticationFlowsStream()
.sorted(AuthenticationFlowModel.AuthenticationFlowComparator.SINGLETON) .sorted(AuthenticationFlowModel.AuthenticationFlowComparator.SINGLETON)
.map(flow -> toRepresentation(realm, flow)) .map(flow -> toRepresentation(session, realm, flow))
.collect(Collectors.toList()); .collect(Collectors.toList());
rep.setAuthenticationFlows(authenticationFlows); rep.setAuthenticationFlows(authenticationFlows);
@ -882,7 +883,7 @@ public class ModelToRepresentation {
return consentRep; return consentRep;
} }
public static AuthenticationFlowRepresentation toRepresentation(RealmModel realm, AuthenticationFlowModel model) { public static AuthenticationFlowRepresentation toRepresentation(KeycloakSession session, RealmModel realm, AuthenticationFlowModel model) {
AuthenticationFlowRepresentation rep = new AuthenticationFlowRepresentation(); AuthenticationFlowRepresentation rep = new AuthenticationFlowRepresentation();
rep.setId(model.getId()); rep.setId(model.getId());
rep.setBuiltIn(model.isBuiltIn()); rep.setBuiltIn(model.isBuiltIn());
@ -891,14 +892,14 @@ public class ModelToRepresentation {
rep.setAlias(model.getAlias()); rep.setAlias(model.getAlias());
rep.setDescription(model.getDescription()); rep.setDescription(model.getDescription());
rep.setAuthenticationExecutions(realm.getAuthenticationExecutionsStream(model.getId()) rep.setAuthenticationExecutions(realm.getAuthenticationExecutionsStream(model.getId())
.map(e -> toRepresentation(realm, e)).collect(Collectors.toList())); .map(e -> toRepresentation(session, realm, e)).collect(Collectors.toList()));
return rep; return rep;
} }
public static AuthenticationExecutionExportRepresentation toRepresentation(RealmModel realm, AuthenticationExecutionModel model) { public static AuthenticationExecutionExportRepresentation toRepresentation(KeycloakSession session, RealmModel realm, AuthenticationExecutionModel model) {
AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation(); AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation();
if (model.getAuthenticatorConfig() != null) { if (model.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(model.getAuthenticatorConfig()); AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfig(realm, model.getAuthenticatorConfig());
rep.setAuthenticatorConfig(config.getAlias()); rep.setAuthenticatorConfig(config.getAlias());
} }
rep.setAuthenticator(model.getAuthenticator()); rep.setAuthenticator(model.getAuthenticator());

View file

@ -57,6 +57,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel; import org.keycloak.credential.CredentialModel;
import org.keycloak.deployment.DeployedConfigurationsManager;
import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.migration.migrators.MigrationUtils;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticationFlowModel;
@ -926,7 +927,7 @@ public class RepresentationToModel {
} }
public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionRepresentation rep) { public static AuthenticationExecutionModel toModel(KeycloakSession session, RealmModel realm, AuthenticationExecutionRepresentation rep) {
AuthenticationExecutionModel model = new AuthenticationExecutionModel(); AuthenticationExecutionModel model = new AuthenticationExecutionModel();
model.setId(rep.getId()); model.setId(rep.getId());
model.setFlowId(rep.getFlowId()); model.setFlowId(rep.getFlowId());
@ -938,7 +939,7 @@ public class RepresentationToModel {
model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement())); model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
if (rep.getAuthenticatorConfig() != null) { if (rep.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel cfg = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig()); AuthenticatorConfigModel cfg = new DeployedConfigurationsManager(session).getAuthenticatorConfigByAlias(realm, rep.getAuthenticatorConfig());
model.setAuthenticatorConfig(cfg.getId()); model.setAuthenticatorConfig(cfg.getId());
} }
return model; return model;

View file

@ -62,6 +62,7 @@ org.keycloak.authentication.otp.OTPApplicationSpi
org.keycloak.authorization.policy.provider.PolicySpi org.keycloak.authorization.policy.provider.PolicySpi
org.keycloak.authorization.store.StoreFactorySpi org.keycloak.authorization.store.StoreFactorySpi
org.keycloak.authorization.AuthorizationSpi org.keycloak.authorization.AuthorizationSpi
org.keycloak.deployment.DeployedConfigurationsSpi
org.keycloak.models.cache.authorization.CachedStoreFactorySpi org.keycloak.models.cache.authorization.CachedStoreFactorySpi
org.keycloak.protocol.oidc.TokenExchangeSpi org.keycloak.protocol.oidc.TokenExchangeSpi
org.keycloak.protocol.oidc.TokenIntrospectionSpi org.keycloak.protocol.oidc.TokenIntrospectionSpi

View file

@ -24,9 +24,14 @@ import org.keycloak.Config;
import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.Authenticator;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.deployment.DeployedConfigurationsManager;
import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.representations.provider.ScriptProviderMetadata; import org.keycloak.representations.provider.ScriptProviderMetadata;
/** /**
@ -88,6 +93,13 @@ public final class DeployedScriptAuthenticatorFactory extends ScriptBasedAuthent
configProperties = super.getConfigProperties(); configProperties = super.getConfigProperties();
} }
@Override
public void postInit(KeycloakSessionFactory factory) {
KeycloakModelUtils.runJobInTransaction(factory, session -> {
new DeployedConfigurationsManager(session).registerDeployedAuthenticatorConfig(model);
});
}
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return configProperties; return configProperties;

View file

@ -0,0 +1,51 @@
/*
* Copyright 2023 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.deployment;
import java.util.Map;
import java.util.stream.Stream;
import org.keycloak.models.AuthenticatorConfigModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DefaultDeployedConfigurationsProvider implements DeployedConfigurationsProvider {
private final Map<String, AuthenticatorConfigModel> deployedAuthenticatorConfigs;
public DefaultDeployedConfigurationsProvider(Map<String, AuthenticatorConfigModel> deployedAuthenticatorConfigs) {
this.deployedAuthenticatorConfigs = deployedAuthenticatorConfigs;
}
@Override
public void registerDeployedAuthenticatorConfig(AuthenticatorConfigModel model) {
deployedAuthenticatorConfigs.put(model.getId(), model);
}
@Override
public Stream<AuthenticatorConfigModel> getDeployedAuthenticatorConfigs() {
return deployedAuthenticatorConfigs.values().stream();
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright 2023 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.deployment;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.keycloak.Config;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DefaultDeployedConfigurationsProviderFactory implements DeployedConfigurationsProviderFactory {
public static final String PROVIDER_ID = "default";
private final Map<String, AuthenticatorConfigModel> deployedAuthenticatorConfigs = new ConcurrentHashMap<>();
@Override
public DeployedConfigurationsProvider create(KeycloakSession session) {
return new DefaultDeployedConfigurationsProvider(deployedAuthenticatorConfigs);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -33,11 +33,13 @@ import org.keycloak.authentication.FormAction;
import org.keycloak.authentication.FormAuthenticator; import org.keycloak.authentication.FormAuthenticator;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.deployment.DeployedConfigurationsManager;
import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType; import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredActionProviderModel;
@ -205,7 +207,7 @@ public class AuthenticationManagementResource {
return realm.getAuthenticationFlowsStream() return realm.getAuthenticationFlowsStream()
.filter(flow -> flow.isTopLevel() && !Objects.equals(flow.getAlias(), DefaultAuthenticationFlows.SAML_ECP_FLOW)) .filter(flow -> flow.isTopLevel() && !Objects.equals(flow.getAlias(), DefaultAuthenticationFlows.SAML_ECP_FLOW))
.map(flow -> ModelToRepresentation.toRepresentation(realm, flow)); .map(flow -> ModelToRepresentation.toRepresentation(session, realm, flow));
} }
/** /**
@ -264,7 +266,7 @@ public class AuthenticationManagementResource {
if (flow == null) { if (flow == null) {
throw new NotFoundException("Could not find flow with id"); throw new NotFoundException("Could not find flow with id");
} }
return ModelToRepresentation.toRepresentation(realm, flow); return ModelToRepresentation.toRepresentation(session, realm, flow);
} }
/** /**
@ -375,7 +377,7 @@ public class AuthenticationManagementResource {
logger.debug("flow not found: " + flowAlias); logger.debug("flow not found: " + flowAlias);
return Response.status(NOT_FOUND).build(); return Response.status(NOT_FOUND).build();
} }
AuthenticationFlowModel copy = copyFlow(realm, flow, newName); AuthenticationFlowModel copy = copyFlow(session, realm, flow, newName);
data.put("id", copy.getId()); data.put("id", copy.getId());
adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri()).representation(data).success(); adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri()).representation(data).success();
@ -384,7 +386,7 @@ public class AuthenticationManagementResource {
} }
public static AuthenticationFlowModel copyFlow(RealmModel realm, AuthenticationFlowModel flow, String newName) { public static AuthenticationFlowModel copyFlow(KeycloakSession session, RealmModel realm, AuthenticationFlowModel flow, String newName) {
AuthenticationFlowModel copy = new AuthenticationFlowModel(); AuthenticationFlowModel copy = new AuthenticationFlowModel();
copy.setAlias(newName); copy.setAlias(newName);
copy.setDescription(flow.getDescription()); copy.setDescription(flow.getDescription());
@ -392,11 +394,11 @@ public class AuthenticationManagementResource {
copy.setBuiltIn(false); copy.setBuiltIn(false);
copy.setTopLevel(flow.isTopLevel()); copy.setTopLevel(flow.isTopLevel());
copy = realm.addAuthenticationFlow(copy); copy = realm.addAuthenticationFlow(copy);
copy(realm, newName, flow, copy); copy(session, realm, newName, flow, copy);
return copy; return copy;
} }
public static void copy(RealmModel realm, String newName, AuthenticationFlowModel from, AuthenticationFlowModel to) { public static void copy(KeycloakSession session, RealmModel realm, String newName, AuthenticationFlowModel from, AuthenticationFlowModel to) {
realm.getAuthenticationExecutionsStream(from.getId()).forEachOrdered(execution -> { realm.getAuthenticationExecutionsStream(from.getId()).forEachOrdered(execution -> {
if (execution.isAuthenticatorFlow()) { if (execution.isAuthenticatorFlow()) {
AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId()); AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
@ -408,26 +410,32 @@ public class AuthenticationManagementResource {
copy.setTopLevel(false); copy.setTopLevel(false);
copy = realm.addAuthenticationFlow(copy); copy = realm.addAuthenticationFlow(copy);
execution.setFlowId(copy.getId()); execution.setFlowId(copy.getId());
copy(realm, newName, subFlow, copy); copy(session, realm, newName, subFlow, copy);
} }
if (execution.getAuthenticatorConfig() != null) { if (execution.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(execution.getAuthenticatorConfig()); DeployedConfigurationsManager configManager = new DeployedConfigurationsManager(session);
AuthenticatorConfigModel config = configManager.getAuthenticatorConfig(realm, execution.getAuthenticatorConfig());
if (config == null) { if (config == null) {
logger.debugf("Authentication execution with id [%s] not found", config.getId()); logger.debugf("Authentication execution with id [%s] not found", config.getId());
throw new IllegalStateException("Authentication execution configuration not found"); throw new IllegalStateException("Authentication execution configuration not found");
} }
config.setId(null); if (configManager.getDeployedAuthenticatorConfig(execution.getAuthenticatorConfig()) != null) {
// Shared configuration of deployed provider
execution.setAuthenticatorConfig(config.getId());
} else {
config.setId(null);
if (config.getAlias() != null) { if (config.getAlias() != null) {
config.setAlias(newName + " " + config.getAlias()); config.setAlias(newName + " " + config.getAlias());
}
AuthenticatorConfigModel newConfig = realm.addAuthenticatorConfig(config);
execution.setAuthenticatorConfig(newConfig.getId());
} }
AuthenticatorConfigModel newConfig = realm.addAuthenticatorConfig(config);
execution.setAuthenticatorConfig(newConfig.getId());
} }
execution.setId(null); execution.setId(null);
@ -524,17 +532,7 @@ public class AuthenticationManagementResource {
String provider = data.get("provider"); String provider = data.get("provider");
// make sure provider is one of the registered providers // make sure provider is one of the registered providers
ProviderFactory f; ProviderFactory f = getProviderFactory( parentFlow, provider);
if (parentFlow.getProviderId().equals(AuthenticationFlow.CLIENT_FLOW)) {
f = session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, provider);
} else if (parentFlow.getProviderId().equals(AuthenticationFlow.FORM_FLOW)) {
f = session.getKeycloakSessionFactory().getProviderFactory(FormAction.class, provider);
} else {
f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider);
}
if (f == null) {
throw new BadRequestException("No authentication provider found for id: " + provider);
}
AuthenticationExecutionModel execution = new AuthenticationExecutionModel(); AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(parentFlow.getId()); execution.setParentFlow(parentFlow.getId());
@ -551,18 +549,7 @@ public class AuthenticationManagementResource {
execution = realm.addAuthenticatorExecution(execution); execution = realm.addAuthenticatorExecution(execution);
if (f instanceof ConfiguredProvider) { checkConfigForDeployedProvider(f, execution);
ConfiguredProvider internalProviderFactory = (ConfiguredProvider) f;
AuthenticatorConfigModel config = internalProviderFactory.getConfig();
if (config != null) {
// creates a default configuration if the factory defines one
// useful for internal providers that already provide a built-in configuration
AuthenticatorConfigRepresentation configRepresentation = ModelToRepresentation.toRepresentation(
config);
newExecutionConfig(execution.getId(), configRepresentation).close();
}
}
data.put("id", execution.getId()); data.put("id", execution.getId());
adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri()).representation(data).success(); adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri()).representation(data).success();
@ -571,6 +558,38 @@ public class AuthenticationManagementResource {
return Response.created(session.getContext().getUri().getBaseUriBuilder().path(session.getContext().getUri().getPath().replace(addExecutionPathSegment, "")).path("executions").path(execution.getId()).build()).build(); return Response.created(session.getContext().getUri().getBaseUriBuilder().path(session.getContext().getUri().getPath().replace(addExecutionPathSegment, "")).path("executions").path(execution.getId()).build()).build();
} }
private ProviderFactory getProviderFactory(AuthenticationFlowModel parentFlow, String provider) {
ProviderFactory f = null;
if (parentFlow.getProviderId().equals(AuthenticationFlow.CLIENT_FLOW)) {
f = session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, provider);
} else if (parentFlow.getProviderId().equals(AuthenticationFlow.FORM_FLOW)) {
f = session.getKeycloakSessionFactory().getProviderFactory(FormAction.class, provider);
} else {
f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider);
}
if (f == null) {
throw new BadRequestException("No authentication provider found for id: " + provider);
}
return f;
}
private void checkConfigForDeployedProvider(ProviderFactory f, AuthenticationExecutionModel execution) {
if (f instanceof ConfiguredProvider) {
ConfiguredProvider internalProviderFactory = (ConfiguredProvider) f;
AuthenticatorConfigModel config = internalProviderFactory.getConfig();
if (config != null) {
// use a default configuration if the factory defines one
// Assumption is that this is registered in DeployedConfigurationsProvider
// useful for internal providers that already provide a built-in configuration
logger.tracef("Updating execution of provider '%s' with shared configuration.", execution.getAuthenticator());
execution.setAuthenticatorConfig(config.getId());
realm.updateAuthenticatorExecution(execution);
}
}
}
/** /**
* Get authentication executions for a flow * Get authentication executions for a flow
* *
@ -649,7 +668,7 @@ public class AuthenticationManagementResource {
if (factory.isConfigurable()) { if (factory.isConfigurable()) {
String authenticatorConfigId = execution.getAuthenticatorConfig(); String authenticatorConfigId = execution.getAuthenticatorConfig();
if(authenticatorConfigId != null) { if(authenticatorConfigId != null) {
AuthenticatorConfigModel authenticatorConfig = realm.getAuthenticatorConfigById(authenticatorConfigId); AuthenticatorConfigModel authenticatorConfig = new DeployedConfigurationsManager(session).getAuthenticatorConfig(realm, authenticatorConfigId);
if (authenticatorConfig != null) { if (authenticatorConfig != null) {
rep.setAlias(authenticatorConfig.getAlias()); rep.setAlias(authenticatorConfig.getAlias());
@ -779,7 +798,7 @@ public class AuthenticationManagementResource {
public Response addExecution(@Parameter(description = "JSON model describing authentication execution") AuthenticationExecutionRepresentation execution) { public Response addExecution(@Parameter(description = "JSON model describing authentication execution") AuthenticationExecutionRepresentation execution) {
auth.realm().requireManageRealm(); auth.realm().requireManageRealm();
AuthenticationExecutionModel model = RepresentationToModel.toModel(realm, execution); AuthenticationExecutionModel model = RepresentationToModel.toModel(session, realm, execution);
AuthenticationFlowModel parentFlow = getParentFlow(model); AuthenticationFlowModel parentFlow = getParentFlow(model);
if (parentFlow.isBuiltIn()) { if (parentFlow.isBuiltIn()) {
throw new BadRequestException("It is illegal to add execution to a built in flow"); throw new BadRequestException("It is illegal to add execution to a built in flow");
@ -787,6 +806,11 @@ public class AuthenticationManagementResource {
model.setPriority(getNextPriority(parentFlow)); model.setPriority(getNextPriority(parentFlow));
model = realm.addAuthenticatorExecution(model); model = realm.addAuthenticatorExecution(model);
if (!execution.isAuthenticatorFlow()) {
ProviderFactory f = getProviderFactory(parentFlow, execution.getAuthenticator());
checkConfigForDeployedProvider(f, model);
}
adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri(), model.getId()).representation(execution).success(); adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri(), model.getId()).representation(execution).success();
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(model.getId()).build()).build(); return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(model.getId()).build()).build();
} }
@ -976,7 +1000,7 @@ public class AuthenticationManagementResource {
public AuthenticatorConfigRepresentation getAuthenticatorConfig(@Parameter(description = "Execution id") @PathParam("executionId") String execution, @Parameter(description = "Configuration id") @PathParam("id") String id) { public AuthenticatorConfigRepresentation getAuthenticatorConfig(@Parameter(description = "Execution id") @PathParam("executionId") String execution, @Parameter(description = "Configuration id") @PathParam("id") String id) {
auth.realm().requireViewRealm(); auth.realm().requireViewRealm();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfig(realm, id);
if (config == null) { if (config == null) {
throw new NotFoundException("Could not find authenticator config"); throw new NotFoundException("Could not find authenticator config");
@ -1317,7 +1341,7 @@ public class AuthenticationManagementResource {
public AuthenticatorConfigRepresentation getAuthenticatorConfig(@Parameter(description = "Configuration id") @PathParam("id") String id) { public AuthenticatorConfigRepresentation getAuthenticatorConfig(@Parameter(description = "Configuration id") @PathParam("id") String id) {
auth.realm().requireViewRealm(); auth.realm().requireViewRealm();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfig(realm, id);
if (config == null) { if (config == null) {
throw new NotFoundException("Could not find authenticator config"); throw new NotFoundException("Could not find authenticator config");
@ -1369,6 +1393,10 @@ public class AuthenticationManagementResource {
auth.realm().requireManageRealm(); auth.realm().requireManageRealm();
ReservedCharValidator.validate(rep.getAlias()); ReservedCharValidator.validate(rep.getAlias());
if (new DeployedConfigurationsManager(session).getDeployedAuthenticatorConfig(id) != null) {
throw new BadRequestException("Authenticator config is read-only");
}
AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id); AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id);
if (exists == null) { if (exists == null) {
throw new NotFoundException("Could not find authenticator config"); throw new NotFoundException("Could not find authenticator config");

View file

@ -0,0 +1,20 @@
#
# Copyright 2023 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.
#
#
org.keycloak.deployment.DefaultDeployedConfigurationsProviderFactory

View file

@ -674,7 +674,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
RealmModel realm = getRealmByName(realmName); RealmModel realm = getRealmByName(realmName);
AuthenticationFlowModel flow = realm.getClientAuthenticationFlow(); AuthenticationFlowModel flow = realm.getClientAuthenticationFlow();
if (flow == null) return null; if (flow == null) return null;
return ModelToRepresentation.toRepresentation(realm, flow); return ModelToRepresentation.toRepresentation(session, realm, flow);
} }
@GET @GET
@ -684,7 +684,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
RealmModel realm = getRealmByName(realmName); RealmModel realm = getRealmByName(realmName);
AuthenticationFlowModel flow = realm.getResetCredentialsFlow(); AuthenticationFlowModel flow = realm.getResetCredentialsFlow();
if (flow == null) return null; if (flow == null) return null;
return ModelToRepresentation.toRepresentation(realm, flow); return ModelToRepresentation.toRepresentation(session, realm, flow);
} }
@GET @GET

View file

@ -21,7 +21,12 @@ import static org.keycloak.common.Profile.Feature.SCRIPTS;
import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT; import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.Deployment;
@ -34,18 +39,23 @@ import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory; import org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory; import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.provider.ScriptProviderDescriptor; import org.keycloak.representations.provider.ScriptProviderDescriptor;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.AbstractFlowTest; import org.keycloak.testsuite.forms.AbstractFlowTest;
@ -180,6 +190,53 @@ public class DeployedScriptAuthenticatorTest extends AbstractFlowTest {
events.expectLogin().user(okayUser()).detail(Details.USERNAME, "user").assertEvent(); events.expectLogin().user(okayUser()).detail(Details.USERNAME, "user").assertEvent();
} }
// Issue 20005
@Test
public void testManyScriptAuthenticatorInstances() throws Exception {
configureFlows();
AuthenticationManagementResource authMgmtResource = adminClient.realm(TEST_REALM_NAME).flows();
// Endpoint used by admin console
Map<String, String> scriptExecution = new HashMap<>();
scriptExecution.put("provider", "script-authenticator-a.js");
// It should be possible to add another script-authenticator to the flow
authMgmtResource.addExecution("scriptBrowser", scriptExecution);
List<AuthenticationExecutionInfoRepresentation> executions = authMgmtResource.getExecutions("scriptBrowser");
List<AuthenticationExecutionInfoRepresentation> scriptExecutions = executions.stream()
.filter(execution -> execution.getDisplayName().equals("My Authenticator"))
.collect(Collectors.toList());
// Both executions refers to same config of deployed script provider
Assert.assertEquals(2, scriptExecutions.size());
for (AuthenticationExecutionInfoRepresentation execution : scriptExecutions) {
Assert.assertEquals(execution.getAuthenticationConfig(), "script-authenticator-a.js");
}
// Assert updating config should fail due it's read-only
AuthenticatorConfigRepresentation configRep = authMgmtResource.getAuthenticatorConfig("script-authenticator-a.js");
configRep.getConfig().put("scriptCode", "Something");
try {
authMgmtResource.updateAuthenticatorConfig("script-authenticator-a.js", configRep);
Assert.fail("Update of configuration should have failed");
} catch (BadRequestException bre) {
// Expected
}
// Test copy flow is OK
Map<String, String> newFlow = new HashMap<>();
newFlow.put("newName", "Copy of script flow");
Response resp = authMgmtResource.copy("scriptBrowser", newFlow);
Assert.assertEquals(201, resp.getStatus());
resp.close();
AuthenticationFlowRepresentation copiedFlow = AbstractAuthenticationTest.findFlowByAlias("Copy of script flow", authMgmtResource.getFlows());
// Cleanup
authMgmtResource.deleteFlow(copiedFlow.getId());
authMgmtResource.removeExecution(scriptExecutions.get(1).getId());
}
private UserRepresentation okayUser() { private UserRepresentation okayUser() {
return adminClient.realm(TEST_REALM_NAME).users().search("user", true).get(0); return adminClient.realm(TEST_REALM_NAME).users().search("user", true).get(0);
} }

View file

@ -27,7 +27,8 @@ import static org.keycloak.models.utils.DefaultAuthenticationFlows.REGISTRATION_
import static org.keycloak.models.utils.DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW; import static org.keycloak.models.utils.DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW;
public class FlowUtil { public class FlowUtil {
private RealmModel realm; private final KeycloakSession session;
private final RealmModel realm;
private AuthenticationFlowModel currentFlow; private AuthenticationFlowModel currentFlow;
private String flowAlias; private String flowAlias;
private int maxPriority = 0; private int maxPriority = 0;
@ -42,7 +43,8 @@ public class FlowUtil {
} }
} }
public FlowUtil(RealmModel realm) { private FlowUtil(KeycloakSession session, RealmModel realm) {
this.session = session;
this.realm = realm; this.realm = realm;
} }
@ -55,11 +57,11 @@ public class FlowUtil {
} }
public static FlowUtil inCurrentRealm(KeycloakSession session) { public static FlowUtil inCurrentRealm(KeycloakSession session) {
return new FlowUtil(session.getContext().getRealm()); return new FlowUtil(session, session.getContext().getRealm());
} }
private FlowUtil newFlowUtil(AuthenticationFlowModel flowModel) { private FlowUtil newFlowUtil(AuthenticationFlowModel flowModel) {
FlowUtil subflow = new FlowUtil(realm); FlowUtil subflow = new FlowUtil(session, realm);
subflow.currentFlow = flowModel; subflow.currentFlow = flowModel;
return subflow; return subflow;
} }
@ -112,7 +114,7 @@ public class FlowUtil {
realm.removeAuthenticationFlow(foundFlow); realm.removeAuthenticationFlow(foundFlow);
} }
currentFlow = AuthenticationManagementResource.copyFlow(realm, existingBrowserFlow, newFlowAlias); currentFlow = AuthenticationManagementResource.copyFlow(session, realm, existingBrowserFlow, newFlowAlias);
return this; return this;
} }