shashank created CAMEL-25615:
--------------------------------
Summary: camel-bonita - startCase uploads a file input to the
Bonita server and user of the bonita endpoint that was used first in the JVM
instead of its own, and never deletes the temporary file it creates for each
upload
Key: CAMEL-25615
URL: https://issues.apache.org/jira/browse/CAMEL-25615
Project: Camel
Issue Type: Bug
Reporter: shashank
Assignee: shashank
{{bonita:startCase}} sends three requests per exchange when the body contains a
{{FileInput}}: the process lookup, the upload of the file, and the case
creation. Two defects are in the upload step, both in {{BonitaAPIUtil}}: one in
how the upload helper is obtained ({{getInstance}}), one in {{uploadFile}}
(line numbers at main b5a64d3cc981).
h3. 1. The upload uses the server and the user of the endpoint that was used
first
{{BonitaStartProducer.process}} (line 44) builds a {{BonitaAPI}} with a client
for the configuration of its own endpoint, and the lookup and the case creation
use it. For the file inputs {{BonitaAPI.startCase}} (lines 77-78) calls
{code:java}
BonitaAPIUtil.getInstance(bonitaApiConfig).prepareInputs(processDefinition,
rawInputs);
{code}
and {{BonitaAPIUtil.getInstance}} (lines 47-61) is a lazily created static
singleton:
{code:java}
private static BonitaAPIUtil instance;
public static BonitaAPIUtil getInstance(BonitaAPIConfig bonitaAPIConfig) {
if (instance == null) {
instance = new BonitaAPIUtil();
...
client.register(new BonitaAuthFilter(bonitaAPIConfig));
instance.setWebTarget(client.target(bonitaAPIConfig.getBaseBonitaURI()));
}
return instance;
}
{code}
The configuration argument is only used by the first call in the JVM. Every
later upload goes to the host and port of that first configuration, after a
login with its username and password, whatever endpoint the exchange is on.
With one bonita endpoint, or with several that have the same hostname, port,
username and password, nothing is wrong. With two endpoints that differ in one
of these, for example
{code:java}
from("direct:a").to("bonita:startCase?hostname=bonita-a&port=8080&processName=orders&username=userA&password=...");
from("direct:b").to("bonita:startCase?hostname=bonita-b&port=8080&processName=orders&username=userB&password=...");
{code}
and a first exchange on {{direct:a}}, an exchange with a file input on
{{direct:b}} looks the process up on bonita-b, uploads the file to bonita-a as
userA, and then creates the case on bonita-b with the {{tempPath}} that
bonita-a returned, a path of a file that bonita-b never received. Which
endpoint "wins" depends on which one happens to process the first exchange
after the JVM started.
The singleton is also assigned before its web target is set, and without
synchronisation, so a second thread that enters {{getInstance}} during the
first call can get a helper whose web target is still null. This was read from
the code and shown by a small model, not reproduced in a test.
h3. 2. A temporary file per upload is never deleted
{{BonitaAPIUtil.uploadFile}} (lines 72-76) creates a file with
{{Files.createTempFile("tempFile", ".tmp")}}, writes the content of the file
input to it, and uses only its name, for the {{filename}} of the multipart
part; the content that is sent comes from the byte array. The file is never
deleted (no {{delete}}, no {{deleteOnExit}}). Every file input of every
exchange leaves a copy of its content in {{java.io.tmpdir}}. This needs no
second endpoint: it happens with the default configuration for every uploaded
file.
h3. Reproduction
New {{BonitaStartCaseFileUploadTest}} (4 tests): a CamelContext with two
{{bonita:startCase}} endpoints that point to two WireMock servers (the module
already uses WireMock) and have different users. Each stub server answers the
login with its own session cookie and API token and returns its own
{{tempPath}} for an upload, and records its requests. With the code of main,
each test run in a JVM of its own:
* {{fileOfSecondEndpointIsUploadedToItsOwnServer}} (A first, then B) and
{{fileOfFirstEndpointIsUploadedToItsOwnServerWhenTheOtherEndpointWasUsedFirst}}
(B first, then A): {{file uploads among the requests received by the Bonita
server on port ... ==> expected: <1> but was: <0>}}, for the server of the
endpoint that was used second;
* {{temporaryFileOfTheUploadIsDeleted}} and
{{temporaryFileOfTheUploadIsDeletedWhenTheUploadFails}}: {{the temporary file
of the upload was left behind: .../tempFile12892494639172391283.tmp ==>
expected: <false> but was: <true>}}. These two tests also assert that the
upload is one multipart part named {{tempFile<digits>.tmp}} with the content of
the file input, and the second one that the exchange fails with
{{InternalServerErrorException}} when the server answers the upload with 500;
those assertions pass on main.
When the four tests run in one JVM on main, only the first one sees the helper
being created; the others then fail with {{jakarta.ws.rs.ProcessingException:
java.net.ConnectException}}, because their uploads go to the stub server of the
first test, which has been stopped. That is the same defect seen from a later
exchange. In a run of the whole module on main the helper may already have been
created by {{BonitaAPIUtilPrepareInputsTest}}, so there the failure messages
depend on the order of the test classes as well as on the order of the tests;
all four tests fail in either case.
h3. Proposed fix
{{BonitaAPIBuilder.build}} creates the upload helper from the client of the
{{BonitaAPI}} it builds (new {{BonitaAPIUtil.forClient(Client,
BonitaAPIConfig)}}), so the three requests of an exchange use the same server
and the same user, and no static state is left.
{{BonitaAPIUtil.getInstance(BonitaAPIConfig)}} is kept, but no longer returns a
shared instance: every call returns a new helper for the given configuration;
the existing protected constructor of {{BonitaAPI}} is kept and uses it. The
response filter that turns the {{text/plain}} answer of the upload into JSON is
registered on the upload target only, so the other requests are read as before.
{{uploadFile}} no longer writes the content to the temporary file (nothing
reads it back) and deletes the file in a {{finally}} block with
{{File.delete()}}, which does not throw, so a failing clean-up cannot replace
the exception of the upload. The empty file is still created, because its
generated name is the file name of the part; the multipart request is the same
as on main (compared at the stub server: only the random boundary and the
random digits of the name differ).
No change for a route with a single bonita endpoint, other than the temporary
files not being left behind. No upgrade-guide entry.
camel-bonita tests with the fix: 22 run, 0 failures, 0 skipped (18 existing, 4
new).
How it was found: a review of static mutable state that is initialised from
endpoint configuration, and a small TLA+ model (two endpoints, interleaved
startCase calls, {{getInstance}} statement by statement). For the code of main
TLC reports an upload that goes to the other endpoint's server, a case created
on a server that does not have its file, a helper without web target, and a
temporary file left after the last call; with the fix all four properties hold.
The model is only a sketch of the code.
Not run: nothing was run against a Bonita server ({{BonitaProducerManualIT}} is
disabled and needs a local Bonita). What a Bonita server does with a case whose
file input names a {{tempPath}} it does not know, and whether an upload made by
one user can be used in a case started by another user of the same server, was
not checked; the test only shows which server and which user each request is
sent with. How often two differently configured bonita endpoints are used in
one JVM is not known; the temporary files are left for every file input.
Affected: main (b5a64d3cc981, tested). camel-4.23.x, camel-4.22.1, camel-4.18.4
and camel-4.14.9 have the same lines ({{BonitaAPIUtil}} 47, 51, 72;
{{BonitaAPI}} 77; {{BonitaStartProducer}} 44): read, not run.
Duplicate check (2026-10-13): JIRA text search "bonita" (9 issues: CAMEL-10350,
CAMEL-10858, CAMEL-11383, CAMEL-11845 and five that only mention the
component), none about the upload helper or the temporary file; GitHub pull
requests of apache/camel with "bonita" (9, among them #4455, which closed the
stream of the temporary file but did not delete the file), "BonitaAPIUtil",
"bonita upload", "bonita temp file": none.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)