parent
b2e11735ed
commit
6f6b5e8e84
18 changed files with 500 additions and 69 deletions
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue