fix: accounting for the possibility of null flows from existing realms

closes: #23980

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steve Hawkins 2024-03-06 12:47:19 -05:00 committed by Marek Posolda
parent 7695532011
commit 4091baf4c2
5 changed files with 64 additions and 35 deletions

View file

@ -1288,38 +1288,35 @@ public class DefaultExportImportManager implements ExportImportManager {
} }
public static Map<String, String> importAuthenticationFlows(KeycloakSession session, 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) {
// assume this is an old version being imported if (rep.getAuthenticatorConfig() != null) {
DefaultAuthenticationFlows.migrateFlows(newRealm); for (AuthenticatorConfigRepresentation configRep : rep.getAuthenticatorConfig()) {
} else { if (configRep.getAlias() == null) {
if (rep.getAuthenticatorConfig() != null) { // this can happen only during import json files from keycloak 3.4.0 and older
for (AuthenticatorConfigRepresentation configRep : rep.getAuthenticatorConfig()) { throw new IllegalStateException("Provided realm contains authenticator config with null alias. "
if (configRep.getAlias() == null) { + "It should be resolved by adding alias to the authenticator config before exporting the realm.");
// this can happen only during import json files from keycloak 3.4.0 and older
throw new IllegalStateException("Provided realm contains authenticator config with null alias. "
+ "It should be resolved by adding alias to the authenticator config before exporting the realm.");
}
AuthenticatorConfigModel model = RepresentationToModel.toModel(configRep);
newRealm.addAuthenticatorConfig(model);
} }
AuthenticatorConfigModel model = RepresentationToModel.toModel(configRep);
newRealm.addAuthenticatorConfig(model);
} }
if (rep.getAuthenticationFlows() != null) { }
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { if (rep.getAuthenticationFlows() != null) {
AuthenticationFlowModel model = RepresentationToModel.toModel(flowRep); for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
String previousId = model.getId(); AuthenticationFlowModel model = RepresentationToModel.toModel(flowRep);
model = newRealm.addAuthenticationFlow(model); String previousId = model.getId();
// store the mapped ids so that clients can reference the correct flow when importing the authenticationFlowBindingOverrides model = newRealm.addAuthenticationFlow(model);
mappedFlows.put(previousId, model.getId()); // store the mapped ids so that clients can reference the correct flow when importing the authenticationFlowBindingOverrides
} mappedFlows.put(previousId, model.getId());
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { }
AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias()); for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) { AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias());
AuthenticationExecutionModel execution = toModel(session, newRealm, model, exeRep); for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) {
newRealm.addAuthenticatorExecution(execution); AuthenticationExecutionModel execution = toModel(session, newRealm, model, exeRep);
} newRealm.addAuthenticatorExecution(execution);
} }
} }
} }
DefaultAuthenticationFlows.migrateFlows(newRealm);
if (rep.getBrowserFlow() == null) { if (rep.getBrowserFlow() == null) {
AuthenticationFlowModel defaultFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); AuthenticationFlowModel defaultFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
if (defaultFlow != null) { if (defaultFlow != null) {

View file

@ -43,7 +43,7 @@ public class AuthenticationMapper {
final List<String> useAsDefault = Stream.of(realm.getBrowserFlow(), realm.getRegistrationFlow(), realm.getDirectGrantFlow(), final List<String> useAsDefault = Stream.of(realm.getBrowserFlow(), realm.getRegistrationFlow(), realm.getDirectGrantFlow(),
realm.getResetCredentialsFlow(), realm.getClientAuthenticationFlow(), realm.getDockerAuthenticationFlow(), realm.getFirstBrokerLoginFlow()) realm.getResetCredentialsFlow(), realm.getClientAuthenticationFlow(), realm.getDockerAuthenticationFlow(), realm.getFirstBrokerLoginFlow())
.filter(f -> flow.getAlias().equals(f.getAlias())).map(AuthenticationFlowModel::getAlias).collect(Collectors.toList()); .filter(f -> f != null && flow.getAlias().equals(f.getAlias())).map(AuthenticationFlowModel::getAlias).collect(Collectors.toList());
if (!useAsDefault.isEmpty()) { if (!useAsDefault.isEmpty()) {
authentication.setUsedBy(new UsedBy(UsedBy.UsedByType.DEFAULT, useAsDefault)); authentication.setUsedBy(new UsedBy(UsedBy.UsedByType.DEFAULT, useAsDefault));

View file

@ -605,13 +605,15 @@ public class DefaultAuthenticationFlows {
if (browserFlow == null) { if (browserFlow == null) {
browserFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); browserFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
} }
List<AuthenticationExecutionModel> browserExecutions = new LinkedList<>(); if (browserFlow != null) {
KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions); List<AuthenticationExecutionModel> browserExecutions = new LinkedList<>();
for (AuthenticationExecutionModel browserExecution : browserExecutions) { KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions);
if (browserExecution.isAuthenticatorFlow()){ for (AuthenticationExecutionModel browserExecution : browserExecutions) {
if (realm.getAuthenticationExecutionsStream(browserExecution.getFlowId()) if (browserExecution.isAuthenticatorFlow()){
.anyMatch(e -> e.getAuthenticator().equals("auth-otp-form"))){ if (realm.getAuthenticationExecutionsStream(browserExecution.getFlowId())
execution.setRequirement(browserExecution.getRequirement()); .anyMatch(e -> e.getAuthenticator().equals("auth-otp-form"))){
execution.setRequirement(browserExecution.getRequirement());
}
} }
} }
} }

View file

@ -257,6 +257,13 @@ public class ExportImportTest extends AbstractKeycloakTest {
addTestRealmToTestRealmReps("import-without-clients"); addTestRealmToTestRealmReps("import-without-clients");
} }
@Test
public void testImportFromRealmWithPartialAuthenticationFlows() {
// import a realm with no built-in authentication flows
importRealmFromFile("/import/partial-authentication-flows-import.json");
Assert.assertTrue("Imported realm hasn't been found!", isRealmPresent("partial-authentication-flows-import"));
}
@Test @Test
public void testImportWithNullAuthenticatorConfigAndNoDefaultBrowserFlow() { public void testImportWithNullAuthenticatorConfigAndNoDefaultBrowserFlow() {
importRealmFromFile("/import/testrealm-authenticator-config-null.json"); importRealmFromFile("/import/testrealm-authenticator-config-null.json");

View file

@ -0,0 +1,23 @@
{
"enabled": true,
"realm": "partial-authentication-flows-import",
"authenticationFlows": [
{
"alias": "X.509 browser",
"description": "Browser-based authentication",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": false,
"authenticationExecutions": [
{
"requirement": "ALTERNATIVE",
"priority": 10,
"authenticator": "auth-cookie",
"authenticatorFlow": false,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
}
]
}