Avoid adding organization flows if they are already exist

Closes #31182

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-07-16 14:34:31 -03:00 committed by Alexander Schwartz
parent 25da859d18
commit de1de06354
3 changed files with 113 additions and 132 deletions

View file

@ -17,20 +17,12 @@
package org.keycloak.organization.jpa; package org.keycloak.organization.jpa;
import org.keycloak.Config;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupModel.GroupEvent; import org.keycloak.models.GroupModel.GroupEvent;
import org.keycloak.models.ModelValidationException; import org.keycloak.models.ModelValidationException;
import org.keycloak.organization.authentication.authenticators.broker.IdpOrganizationAuthenticatorFactory;
import org.keycloak.organization.authentication.authenticators.browser.OrganizationAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmModel.RealmPostCreateEvent;
import org.keycloak.models.RealmModel.RealmRemovedEvent;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.organization.OrganizationProviderFactory; import org.keycloak.organization.OrganizationProviderFactory;
import org.keycloak.organization.utils.Organizations; import org.keycloak.organization.utils.Organizations;
@ -65,133 +57,13 @@ public class JpaOrganizationProviderFactory implements OrganizationProviderFacto
return ID; return ID;
} }
private void handleEvents(ProviderEvent event) { private void handleEvents(ProviderEvent e) {
if (event instanceof RealmPostCreateEvent) { if (e instanceof GroupEvent event) {
RealmModel realm = ((RealmPostCreateEvent) event).getCreatedRealm(); KeycloakSession session = event.getKeycloakSession();
configureAuthenticationFlows(realm); GroupModel group = event.getGroup();
}
if (event instanceof GroupEvent) {
GroupEvent groupEvent = (GroupEvent) event;
KeycloakSession session = groupEvent.getKeycloakSession();
GroupModel group = groupEvent.getGroup();
if (!Organizations.canManageOrganizationGroup(session, group)) { if (!Organizations.canManageOrganizationGroup(session, group)) {
throw new ModelValidationException("Can not update organization group"); throw new ModelValidationException("Can not update organization group");
} }
} }
} }
private void configureAuthenticationFlows(RealmModel realm) {
addOrganizationFirstBrokerFlowStep(realm);
addOrganizationBrowserFlowStep(realm);
}
private void addOrganizationFirstBrokerFlowStep(RealmModel realm) {
AuthenticationFlowModel firstBrokerLoginFlow = realm.getFirstBrokerLoginFlow();
if (firstBrokerLoginFlow == null) {
return;
}
if (realm.getAuthenticationExecutionsStream(firstBrokerLoginFlow.getId())
.map(AuthenticationExecutionModel::getAuthenticator)
.anyMatch(IdpOrganizationAuthenticatorFactory.ID::equals)) {
return;
}
if (!Config.getAdminRealm().equals(realm.getName())) {
// do not add the org flows to the master realm for now.
AuthenticationFlowModel conditionalOrg = new AuthenticationFlowModel();
conditionalOrg.setTopLevel(false);
conditionalOrg.setBuiltIn(true);
conditionalOrg.setAlias("First Broker Login - Conditional Organization");
conditionalOrg.setDescription("Flow to determine if the authenticator that adds organization members is to be used");
conditionalOrg.setProviderId("basic-flow");
conditionalOrg = realm.addAuthenticationFlow(conditionalOrg);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(firstBrokerLoginFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.CONDITIONAL);
execution.setFlowId(conditionalOrg.getId());
execution.setPriority(50);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("conditional-user-configured");
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator(IdpOrganizationAuthenticatorFactory.ID);
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}
}
public void addOrganizationBrowserFlowStep(RealmModel realm) {
AuthenticationFlowModel browserFlow = realm.getBrowserFlow();
if (browserFlow == null) {
return;
}
if (realm.getAuthenticationExecutionsStream(browserFlow.getId())
.map(AuthenticationExecutionModel::getAuthenticator)
.anyMatch(OrganizationAuthenticatorFactory.ID::equals)) {
return;
}
if (!Config.getAdminRealm().equals(realm.getName())) {
// do not add the org flows to the master realm for now.
AuthenticationFlowModel organizations = new AuthenticationFlowModel();
organizations.setTopLevel(false);
organizations.setBuiltIn(true);
organizations.setAlias("Organization");
organizations.setProviderId("basic-flow");
organizations = realm.addAuthenticationFlow(organizations);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(browserFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setFlowId(organizations.getId());
execution.setPriority(26);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
AuthenticationFlowModel conditionalOrg = new AuthenticationFlowModel();
conditionalOrg.setTopLevel(false);
conditionalOrg.setBuiltIn(true);
conditionalOrg.setAlias("Browser - Conditional Organization");
conditionalOrg.setDescription("Flow to determine if the organization identity-first login is to be used");
conditionalOrg.setProviderId("basic-flow");
conditionalOrg = realm.addAuthenticationFlow(conditionalOrg);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(organizations.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.CONDITIONAL);
execution.setFlowId(conditionalOrg.getId());
execution.setPriority(10);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("conditional-user-configured");
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator(OrganizationAuthenticatorFactory.ID);
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}
}
} }

