Skip to content

Commit 45cbb64

Browse files
author
kevinklever
committed
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.
1 parent 888eef6 commit 45cbb64

4 files changed

Lines changed: 105 additions & 1 deletion

File tree

modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/SendEventTaskActivityBehavior.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.flowable.common.engine.api.delegate.Expression;
3131
import org.flowable.common.engine.api.scope.ScopeTypes;
3232
import org.flowable.common.engine.impl.el.ExpressionManager;
33+
import org.flowable.common.engine.impl.identity.Authentication;
3334
import org.flowable.common.engine.impl.interceptor.CommandContext;
3435
import org.flowable.engine.ProcessEngineConfiguration;
3536
import org.flowable.engine.delegate.DelegateExecution;
@@ -92,9 +93,16 @@ public void execute(DelegateExecution execution) {
9293
boolean sendSynchronously = sendEventServiceTask.isSendSynchronously() || executedAsAsyncJob;
9394
if (!sendSynchronously) {
9495
JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService();
95-
96+
9697
JobEntity job = JobUtil.createJob(executionEntity, sendEventServiceTask, AsyncSendEventJobHandler.TYPE, processEngineConfiguration);
9798

99+
// Capture the currently authenticated user so that ${authenticatedUserId} references in
100+
// eventInParameters resolve to the scheduling user when the async job runs in a worker thread.
101+
String authenticatedUserId = Authentication.getAuthenticatedUserId();
102+
if (StringUtils.isNotEmpty(authenticatedUserId)) {
103+
job.setJobHandlerConfiguration(authenticatedUserId);
104+
}
105+
98106
jobService.createAsyncJob(job, true);
99107
jobService.scheduleAsyncJob(job);
100108

modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/AsyncSendEventJobHandler.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
*/
1313
package org.flowable.engine.impl.jobexecutor;
1414

15+
import org.apache.commons.lang3.StringUtils;
1516
import org.flowable.bpmn.model.FlowElement;
1617
import org.flowable.bpmn.model.SendEventServiceTask;
1718
import org.flowable.common.engine.api.FlowableException;
19+
import org.flowable.common.engine.impl.identity.Authentication;
1820
import org.flowable.common.engine.impl.interceptor.CommandContext;
1921
import org.flowable.engine.impl.delegate.ActivityBehavior;
2022
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
@@ -50,11 +52,23 @@ public void execute(JobEntity job, String configuration, VariableScope variableS
5052
"Unexpected activity behavior (" + behavior.getClass() + ") found for " + job + " at " + executionEntity);
5153
}
5254

55+
// Restore the authenticated user that scheduled the send so that ${authenticatedUserId}
56+
// references in eventInParameters resolve consistently with synchronous sending.
57+
boolean restoreAuthentication = false;
58+
String previousAuthenticatedUserId = Authentication.getAuthenticatedUserId();
59+
if (StringUtils.isNotEmpty(configuration) && previousAuthenticatedUserId == null) {
60+
Authentication.setAuthenticatedUserId(configuration);
61+
restoreAuthentication = true;
62+
}
63+
5364
try {
5465
commandContext.addAttribute(TYPE, true); // Will be read in the SendEventTaskActivityBehavior
5566
activityBehavior.execute(executionEntity);
5667
} finally {
5768
commandContext.removeAttribute(TYPE);
69+
if (restoreAuthentication) {
70+
Authentication.setAuthenticatedUserId(previousAuthenticatedUserId);
71+
}
5872
}
5973
}
6074

modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/SendEventTaskTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map;
2222

2323
import org.flowable.common.engine.impl.history.HistoryLevel;
24+
import org.flowable.common.engine.impl.identity.Authentication;
2425
import org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants;
2526
import org.flowable.engine.history.HistoricActivityInstance;
2627
import org.flowable.engine.impl.jobexecutor.AsyncSendEventJobHandler;
@@ -322,6 +323,50 @@ public void testSendEventWithExpressions() throws Exception {
322323
+ " }");
323324
}
324325

326+
@Test
327+
@Deployment
328+
public void testSendEventWithAuthenticatedUserExpression() throws Exception {
329+
Authentication.setAuthenticatedUserId("alice");
330+
try {
331+
ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
332+
.processDefinitionKey("process")
333+
.variable("accountNumber", 123)
334+
.start();
335+
336+
assertThat(outboundEventChannelAdapter.receivedEvents).isEmpty();
337+
338+
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
339+
assertThat(task).isNotNull();
340+
341+
taskService.complete(task.getId());
342+
343+
Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
344+
assertThat(job).isNotNull();
345+
assertThat(job.getJobHandlerType()).isEqualTo(AsyncSendEventJobHandler.TYPE);
346+
assertThat(job.getElementId()).isEqualTo("sendEventTask");
347+
348+
assertThat(outboundEventChannelAdapter.receivedEvents).isEmpty();
349+
350+
// Clear the authenticated user before the job runs to prove the captured value is restored
351+
// by the AsyncSendEventJobHandler instead of relying on a thread-local that the job worker
352+
// does not inherit.
353+
Authentication.setAuthenticatedUserId(null);
354+
355+
JobTestHelper.waitForJobExecutorToProcessAllJobs(processEngineConfiguration, managementService, 5000, 200);
356+
357+
assertThat(outboundEventChannelAdapter.receivedEvents).hasSize(1);
358+
359+
JsonNode jsonNode = processEngineConfiguration.getObjectMapper().readTree(outboundEventChannelAdapter.receivedEvents.get(0));
360+
assertThatJson(jsonNode)
361+
.isEqualTo("{"
362+
+ " nameProperty: 'alice',"
363+
+ " numberProperty: 123"
364+
+ " }");
365+
} finally {
366+
Authentication.setAuthenticatedUserId(null);
367+
}
368+
}
369+
325370
@Test
326371
@Deployment
327372
public void testSendEventSkipExpression() throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions id="definitions"
3+
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
4+
xmlns:flowable="http://flowable.org/bpmn"
5+
targetNamespace="Examples"
6+
xmlns:tns="Examples">
7+
8+
<process id="process">
9+
10+
<startEvent id="theStart" />
11+
12+
<sequenceFlow sourceRef="theStart" targetRef="task" />
13+
14+
<userTask id="task" />
15+
16+
<sequenceFlow sourceRef="task" targetRef="sendEventTask" />
17+
18+
<serviceTask id="sendEventTask" flowable:type="send-event">
19+
<extensionElements>
20+
<flowable:eventType>anotherEvent</flowable:eventType>
21+
<flowable:channelKey>out-channel</flowable:channelKey>
22+
<flowable:eventInParameter source="${authenticatedUserId}" target="nameProperty" />
23+
<flowable:eventInParameter source="${accountNumber}" target="numberProperty" />
24+
</extensionElements>
25+
</serviceTask>
26+
27+
<sequenceFlow sourceRef="sendEventTask" targetRef="taskAfter" />
28+
29+
<userTask id="taskAfter" />
30+
31+
<sequenceFlow sourceRef="taskAfter" targetRef="theEnd" />
32+
33+
<endEvent id="theEnd" />
34+
35+
</process>
36+
37+
</definitions>

0 commit comments

Comments
 (0)