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 + + + + + + + + + + + + + + + +