View file

@ -17,6 +17,9 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
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;
@ -379,6 +382,8 @@ public class DefaultAuthenticationFlows {
execution.setPriority(20); execution.setPriority(20);
execution.setAuthenticatorFlow(false); execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution); realm.addAuthenticatorExecution(execution);
addOrganizationBrowserFlowStep(realm, browser);
} }
public static void addIdentityProviderAuthenticator(RealmModel realm, String defaultProvider) { public static void addIdentityProviderAuthenticator(RealmModel realm, String defaultProvider) {
@ -638,6 +643,8 @@ public class DefaultAuthenticationFlows {
execution.setPriority(20); execution.setPriority(20);
execution.setAuthenticatorFlow(false); execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution); realm.addAuthenticatorExecution(execution);
addOrganizationFirstBrokerFlowStep(realm, firstBrokerLogin);
} }
public static void samlEcpProfile(RealmModel realm) { public static void samlEcpProfile(RealmModel realm) {
@ -682,4 +689,96 @@ public class DefaultAuthenticationFlows {
realm.addAuthenticatorExecution(execution); realm.addAuthenticatorExecution(execution);
} }
private static void addOrganizationFirstBrokerFlowStep(RealmModel realm, AuthenticationFlowModel flow) {
if (!Profile.isFeatureEnabled(Feature.ORGANIZATION)) {
return;
}
if (!Config.getAdminRealm().equals(realm.getName())) {
// do not add the org flows to the master realm for now.
AuthenticationFlowModel conditionalOrg = new AuthenticationFlowModel();
conditionalOrg.setTopLevel(false);
conditionalOrg.setBuiltIn(true);
conditionalOrg.setAlias("First Broker Login - Conditional Organization");
conditionalOrg.setDescription("Flow to determine if the authenticator that adds organization members is to be used");
conditionalOrg.setProviderId("basic-flow");
conditionalOrg = realm.addAuthenticationFlow(conditionalOrg);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(flow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.CONDITIONAL);
execution.setFlowId(conditionalOrg.getId());
execution.setPriority(50);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("conditional-user-configured");
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("idp-add-organization-member");
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}
}
public static void addOrganizationBrowserFlowStep(RealmModel realm, AuthenticationFlowModel flow) {
if (!Profile.isFeatureEnabled(Feature.ORGANIZATION)) {
return;
}
if (!Config.getAdminRealm().equals(realm.getName())) {
// do not add the org flows to the master realm for now.
AuthenticationFlowModel organizations = new AuthenticationFlowModel();
organizations.setTopLevel(false);
organizations.setBuiltIn(true);
organizations.setAlias("Organization");
organizations.setProviderId("basic-flow");
organizations = realm.addAuthenticationFlow(organizations);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(flow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setFlowId(organizations.getId());
execution.setPriority(26);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
AuthenticationFlowModel conditionalOrg = new AuthenticationFlowModel();
conditionalOrg.setTopLevel(false);
conditionalOrg.setBuiltIn(true);
conditionalOrg.setAlias("Browser - Conditional Organization");
conditionalOrg.setDescription("Flow to determine if the organization identity-first login is to be used");
conditionalOrg.setProviderId("basic-flow");
conditionalOrg = realm.addAuthenticationFlow(conditionalOrg);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(organizations.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.CONDITIONAL);
execution.setFlowId(conditionalOrg.getId());
execution.setPriority(10);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("conditional-user-configured");
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(conditionalOrg.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator("organization");
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}
}
} }

View file

@ -28,10 +28,12 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.OrganizationResource; import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.admin.client.resource.UsersResource;
@ -40,6 +42,8 @@ import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory; import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.PartialImportRepresentation; import org.keycloak.representations.idm.PartialImportRepresentation;
@ -125,6 +129,12 @@ public class OrganizationExportTest extends AbstractOrganizationTest {
// login to the organization identity provider and run the configured first broker login flow // login to the organization identity provider and run the configured first broker login flow
loginPage.login(email, bc.getUserPassword()); loginPage.login(email, bc.getUserPassword());
assertThat(appPage.getRequestType(),is(AppPage.RequestType.AUTH_RESPONSE)); assertThat(appPage.getRequestType(),is(AppPage.RequestType.AUTH_RESPONSE));
AuthenticationManagementResource flows = testRealm().flows();
List<AuthenticationExecutionInfoRepresentation> executions = flows.getExecutions(DefaultAuthenticationFlows.BROWSER_FLOW);
assertThat(executions.stream().filter(e -> "Organization".equals(e.getDisplayName())).count(), is(1L));
executions = flows.getExecutions(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW);
assertThat(executions.stream().filter(e -> "First Broker Login - Conditional Organization".equals(e.getDisplayName())).count(), is(1L));
} }
@Test @Test