mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-16 20:15:46 -06:00
Cache expression EvaluatorContext in the workflow component model's notes
Closes #42961 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
committed by
Pedro Igor
parent
5ae0e0a645
commit
a2562caa11
@@ -2,8 +2,8 @@ package org.keycloak.models.workflow;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.workflow.conditions.ExpressionWorkflowConditionProvider;
|
||||
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser;
|
||||
import org.keycloak.models.workflow.conditions.expression.ConditionEvaluator;
|
||||
import org.keycloak.models.workflow.conditions.expression.EvaluatorUtils;
|
||||
import org.keycloak.models.workflow.conditions.expression.EventEvaluator;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
@@ -30,11 +30,10 @@ final class EventBasedWorkflow {
|
||||
* Evaluates the specified context to determine whether the workflow should be activated or not. Activation will happen
|
||||
* if the context's event matches the configured activation events and the resource conditions evaluate to true.
|
||||
*
|
||||
* @param executionContext
|
||||
* @return
|
||||
* @throws WorkflowInvalidStateException
|
||||
* @param executionContext a reference to the workflow execution context.
|
||||
* @return {@code true} if the workflow should be activated, {@code false} otherwise.
|
||||
*/
|
||||
boolean activate(WorkflowExecutionContext executionContext) throws WorkflowInvalidStateException {
|
||||
boolean activate(WorkflowExecutionContext executionContext) {
|
||||
WorkflowEvent event = executionContext.getEvent();
|
||||
if (event == null) {
|
||||
return false;
|
||||
@@ -42,12 +41,12 @@ final class EventBasedWorkflow {
|
||||
return supports(event.getResourceType()) && activateOnEvent(event) && validateResourceConditions(executionContext);
|
||||
}
|
||||
|
||||
boolean deactivate(WorkflowExecutionContext executionContext) throws WorkflowInvalidStateException {
|
||||
boolean deactivate(WorkflowExecutionContext executionContext) {
|
||||
// TODO: rework this once we support concurrency/restart-if-running and concurrency/cancel-if-running to use expressions just like activation conditions
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean restart(WorkflowExecutionContext executionContext) throws WorkflowInvalidStateException {
|
||||
boolean restart(WorkflowExecutionContext executionContext) {
|
||||
WorkflowEvent event = executionContext.getEvent();
|
||||
if (event == null) {
|
||||
return false;
|
||||
@@ -57,14 +56,17 @@ final class EventBasedWorkflow {
|
||||
|
||||
public boolean validateResourceConditions(WorkflowExecutionContext context) {
|
||||
String conditions = getModel().getConfig().getFirst(CONFIG_CONDITIONS);
|
||||
if (StringUtil.isBlank(conditions)) {
|
||||
if (StringUtil.isNotBlank(conditions)) {
|
||||
BooleanConditionParser.EvaluatorContext evaluatorContext = EvaluatorUtils.createEvaluatorContext(model, conditions);
|
||||
ConditionEvaluator evaluator = new ConditionEvaluator(session, context);
|
||||
return evaluator.visit(evaluatorContext);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return new ExpressionWorkflowConditionProvider(getSession(), conditions).evaluate(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determins whether the workflow should be activated based on the given event or not.
|
||||
* Determines whether the workflow should be activated based on the given event or not.
|
||||
*
|
||||
* @param event a reference to the workflow event.
|
||||
* @return {@code true} if the workflow should be activated, {@code false} otherwise.
|
||||
@@ -77,7 +79,7 @@ final class EventBasedWorkflow {
|
||||
|
||||
String eventConditions = model.getConfig().getFirst(CONFIG_ON_EVENT);
|
||||
if (StringUtil.isNotBlank(eventConditions)) {
|
||||
BooleanConditionParser.EvaluatorContext context = EvaluatorUtils.createEvaluatorContext(eventConditions);
|
||||
BooleanConditionParser.EvaluatorContext context = EvaluatorUtils.createEvaluatorContext(model, eventConditions);
|
||||
EventEvaluator eventEvaluator = new EventEvaluator(getSession(), event);
|
||||
return eventEvaluator.visit(context);
|
||||
} else {
|
||||
|
||||
@@ -32,7 +32,9 @@ import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.workflow.conditions.ExpressionWorkflowConditionProvider;
|
||||
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser;
|
||||
import org.keycloak.models.workflow.conditions.expression.EvaluatorUtils;
|
||||
import org.keycloak.models.workflow.conditions.expression.PredicateEvaluator;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_CONDITIONS;
|
||||
@@ -89,6 +91,8 @@ public class UserResourceTypeWorkflowProvider implements ResourceTypeSelector {
|
||||
return cb.conjunction();
|
||||
}
|
||||
|
||||
return new ExpressionWorkflowConditionProvider(session, conditions).toPredicate(cb, query, path);
|
||||
BooleanConditionParser.EvaluatorContext context = EvaluatorUtils.createEvaluatorContext(conditions);
|
||||
PredicateEvaluator evaluator = new PredicateEvaluator(session, cb, query, path);
|
||||
return evaluator.visit(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.keycloak.models.workflow.conditions;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.workflow.WorkflowConditionProviderFactory;
|
||||
|
||||
public class ExpressionWorkflowConditionFactory implements WorkflowConditionProviderFactory<ExpressionWorkflowConditionProvider> {
|
||||
|
||||
public static final String ID = "expression";
|
||||
|
||||
@Override
|
||||
public ExpressionWorkflowConditionProvider create(KeycloakSession session, String configParameter) {
|
||||
if (configParameter == null) {
|
||||
throw new IllegalArgumentException("Expected single configuration parameter (expression)");
|
||||
}
|
||||
return new ExpressionWorkflowConditionProvider(session, configParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(org.keycloak.Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package org.keycloak.models.workflow.conditions;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.workflow.WorkflowConditionProvider;
|
||||
import org.keycloak.models.workflow.WorkflowExecutionContext;
|
||||
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser.EvaluatorContext;
|
||||
import org.keycloak.models.workflow.conditions.expression.ConditionEvaluator;
|
||||
import org.keycloak.models.workflow.conditions.expression.EvaluatorUtils;
|
||||
import org.keycloak.models.workflow.conditions.expression.PredicateEvaluator;
|
||||
|
||||
public class ExpressionWorkflowConditionProvider implements WorkflowConditionProvider {
|
||||
|
||||
private final String expression;
|
||||
private final KeycloakSession session;
|
||||
private EvaluatorContext evaluatorContext;
|
||||
|
||||
public ExpressionWorkflowConditionProvider(KeycloakSession session, String expression) {
|
||||
this.session = session;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(WorkflowExecutionContext context) {
|
||||
validate();
|
||||
ConditionEvaluator evaluator = new ConditionEvaluator(session, context);
|
||||
return evaluator.visit(this.evaluatorContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate toPredicate(CriteriaBuilder cb, CriteriaQuery<String> query, Root<?> userRoot) {
|
||||
validate();
|
||||
PredicateEvaluator evaluator = new PredicateEvaluator(session, cb, query, userRoot);
|
||||
return evaluator.visit(this.evaluatorContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
if (this.evaluatorContext == null) {
|
||||
this.evaluatorContext = EvaluatorUtils.createEvaluatorContext(expression);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op, nothing to close
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package org.keycloak.models.workflow.conditions.expression;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.workflow.WorkflowInvalidStateException;
|
||||
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser.EvaluatorContext;
|
||||
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
@@ -10,7 +12,14 @@ import org.antlr.v4.runtime.CommonTokenStream;
|
||||
|
||||
public class EvaluatorUtils {
|
||||
|
||||
public static BooleanConditionParser.EvaluatorContext createEvaluatorContext(String expression) {
|
||||
/**
|
||||
* Creates an EvaluatorContext from the given expression. If the expression is invalid, a WorkflowInvalidStateException
|
||||
* is thrown with details about the parsing errors.
|
||||
*
|
||||
* @param expression the boolean expression to parse
|
||||
* @return the EvaluatorContext representing the parsed expression
|
||||
*/
|
||||
public static EvaluatorContext createEvaluatorContext(String expression) {
|
||||
// to properly validate the expression, we need to parse it.
|
||||
CharStream charStream = CharStreams.fromString(expression);
|
||||
BooleanConditionLexer lexer = new BooleanConditionLexer(charStream);
|
||||
@@ -23,7 +32,7 @@ public class EvaluatorUtils {
|
||||
parser.addErrorListener(errorListener);
|
||||
|
||||
// parse the expression and check for errors
|
||||
BooleanConditionParser.EvaluatorContext context = parser.evaluator();
|
||||
EvaluatorContext context = parser.evaluator();
|
||||
if (errorListener.hasErrors()) {
|
||||
String lineSeparator = System.lineSeparator();
|
||||
String errorDetails = errorListener.getErrorMessages().stream()
|
||||
@@ -35,4 +44,20 @@ public class EvaluatorUtils {
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or retrieves a cached EvaluatorContext for the given workflow model and expression.
|
||||
*
|
||||
* @param workflowModel the workflow component model
|
||||
* @param expression the boolean expression to parse
|
||||
* @return the EvaluatorContext representing the parsed expression
|
||||
*/
|
||||
public static EvaluatorContext createEvaluatorContext(ComponentModel workflowModel, String expression) {
|
||||
EvaluatorContext context = workflowModel.getNote(expression);
|
||||
if (context == null) {
|
||||
context = createEvaluatorContext(expression);
|
||||
workflowModel.setNote(expression, context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,4 @@
|
||||
org.keycloak.models.workflow.conditions.GroupMembershipWorkflowConditionFactory
|
||||
org.keycloak.models.workflow.conditions.IdentityProviderWorkflowConditionFactory
|
||||
org.keycloak.models.workflow.conditions.UserAttributeWorkflowConditionFactory
|
||||
org.keycloak.models.workflow.conditions.RoleWorkflowConditionFactory
|
||||
org.keycloak.models.workflow.conditions.ExpressionWorkflowConditionFactory
|
||||
org.keycloak.models.workflow.conditions.RoleWorkflowConditionFactory
|
||||
@@ -199,7 +199,7 @@ public class DeleteUserWorkflowStepTest extends AbstractWorkflowTest {
|
||||
oauth.openLoginForm();
|
||||
loginPage.fillLogin(USER_NAME, USER_PASSWORD);
|
||||
loginPage.submit();
|
||||
assertTrue(driver.getPageSource().contains("Happy days"), "Test user should be successfully logged in.");
|
||||
assertTrue(driver.driver().getPageSource().contains("Happy days"), "Test user should be successfully logged in.");
|
||||
|
||||
// check that we have two scheduled steps for the user
|
||||
runOnServer.run((RunOnServer) session -> {
|
||||
|
||||
@@ -92,7 +92,7 @@ public class UserSessionRefreshTimeWorkflowTest extends AbstractWorkflowTest {
|
||||
String username = userAlice.getUsername();
|
||||
loginPage.fillLogin(username, userAlice.getPassword());
|
||||
loginPage.submit();
|
||||
assertTrue(driver.getPageSource() != null && driver.getPageSource().contains("Happy days"));
|
||||
assertTrue(driver.page().getPageSource() != null && driver.page().getPageSource().contains("Happy days"));
|
||||
|
||||
// store the first step id for later comparison
|
||||
String firstStepId = runOnServer.fetch(session-> {
|
||||
|
||||
Reference in New Issue
Block a user