Repository navigation
Conversation
… resuming a CSV file StandardCsvWriter compared the header of the file it resumes with the headers of the topic schema over the length of the file header only; the length check was commented out. A file with more columns threw ArrayIndexOutOfBoundsException, which escaped CsvAuditEventHandler.startup(), and a file with fewer columns was resumed with rows longer than its header. It now throws the IOException in both cases, as SecureCsvWriter does, and checks the headers for null before it reads the file.
maximthomas
left a comment
There was a problem hiding this comment.
praise: The length check closes both resume failures where they start, and each new test is shown to fail without it.
StandardCsvWriter.java:65-67rejects a length mismatch before the per-column loop, so the more-columns file no longer throwsArrayIndexOutOfBoundsExceptionout ofCsvAuditEventHandler.startup();checkNotNull(headers, ...)now runs before the file is read (:56).- With
StandardCsvWriter.javareverted to the base, all four new cases fail: both rows ofCsvWriterTest.shouldNotResumeCsvFileWithHeadersOfOtherLength,shouldRejectNullHeadersWhenResumingCsvFile, andCsvAuditEventHandlerTest.testStartupWithAuditFileOfMoreColumnsThanTopicSchema.
issue (non-blocking): A resumed header with the right number of columns but an empty column name still throws NullPointerException out of startup.
commons/audit/handler-csv/src/main/java/org/forgerock/audit/handlers/csv/StandardCsvWriter.java:69
super-csv 2.4.0 getHeader(true) reads an empty header cell (_id,,transactionId or "_id","","transactionId") as null, checked with a probe. That header passes the new length check, and actualHeaders[idx].equals(headers[idx]) throws an NPE. CsvAuditEventHandler.startup() catches only IOException (:222), and AuditServiceImpl.startup() catches only ResourceException (AuditServiceImpl.java:399-400), so the whole audit service fails to start instead of the one topic. This is the same kind of escape the PR closes for the length mismatch, reached through a different input. The base has the same behaviour, so it can be fixed here or in a follow-up.
for (int idx = 0; idx < actualHeaders.length; idx++) {
if (!headers[idx].equals(actualHeaders[idx])) {
throw new IOException("Resuming an existing CSV file but the headers do not match.");
}
}Pin: a CsvWriterTest case that resumes a file with the header child1,,child3 against the headers {child1, child2, child3} and expects the IOException. At the head it fails with the NPE.
…n resuming a CSV file Super CSV reads an empty column name as null. Such a header passed the length check, and the comparison called equals() on it, so StandardCsvWriter and SecureCsvWriter threw NullPointerException instead of the IOException that CsvAuditEventHandler.startup() reports for the topic. Call equals() on the headers of the topic schema, which hold no null.
|
Fixed in
|
maximthomas
left a comment
There was a problem hiding this comment.
praise: The round-2 commit fixes the empty-column-name NPE where it starts, and the new test is measured to pin it.
headers[idx].equals(actualHeaders[idx])atStandardCsvWriter.java:70andSecureCsvWriter.java:146puts the topic-schema header, which cannot be null, on the receiver side; for every non-null pair the result is unchanged.CsvWriterTest.shouldNotResumeCsvSecureFileWithEmptyColumnNamefails 1/10 with the NPE against a revertedSecureCsvWriter.java:146, with every sibling module built from this head's sources (-am, JDK 26).
Fixes #345.
StandardCsvWritercompared the header of the CSV file it resumes with the headers of the topic schema in a loop over the header of the file; the length check before the loop was commented out.ArrayIndexOutOfBoundsException.CsvAuditEventHandler.startup()logs anIOExceptionand goes on with the next topic, but this exception escaped it, so the handler failed to start.NullPointerException, which escapedstartup()too: Super CSV reads an empty column name asnull, and the loop calledequals()on the header of the file.nullheaders threw aNullPointerExceptionwithout the message ofcheckNotNull, which ran after the comparison.StandardCsvWriternow compares the lengths and throws "Resuming an existing CSV file but the headers do not match." when they differ, asSecureCsvWriterdoes, callsequals()on the headers of the topic schema, which hold nonull, and checks the headers fornullbefore it reads the file.SecureCsvWriterhad the same loop. A tamper-evident file passes the verification with an emptied column name when that column is empty in every row, since the row HMAC covers the values only; it now gets the sameIOExceptioninstead of theNullPointerException, which the constructor wraps in itsRuntimeExceptionuntil #341.Behavior change
A plain CSV file with fewer columns than the topic schema is no longer resumed. As with a file whose columns differ in name or order, the handler logs the error at startup and rejects the events of the topic with
BadRequestExceptionuntil the file is moved away; the writer then starts a new file with the current header.Tests
CsvWriterTest.shouldNotResumeCsvFileWithHeadersThatDoNotMatch, with one column more and one column less than the schema, and with an empty column name in the middle and at the end (child1,,child3,child1,child2,): the writer throws theIOExceptionand leaves the file as it was.CsvWriterTest.shouldRejectNullHeadersWhenResumingCsvFile.CsvWriterTest.shouldNotResumeCsvSecureFileWithEmptyColumnName: a tamper-evident file whose header lost the name of a column that is empty in every row. The test accepts theIOExceptionof the header check or of the verification: once [#344] Tamper-evident CSV audit: sign the column names and the value lengths in the row HMAC #369 covers the column names in the row HMAC, the same file fails the verification.CsvAuditEventHandlerTest.testStartupWithAuditFileOfMoreColumnsThanTopicSchema: the handler starts, and the events of the topic are rejected withBadRequestException.Each case fails without the fix.
audit/coreandaudit/handler-csvpass locally.#341, #347, #348 and #369 change the same files; each merges with this one without conflicts.