[ 
https://issues.apache.org/jira/browse/CAMEL-25615?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-25615.
---------------------------------
    Resolution: Fixed

> 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
>            Priority: Major
>             Fix For: 4.24.0
>
>
> {{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)

Reply via email to