KEYCLOAK-8504 Ensure the authenticationFlowBindingOverrides client configuration references a valid authentication flow id when a realm is imported

This commit is contained in:
Stefan Guilhen 2018-11-14 22:50:30 -02:00 committed by Marek Posolda
parent 607bb1b995
commit 311e848460
3 changed files with 393 additions and 7 deletions

View file

@ -258,7 +258,7 @@ public class RepresentationToModel {
if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep)); if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY); else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
importAuthenticationFlows(newRealm, rep); Map<String, String> mappedFlows = importAuthenticationFlows(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);
@ -300,7 +300,7 @@ public class RepresentationToModel {
} }
if (rep.getClients() != null) { if (rep.getClients() != null) {
createClients(session, rep, newRealm); createClients(session, rep, newRealm, mappedFlows);
} }
importRoles(rep.getRoles(), newRealm); importRoles(rep.getRoles(), newRealm);
@ -584,7 +584,8 @@ public class RepresentationToModel {
} }
} }
public static void importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) { public static Map<String, String> importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) {
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
DefaultAuthenticationFlows.migrateFlows(newRealm); DefaultAuthenticationFlows.migrateFlows(newRealm);
@ -596,8 +597,11 @@ public class RepresentationToModel {
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = toModel(flowRep); AuthenticationFlowModel model = toModel(flowRep);
// make sure new id is generated for new AuthenticationFlowModel instance // make sure new id is generated for new AuthenticationFlowModel instance
String previousId = model.getId();
model.setId(null); model.setId(null);
model = newRealm.addAuthenticationFlow(model); model = newRealm.addAuthenticationFlow(model);
// 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()) { for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias()); AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias());
@ -675,6 +679,8 @@ public class RepresentationToModel {
} }
DefaultAuthenticationFlows.addIdentityProviderAuthenticator(newRealm, defaultProvider); DefaultAuthenticationFlows.addIdentityProviderAuthenticator(newRealm, defaultProvider);
return mappedFlows;
} }
private static void convertDeprecatedSocialProviders(RealmRepresentation rep) { private static void convertDeprecatedSocialProviders(RealmRepresentation rep) {
@ -1073,10 +1079,10 @@ public class RepresentationToModel {
// CLIENTS // CLIENTS
private static Map<String, ClientModel> createClients(KeycloakSession session, RealmRepresentation rep, RealmModel realm) { private static Map<String, ClientModel> createClients(KeycloakSession session, RealmRepresentation rep, RealmModel realm, Map<String, String> mappedFlows) {
Map<String, ClientModel> appMap = new HashMap<String, ClientModel>(); Map<String, ClientModel> appMap = new HashMap<String, ClientModel>();
for (ClientRepresentation resourceRep : rep.getClients()) { for (ClientRepresentation resourceRep : rep.getClients()) {
ClientModel app = createClient(session, realm, resourceRep, false); ClientModel app = createClient(session, realm, resourceRep, false, mappedFlows);
appMap.put(app.getClientId(), app); appMap.put(app.getClientId(), app);
} }
return appMap; return appMap;
@ -1090,6 +1096,10 @@ public class RepresentationToModel {
* @return * @return
*/ */
public static ClientModel createClient(KeycloakSession session, RealmModel realm, ClientRepresentation resourceRep, boolean addDefaultRoles) { public static ClientModel createClient(KeycloakSession session, RealmModel realm, ClientRepresentation resourceRep, boolean addDefaultRoles) {
return createClient(session, realm, resourceRep, addDefaultRoles, null);
}
private static ClientModel createClient(KeycloakSession session, RealmModel realm, ClientRepresentation resourceRep, boolean addDefaultRoles, Map<String, String> mappedFlows) {
logger.debugv("Create client: {0}", resourceRep.getClientId()); logger.debugv("Create client: {0}", resourceRep.getClientId());
ClientModel client = resourceRep.getId() != null ? realm.addClient(resourceRep.getId(), resourceRep.getClientId()) : realm.addClient(resourceRep.getClientId()); ClientModel client = resourceRep.getId() != null ? realm.addClient(resourceRep.getId(), resourceRep.getClientId()) : realm.addClient(resourceRep.getClientId());
@ -1164,10 +1174,14 @@ public class RepresentationToModel {
continue; continue;
} else { } else {
String flowId = entry.getValue(); String flowId = entry.getValue();
// check if flow id was mapped when the flows were imported
if (mappedFlows != null && mappedFlows.containsKey(flowId)) {
flowId = mappedFlows.get(flowId);
}
if (client.getRealm().getAuthenticationFlowById(flowId) == null) { if (client.getRealm().getAuthenticationFlowById(flowId) == null) {
throw new RuntimeException("Unable to resolve auth flow binding override for: " + entry.getKey()); throw new RuntimeException("Unable to resolve auth flow binding override for: " + entry.getKey());
} }
client.setAuthenticationFlowBindingOverride(entry.getKey(), entry.getValue()); client.setAuthenticationFlowBindingOverride(entry.getKey(), flowId);
} }
} }
} }

View file

@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientScopeResource; import org.keycloak.admin.client.resource.ClientScopeResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.authentication.AuthenticationFlow;
import org.keycloak.common.constants.KerberosConstants; import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
@ -137,6 +138,16 @@ public class ExportImportUtil {
Assert.assertEquals("client-secret", application.getClientAuthenticatorType()); Assert.assertEquals("client-secret", application.getClientAuthenticatorType());
Assert.assertEquals("client-jwt", otherApp.getClientAuthenticatorType()); Assert.assertEquals("client-jwt", otherApp.getClientAuthenticatorType());
// test authenticationFlowBindingOverrides
Map<String, String> flowMap = otherApp.getAuthenticationFlowBindingOverrides();
Assert.assertNotNull(flowMap);
Assert.assertEquals(1, flowMap.size());
Assert.assertTrue(flowMap.containsKey("browser"));
// if the authentication flows were correctly imported there must be a flow whose id matches the one in the authenticationFlowBindingOverrides
AuthenticationFlowRepresentation flowRep = realmRsc.flows().getFlow(flowMap.get("browser"));
Assert.assertNotNull(flowRep);
Assert.assertEquals("browser", flowRep.getAlias());
// Test finding applications by ID // Test finding applications by ID
Assert.assertNull(ApiUtil.findClientResourceById(realmRsc, "982734")); Assert.assertNull(ApiUtil.findClientResourceById(realmRsc, "982734"));
Assert.assertEquals(application.getId(), ApiUtil.findClientResourceById(realmRsc, application.getId()).toRepresentation().getId()); Assert.assertEquals(application.getId(), ApiUtil.findClientResourceById(realmRsc, application.getId()).toRepresentation().getId());

View file

@ -240,6 +240,9 @@
"directAccessGrantsEnabled": false, "directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true, "serviceAccountsEnabled": true,
"clientAuthenticatorType": "client-jwt", "clientAuthenticatorType": "client-jwt",
"authenticationFlowBindingOverrides": {
"browser": "73dcb1e4-2c7c-4494-825d-f2677cbc114c"
},
"protocolMappers" : [ "protocolMappers" : [
{ {
"name" : "gss delegation credential", "name" : "gss delegation credential",
@ -535,5 +538,363 @@
} }
] ]
},
"authenticationFlows": [
{
"id": "aed29d4f-aba7-4992-a600-18c0a28c1fc3",
"alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "idp-confirm-link",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "idp-email-verification",
"requirement": "ALTERNATIVE",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"requirement": "ALTERNATIVE",
"priority": 30,
"flowAlias": "Verify Existing Account by Re-authentication",
"userSetupAllowed": false,
"autheticatorFlow": true
} }
]
},
{
"id": "d8b8f564-6d56-4171-ba36-a8922c6eae49",
"alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "idp-username-password-form",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "auth-otp-form",
"requirement": "OPTIONAL",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
},
{
"id": "73dcb1e4-2c7c-4494-825d-f2677cbc114c",
"alias": "browser",
"description": "browser based authentication",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "auth-cookie",
"requirement": "ALTERNATIVE",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "auth-spnego",
"requirement": "DISABLED",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "identity-provider-redirector",
"requirement": "ALTERNATIVE",
"priority": 25,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"requirement": "ALTERNATIVE",
"priority": 30,
"flowAlias": "forms",
"userSetupAllowed": false,
"autheticatorFlow": true
}
]
},
{
"id": "a0a80dc3-d473-468e-b6e8-f1d306c21360",
"alias": "clients",
"description": "Base authentication for clients",
"providerId": "client-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "client-secret",
"requirement": "ALTERNATIVE",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "client-jwt",
"requirement": "ALTERNATIVE",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "client-secret-jwt",
"requirement": "ALTERNATIVE",
"priority": 30,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
},
{
"id": "91882f46-54be-4738-847a-32e849d53240",
"alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "direct-grant-validate-username",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "direct-grant-validate-password",
"requirement": "REQUIRED",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "direct-grant-validate-otp",
"requirement": "OPTIONAL",
"priority": 30,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
},
{
"id": "b727a208-587c-4f27-8f48-ba2a0d4effdd",
"alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "docker-http-basic-authenticator",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
},
{
"id": "5a6ac775-4000-4ccf-9271-6cb599297d4b",
"alias": "first broker login",
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticatorConfig": "review profile config",
"authenticator": "idp-review-profile",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticatorConfig": "create unique user config",
"authenticator": "idp-create-user-if-unique",
"requirement": "ALTERNATIVE",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"requirement": "ALTERNATIVE",
"priority": 30,
"flowAlias": "Handle Existing Account",
"userSetupAllowed": false,
"autheticatorFlow": true
}
]
},
{
"id": "1a84808d-e0c7-4759-aee8-cf9229542429",
"alias": "forms",
"description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "auth-username-password-form",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "auth-otp-form",
"requirement": "OPTIONAL",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
},
{
"id": "717f990a-1c46-464c-9051-5e0ae39d63db",
"alias": "registration",
"description": "registration flow",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "registration-page-form",
"requirement": "REQUIRED",
"priority": 10,
"flowAlias": "registration form",
"userSetupAllowed": false,
"autheticatorFlow": true
}
]
},
{
"id": "166fca50-7b69-4cd4-80eb-a569e87ff8a2",
"alias": "registration form",
"description": "registration form",
"providerId": "form-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "registration-user-creation",
"requirement": "REQUIRED",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "registration-profile-action",
"requirement": "REQUIRED",
"priority": 40,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "registration-password-action",
"requirement": "REQUIRED",
"priority": 50,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "registration-recaptcha-action",
"requirement": "DISABLED",
"priority": 60,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
},
{
"id": "a516cb39-8f6d-4d08-ac82-236377be6500",
"alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "reset-credentials-choose-user",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "reset-credential-email",
"requirement": "REQUIRED",
"priority": 20,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "reset-password",
"requirement": "REQUIRED",
"priority": 30,
"userSetupAllowed": false,
"autheticatorFlow": false
},
{
"authenticator": "reset-otp",
"requirement": "OPTIONAL",
"priority": 40,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
},
{
"id": "8b9ae730-11e0-451f-b693-e32f09415e42",
"alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "http-basic-authenticator",
"requirement": "REQUIRED",
"priority": 10,
"userSetupAllowed": false,
"autheticatorFlow": false
}
]
}
],
"authenticatorConfig": [
{
"id": "a6d38dcd-7b53-4991-b4eb-c866ce3c5e70",
"alias": "create unique user config",
"config": {
"require.password.update.after.registration": "false"
}
},
{
"id": "7408f503-b929-422f-b52b-277cebda44ba",
"alias": "review profile config",
"config": {
"update.profile.on.first.login": "missing"
}
}
]
} }