Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@
<groupId>org.wso2.carbon.identity.organization.management.core</groupId>
<artifactId>org.wso2.carbon.identity.organization.management.service</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.rule.evaluation</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.rule.management</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.role.v2.mgt.core</artifactId>
</dependency>
</dependencies>

<build>
Expand All @@ -124,7 +136,8 @@
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Name>${project.artifactId}</Bundle-Name>
<Private-Package>
org.wso2.carbon.identity.flow.execution.engine.internal
org.wso2.carbon.identity.flow.execution.engine.internal,
org.wso2.carbon.identity.flow.execution.engine.rule
</Private-Package>
<Import-Package>
javax.xml.parsers; version="${javax.xml.parsers.import.pkg.version}",
Expand Down Expand Up @@ -190,7 +203,13 @@
com.fasterxml.jackson.annotation.*;
version="${com.fasterxml.jackson.annotation.version.range}",
org.wso2.carbon.utils; version="${carbon.kernel.package.import.version.range}",
org.slf4j; version="${org.slf4j.imp.pkg.version.range}"
org.slf4j; version="${org.slf4j.imp.pkg.version.range}",
org.wso2.carbon.identity.rule.evaluation.api.*;
version="${carbon.identity.package.import.version.range}",
org.wso2.carbon.identity.role.v2.mgt.core.*;
version="${carbon.identity.package.import.version.range}",
org.wso2.carbon.identity.rule.management.api.*;
version="${carbon.identity.package.import.version.range}"
</Import-Package>
<Export-Package>
!org.wso2.carbon.identity.flow.execution.internal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ public enum ErrorMessages {
ERROR_CODE_ORGANIZATION_VALIDATION_FAILURE("65035",
"Error while validating organization details.",
"Error occurred while validating the organization details of tenant: %s."),
ERROR_CODE_BRANCH_EVALUATION_FAILURE("65036",
"Error while evaluating a decision.",
"The branch %s of the decision step %s could not be evaluated in the %s flow of tenant: %s."),
ERROR_CODE_CONDITIONAL_BRANCHING_DISABLED("65037",
"Conditional branching is not enabled.",
"The decision step %s in the %s flow of tenant: %s cannot run because conditional branching is not "
+ "enabled on this server."),

// Client errors.
ERROR_CODE_INVALID_FLOW_ID("60001",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.wso2.carbon.identity.flow.execution.engine.exception.FlowEngineException;
import org.wso2.carbon.identity.flow.execution.engine.exception.FlowEngineServerException;
import org.wso2.carbon.identity.flow.execution.engine.graph.PagePromptNode;
import org.wso2.carbon.identity.flow.execution.engine.graph.RuleEvaluationNode;
import org.wso2.carbon.identity.flow.execution.engine.graph.TaskExecutionNode;
import org.wso2.carbon.identity.flow.execution.engine.graph.UserChoiceDecisionNode;
import org.wso2.carbon.identity.flow.execution.engine.model.FlowExecutionContext;
Expand Down Expand Up @@ -223,6 +224,8 @@ private NodeResponse triggerNode(NodeConfig nodeConfig, FlowExecutionContext con
return new TaskExecutionNode().execute(context, nodeConfig);
case Constants.NodeTypes.PROMPT_ONLY:
return new PagePromptNode().execute(context, nodeConfig);
case Constants.NodeTypes.RULE_EVALUATION:
return new RuleEvaluationNode().execute(context, nodeConfig);
default:
throw handleServerException(context.getFlowType(), ERROR_CODE_UNSUPPORTED_NODE, nodeConfig.getType(),
context.getFlowType(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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.wso2.carbon.identity.flow.execution.engine.graph;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.flow.execution.engine.Constants;
import org.wso2.carbon.identity.flow.execution.engine.exception.FlowEngineException;
import org.wso2.carbon.identity.flow.execution.engine.internal.FlowExecutionEngineDataHolder;
import org.wso2.carbon.identity.flow.execution.engine.model.FlowExecutionContext;
import org.wso2.carbon.identity.flow.execution.engine.model.NodeResponse;
import org.wso2.carbon.identity.flow.execution.engine.rule.AbstractFlowRuleEvaluationDataProvider;
import org.wso2.carbon.identity.flow.execution.engine.util.RuleEvaluationDiagnosticLogger;
import org.wso2.carbon.identity.flow.mgt.model.BranchDTO;
import org.wso2.carbon.identity.flow.mgt.model.NodeConfig;
import org.wso2.carbon.identity.flow.mgt.model.StepDTO;
import org.wso2.carbon.identity.flow.mgt.utils.FlowMgtUtils;
import org.wso2.carbon.identity.rule.evaluation.api.exception.RuleEvaluationException;
import org.wso2.carbon.identity.rule.evaluation.api.model.FlowContext;
import org.wso2.carbon.identity.rule.evaluation.api.model.FlowType;
import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.wso2.carbon.identity.flow.execution.engine.util.FlowExecutionEngineUtils.handleServerException;
import static org.wso2.carbon.identity.flow.mgt.Constants.NodeTypes.RULE_EVALUATION;

/**
* A step that decides where the flow goes by evaluating rules against the context.
*/
public class RuleEvaluationNode implements Node {

private static final Log LOG = LogFactory.getLog(RuleEvaluationNode.class);
private static final RuleEvaluationDiagnosticLogger DIAGNOSTIC_LOGGER = new RuleEvaluationDiagnosticLogger();

@Override
public String getName() {

return RULE_EVALUATION;
}

@Override
public NodeResponse execute(FlowExecutionContext context, NodeConfig nodeConfig) throws FlowEngineException {

if (!FlowMgtUtils.isConditionalBranchingEnabled()) {
DIAGNOSTIC_LOGGER.logConditionalBranchingDisabled(context.getFlowType(), nodeConfig.getId());
throw handleServerException(Constants.ErrorMessages.ERROR_CODE_CONDITIONAL_BRANCHING_DISABLED,
nodeConfig.getId(), context.getFlowType(), context.getTenantDomain());
}
BranchDTO selected = selectBranch(context, nodeConfig, branchesOf(context, nodeConfig));
if (selected == null) {
DIAGNOSTIC_LOGGER.logNoBranchSelected(context.getFlowType(), nodeConfig.getId());
throw handleServerException(Constants.ErrorMessages.ERROR_CODE_UNSUPPORTED_NODE,
nodeConfig.getType(), context.getFlowType(),
context.getGraphConfig().getId(), context.getTenantDomain());
}

nodeConfig.setNextNodeId(selected.getNextId());
if (LOG.isDebugEnabled()) {
LOG.debug("Rule evaluation " + nodeConfig.getId() + " selected branch " + selected.getId() + ".");
}
return new NodeResponse.Builder().status(Constants.STATUS_COMPLETE).build();
}

@Override
public NodeResponse rollback(FlowExecutionContext context, NodeConfig nodeConfig) throws FlowEngineException {

return new NodeResponse.Builder().status(Constants.STATUS_COMPLETE).build();
}

/**
* The first branch whose rule holds, or the default.
*
* @param context Flow execution context.
* @param nodeConfig The decision step.
* @param branches Branches of the decision, in evaluation order.
* @return The winning branch, or null when the decision has no default and nothing matched.
* @throws FlowEngineException If a branch cannot be evaluated.
*/
private BranchDTO selectBranch(FlowExecutionContext context, NodeConfig nodeConfig, List<BranchDTO> branches)
throws FlowEngineException {

BranchDTO defaultBranch = null;
for (BranchDTO branch : branches) {
if (branch.isDefault()) {
defaultBranch = branch;
continue;
}
if (holds(branch, context, nodeConfig)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Branch '" + branch.getName() + "' holds.");
}
DIAGNOSTIC_LOGGER.logBranchSelected(context.getFlowType(), nodeConfig.getId(), branch.getId(),
branch.getName());
return branch;
}
}
if (defaultBranch != null) {
DIAGNOSTIC_LOGGER.logDefaultBranchSelected(context.getFlowType(), nodeConfig.getId(),
defaultBranch.getId(), defaultBranch.getName());
}
return defaultBranch;
}

/**
* Whether a branch's rule holds. A branch that cannot be evaluated fails the flow rather than counting as not
* holding, so a failed lookup never sends the user down another branch.
*/
private boolean holds(BranchDTO branch, FlowExecutionContext context, NodeConfig nodeConfig)
throws FlowEngineException {

RuleEvaluationService evaluationService =
FlowExecutionEngineDataHolder.getInstance().getRuleEvaluationService();
try {
if (evaluationService == null) {
throw new IllegalStateException("Rule evaluation service is not available.");
}
Map<String, Object> contextData = new HashMap<>();
contextData.put(AbstractFlowRuleEvaluationDataProvider.FLOW_EXECUTION_CONTEXT, context);
FlowContext ruleFlowContext = new FlowContext(
FlowType.valueOf(context.getFlowType()), contextData);

return evaluationService.evaluate(branch.getRule(), ruleFlowContext, context.getTenantDomain())
.isRuleSatisfied();
} catch (RuleEvaluationException | RuntimeException e) {
DIAGNOSTIC_LOGGER.logBranchEvaluationFailed(context.getFlowType(), nodeConfig.getId(), branch.getName(),
e.getMessage());
throw handleServerException(Constants.ErrorMessages.ERROR_CODE_BRANCH_EVALUATION_FAILURE, e,
branch.getName(), nodeConfig.getId(), context.getFlowType(), context.getTenantDomain());
}
}

private List<BranchDTO> branchesOf(FlowExecutionContext context, NodeConfig nodeConfig)
throws FlowEngineException {

StepDTO step = context.getGraphConfig() == null
? null : context.getGraphConfig().getNodePageMappings().get(nodeConfig.getId());
if (step == null || step.getData() == null || step.getData().getAction() == null
|| step.getData().getAction().getBranches() == null) {
throw handleServerException(Constants.ErrorMessages.ERROR_CODE_UNSUPPORTED_NODE,
nodeConfig.getType(), context.getFlowType(),
context.getGraphConfig() == null ? null : context.getGraphConfig().getId(),
context.getTenantDomain());
}
return step.getData().getAction().getBranches();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.wso2.carbon.identity.flow.mgt.FlowMgtService;
import org.wso2.carbon.identity.input.validation.mgt.services.InputValidationManagementService;
import org.wso2.carbon.identity.organization.management.service.OrganizationManager;
import org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService;
import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.FederatedAssociationManager;
import org.wso2.carbon.user.core.service.RealmService;

Expand All @@ -51,6 +53,8 @@ public class FlowExecutionEngineDataHolder {
private FederatedAssociationManager federatedAssociationManager;
private IdentityEventService identityEventService;
private OrganizationManager organizationManager;
private RuleEvaluationService ruleEvaluationService;
private RoleManagementService roleManagementService;
private List<FlowExecutionListener> flowExecutionListeners = new ArrayList<>();

private FlowExecutionEngineDataHolder() {
Expand Down Expand Up @@ -106,6 +110,36 @@ public void removeExecutor(Executor executor) {
executors.remove(name, executor);
}

/**
* Evaluates the rules guarding the branches of a rule decision.
*
* @return The rule evaluation service, or null when it is not yet available.
*/
public RuleEvaluationService getRuleEvaluationService() {

return ruleEvaluationService;
}

public void setRuleEvaluationService(RuleEvaluationService ruleEvaluationService) {

this.ruleEvaluationService = ruleEvaluationService;
}

/**
* Role management, used to read the roles a user is assigned when a flow condition names them.
*
* @return The role management service, or null when it is not yet available.
*/
public RoleManagementService getRoleManagementService() {

return roleManagementService;
}

public void setRoleManagementService(RoleManagementService roleManagementService) {

this.roleManagementService = roleManagementService;
}

/**
* Get the realm service.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
import org.wso2.carbon.identity.input.validation.mgt.services.InputValidationManagementService;
import org.wso2.carbon.identity.organization.management.service.OrganizationManager;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.FederatedAssociationManager;
import org.wso2.carbon.identity.flow.execution.engine.rule.InvitedUserRegistrationFlowRuleEvaluationDataProvider;
import org.wso2.carbon.identity.flow.execution.engine.rule.PasswordRecoveryFlowRuleEvaluationDataProvider;
import org.wso2.carbon.identity.flow.execution.engine.rule.RegistrationFlowRuleEvaluationDataProvider;
import org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService;
import org.wso2.carbon.identity.rule.evaluation.api.provider.RuleEvaluationDataProvider;
import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService;
import org.wso2.carbon.user.core.service.RealmService;

import java.util.Comparator;
Expand Down Expand Up @@ -80,6 +86,12 @@
FlowExecutionService.getInstance(), null);
bundleContext.registerService(FlowExecutionListener.class.getName(), new InputProcessingListener(),
null);
bundleContext.registerService(RuleEvaluationDataProvider.class.getName(),
new RegistrationFlowRuleEvaluationDataProvider(), null);
bundleContext.registerService(RuleEvaluationDataProvider.class.getName(),
new PasswordRecoveryFlowRuleEvaluationDataProvider(), null);
bundleContext.registerService(RuleEvaluationDataProvider.class.getName(),
new InvitedUserRegistrationFlowRuleEvaluationDataProvider(), null);
LOG.debug("Flow Engine service successfully activated.");
} catch (Throwable e) {
LOG.error("Error while initiating Flow Engine service", e);
Expand All @@ -98,6 +110,42 @@
}
}

@Reference(
name = "rule.evaluation.service",
service = RuleEvaluationService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetRuleEvaluationService")
protected void setRuleEvaluationService(RuleEvaluationService ruleEvaluationService) {

LOG.debug("Setting the Rule Evaluation Service in the Flow Engine service component.");
FlowExecutionEngineDataHolder.getInstance().setRuleEvaluationService(ruleEvaluationService);
}

protected void unsetRuleEvaluationService(RuleEvaluationService ruleEvaluationService) {

Check warning on line 125 in components/flow-orchestration-framework/org.wso2.carbon.identity.flow.execution.engine/src/main/java/org/wso2/carbon/identity/flow/execution/engine/internal/FlowExecutionEngineServiceComponent.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "ruleEvaluationService".

See more on https://sonarcloud.io/project/issues?id=wso2_carbon-identity-framework&issues=AaEaiZPzXWoDynQjzS8j&open=AaEaiZPzXWoDynQjzS8j&pullRequest=8335

LOG.debug("Unsetting the Rule Evaluation Service in the Flow Engine service component.");
FlowExecutionEngineDataHolder.getInstance().setRuleEvaluationService(null);
}

@Reference(
name = "role.management.service",
service = RoleManagementService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetRoleManagementService")
protected void setRoleManagementService(RoleManagementService roleManagementService) {

LOG.debug("Setting the Role Management Service in the Flow Engine service component.");
FlowExecutionEngineDataHolder.getInstance().setRoleManagementService(roleManagementService);
}

protected void unsetRoleManagementService(RoleManagementService roleManagementService) {

Check warning on line 143 in components/flow-orchestration-framework/org.wso2.carbon.identity.flow.execution.engine/src/main/java/org/wso2/carbon/identity/flow/execution/engine/internal/FlowExecutionEngineServiceComponent.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "roleManagementService".

See more on https://sonarcloud.io/project/issues?id=wso2_carbon-identity-framework&issues=AaEaiZPzXWoDynQjzS8k&open=AaEaiZPzXWoDynQjzS8k&pullRequest=8335

LOG.debug("Unsetting the Role Management Service in the Flow Engine service component.");
FlowExecutionEngineDataHolder.getInstance().setRoleManagementService(null);
}

@Reference(
name = "user.realmservice.default",
service = RealmService.class,
Expand Down
Loading
Loading