From 45cbb641b899c2b4e24fb88e72bc3e99bbb8badb Mon Sep 17 00:00:00 2001 From: kevinklever Date: Wed, 29 Apr 2026 22:53:14 +0200 Subject: [PATCH] Propagate authenticated user when send-event task is sent asynchronously The async send-event job re-evaluates eventInParameter source expressions in a worker thread, where the Authentication thread-local is not set. As a result expressions like ${authenticatedUserId} resolve to null even though they would resolve correctly when the same task is configured with flowable:sendSynchronously=true. Capture the authenticated user when the async job is scheduled (storing it on the JobEntity's jobHandlerConfiguration, which the handler does not otherwise use) and restore it in AsyncSendEventJobHandler before invoking the activity behavior. The previous authenticated user is restored in a finally block. --- .../SendEventTaskActivityBehavior.java | 10 ++++- .../jobexecutor/AsyncSendEventJobHandler.java | 14 ++++++ .../test/eventregistry/SendEventTaskTest.java | 45 +++++++++++++++++++ ...WithAuthenticatedUserExpression.bpmn20.xml | 37 +++++++++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/SendEventTaskTest.testSendEventWithAuthenticatedUserExpression.bpmn20.xml diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/SendEventTaskActivityBehavior.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/SendEventTaskActivityBehavior.java index 3d5bc1e1774..8a24e4caa14 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/SendEventTaskActivityBehavior.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/SendEventTaskActivityBehavior.java @@ -30,6 +30,7 @@ import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.el.ExpressionManager; +import org.flowable.common.engine.impl.identity.Authentication; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.ProcessEngineConfiguration; import org.flowable.engine.delegate.DelegateExecution; @@ -92,9 +93,16 @@ public void execute(DelegateExecution execution) { boolean sendSynchronously = sendEventServiceTask.isSendSynchronously() || executedAsAsyncJob; if (!sendSynchronously) { JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); - + JobEntity job = JobUtil.createJob(executionEntity, sendEventServiceTask, AsyncSendEventJobHandler.TYPE, processEngineConfiguration); + // Capture the currently authenticated user so that ${authenticatedUserId} references in + // eventInParameters resolve to the scheduling user when the async job runs in a worker thread. + String authenticatedUserId = Authentication.getAuthenticatedUserId(); + if (StringUtils.isNotEmpty(authenticatedUserId)) { + job.setJobHandlerConfiguration(authenticatedUserId); + } + jobService.createAsyncJob(job, true); jobService.scheduleAsyncJob(job); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/AsyncSendEventJobHandler.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/AsyncSendEventJobHandler.java index e2f770ebf87..9ca444127d2 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/AsyncSendEventJobHandler.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/AsyncSendEventJobHandler.java @@ -12,9 +12,11 @@ */ package org.flowable.engine.impl.jobexecutor; +import org.apache.commons.lang3.StringUtils; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.SendEventServiceTask; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.identity.Authentication; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.impl.delegate.ActivityBehavior; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; @@ -50,11 +52,23 @@ public void execute(JobEntity job, String configuration, VariableScope variableS "Unexpected activity behavior (" + behavior.getClass() + ") found for " + job + " at " + executionEntity); } + // Restore the authenticated user that scheduled the send so that ${authenticatedUserId} + // references in eventInParameters resolve consistently with synchronous sending. + boolean restoreAuthentication = false; + String previousAuthenticatedUserId = Authentication.getAuthenticatedUserId(); + if (StringUtils.isNotEmpty(configuration) && previousAuthenticatedUserId == null) { + Authentication.setAuthenticatedUserId(configuration); + restoreAuthentication = true; + } + try { commandContext.addAttribute(TYPE, true); // Will be read in the SendEventTaskActivityBehavior activityBehavior.execute(executionEntity); } finally { commandContext.removeAttribute(TYPE); + if (restoreAuthentication) { + Authentication.setAuthenticatedUserId(previousAuthenticatedUserId); + } } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/SendEventTaskTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/SendEventTaskTest.java index 9001d14a433..5a96ae1d049 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/SendEventTaskTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/SendEventTaskTest.java @@ -21,6 +21,7 @@ import java.util.Map; import org.flowable.common.engine.impl.history.HistoryLevel; +import org.flowable.common.engine.impl.identity.Authentication; import org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.impl.jobexecutor.AsyncSendEventJobHandler; @@ -322,6 +323,50 @@ public void testSendEventWithExpressions() throws Exception { + " }"); } + @Test + @Deployment + public void testSendEventWithAuthenticatedUserExpression() throws Exception { + Authentication.setAuthenticatedUserId("alice"); + try { + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("process") + .variable("accountNumber", 123) + .start(); + + assertThat(outboundEventChannelAdapter.receivedEvents).isEmpty(); + + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + assertThat(task).isNotNull(); + + taskService.complete(task.getId()); + + Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult(); + assertThat(job).isNotNull(); + assertThat(job.getJobHandlerType()).isEqualTo(AsyncSendEventJobHandler.TYPE); + assertThat(job.getElementId()).isEqualTo("sendEventTask"); + + assertThat(outboundEventChannelAdapter.receivedEvents).isEmpty(); + + // Clear the authenticated user before the job runs to prove the captured value is restored + // by the AsyncSendEventJobHandler instead of relying on a thread-local that the job worker + // does not inherit. + Authentication.setAuthenticatedUserId(null); + + JobTestHelper.waitForJobExecutorToProcessAllJobs(processEngineConfiguration, managementService, 5000, 200); + + assertThat(outboundEventChannelAdapter.receivedEvents).hasSize(1); + + JsonNode jsonNode = processEngineConfiguration.getObjectMapper().readTree(outboundEventChannelAdapter.receivedEvents.get(0)); + assertThatJson(jsonNode) + .isEqualTo("{" + + " nameProperty: 'alice'," + + " numberProperty: 123" + + " }"); + } finally { + Authentication.setAuthenticatedUserId(null); + } + } + @Test @Deployment public void testSendEventSkipExpression() throws Exception { diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/SendEventTaskTest.testSendEventWithAuthenticatedUserExpression.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/SendEventTaskTest.testSendEventWithAuthenticatedUserExpression.bpmn20.xml new file mode 100644 index 00000000000..71d548ec7da --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/SendEventTaskTest.testSendEventWithAuthenticatedUserExpression.bpmn20.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + anotherEvent + out-channel + + + + + + + + + + + + + + + +