KEYCLOAK-8504 Ensure the authenticationFlowBindingOverrides client configuration references a valid authentication flow id when a realm is imported
This commit is contained in:
parent
607bb1b995
commit
311e848460
3 changed files with 393 additions and 7 deletions
|
@ -258,7 +258,7 @@ public class RepresentationToModel {
|
|||
if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
|
||||
else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
|
||||
|
||||
importAuthenticationFlows(newRealm, rep);
|
||||
Map<String, String> mappedFlows = importAuthenticationFlows(newRealm, rep);
|
||||
if (rep.getRequiredActions() != null) {
|
||||
for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
|
||||
RequiredActionProviderModel model = toModel(action);
|
||||
|
@ -300,7 +300,7 @@ public class RepresentationToModel {
|
|||
}
|
||||
|
||||
if (rep.getClients() != null) {
|
||||
createClients(session, rep, newRealm);
|
||||
createClients(session, rep, newRealm, mappedFlows);
|
||||
}
|
||||
|
||||
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) {
|
||||
// assume this is an old version being imported
|
||||
DefaultAuthenticationFlows.migrateFlows(newRealm);
|
||||
|
@ -596,8 +597,11 @@ public class RepresentationToModel {
|
|||
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
|
||||
AuthenticationFlowModel model = toModel(flowRep);
|
||||
// make sure new id is generated for new AuthenticationFlowModel instance
|
||||
String previousId = model.getId();
|
||||
model.setId(null);
|
||||
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()) {
|
||||
AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias());
|
||||
|
@ -675,6 +679,8 @@ public class RepresentationToModel {
|
|||
}
|
||||
|
||||
DefaultAuthenticationFlows.addIdentityProviderAuthenticator(newRealm, defaultProvider);
|
||||
|
||||
return mappedFlows;
|
||||
}
|
||||
|
||||
private static void convertDeprecatedSocialProviders(RealmRepresentation rep) {
|
||||
|
@ -1073,10 +1079,10 @@ public class RepresentationToModel {
|
|||
|
||||
// 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>();
|
||||
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);
|
||||
}
|
||||
return appMap;
|
||||
|
@ -1090,6 +1096,10 @@ public class RepresentationToModel {
|
|||
* @return
|
||||
*/
|
||||
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());
|
||||
|
||||
ClientModel client = resourceRep.getId() != null ? realm.addClient(resourceRep.getId(), resourceRep.getClientId()) : realm.addClient(resourceRep.getClientId());
|
||||
|
@ -1164,10 +1174,14 @@ public class RepresentationToModel {
|
|||
continue;
|
||||
} else {
|
||||
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) {
|
||||
throw new RuntimeException("Unable to resolve auth flow binding override for: " + entry.getKey());
|
||||
}
|
||||
client.setAuthenticationFlowBindingOverride(entry.getKey(), entry.getValue());
|
||||
client.setAuthenticationFlowBindingOverride(entry.getKey(), flowId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.ClientResource;
|
|||
import org.keycloak.admin.client.resource.ClientScopeResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.authentication.AuthenticationFlow;
|
||||
import org.keycloak.common.constants.KerberosConstants;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
|
@ -137,6 +138,16 @@ public class ExportImportUtil {
|
|||
Assert.assertEquals("client-secret", application.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
|
||||
Assert.assertNull(ApiUtil.findClientResourceById(realmRsc, "982734"));
|
||||
Assert.assertEquals(application.getId(), ApiUtil.findClientResourceById(realmRsc, application.getId()).toRepresentation().getId());
|
||||
|
|
|
@ -240,6 +240,9 @@
|
|||
"directAccessGrantsEnabled": false,
|
||||
"serviceAccountsEnabled": true,
|
||||
"clientAuthenticatorType": "client-jwt",
|
||||
"authenticationFlowBindingOverrides": {
|
||||
"browser": "73dcb1e4-2c7c-4494-825d-f2677cbc114c"
|
||||
},
|
||||
"protocolMappers" : [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue