Skip to content

FINERACT-2461: Refactor EmailReadPlatformServiceImpl to use Prepared Statements#5417

Merged
adamsaghy merged 2 commits intoapache:developfrom
Saifulhuq01:fix-email-sql-injection
Feb 6, 2026
Merged

FINERACT-2461: Refactor EmailReadPlatformServiceImpl to use Prepared Statements#5417
adamsaghy merged 2 commits intoapache:developfrom
Saifulhuq01:fix-email-sql-injection

Conversation

@Saifulhuq01
Copy link
Contributor

Description

Refactored EmailReadPlatformServiceImpl.java to replace legacy SQL string concatenation with JDBC Prepared Statements.

This change prevents potential SQL injection vulnerabilities by using ? placeholders and passing parameters dynamically via JdbcTemplate.

Resolves FINERACT-2461.
Also related to FINERACT-2459.

Changes

  • Refactored queries in retrieveAllPending, retrieveAllSent, and other read methods to use ? placeholders.
  • Implemented List<Object> to pass parameters dynamically.
  • Applied Spotless formatting.

Checklist

  • Commit message follows guidelines
  • Coding conventions followed
  • Build is passing

* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this removed?

Apologies, that was accidental during the refactor. I have restored the License Header in the latest push.

@oleksii-novikov-onix
Copy link
Contributor

These changes really improved the code, but in my opinion it would be much more beneficial to rewrite it from jdbcTemplate to Spring Data JPA.

@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch from 4c731e2 to 7eb8e83 Compare January 31, 2026 04:42
@Saifulhuq01
Copy link
Contributor Author

These changes really improved the code, but in my opinion it would be much more beneficial to rewrite it from jdbcTemplate to Spring Data JPA.

Thank you for the review! I agree that moving to Spring Data JPA is the better long-term architectural goal.

However, since this PR is focused on a critical Security Fix (SQL Injection), I would prefer to keep the scope limited to jdbcTemplate for now to ensure a faster merge. I can open a separate Jira ticket to handle the JPA migration as a follow-up task. Does that sound reasonable?

@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch from 7eb8e83 to 948f2b5 Compare January 31, 2026 09:44
public Collection<EmailData> retrieveAllFailed(final SearchParameters searchParameters) {
final String sqlPlusLimit = (searchParameters.getLimit() > 0) ? " " + sqlGenerator.limit(searchParameters.getLimit()) : "";
final String sql = "select " + this.emailRowMapper.schema() + " where emo.status_enum = ?" + sqlPlusLimit;
String sql = "select " + this.emailRowMapper.schema() + " where emo.status_enum = ?";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a pattern here i think you can create helper function here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a pattern here i think you can create helper function here

Good catch. I have refactored the duplicate logic into a private helper method retrieveEmailByStatus to reduce code repetition

@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch 2 times, most recently from 7eb4a6d to c925217 Compare January 31, 2026 17:09
@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch 5 times, most recently from e78692f to 097d594 Compare February 2, 2026 04:32
Copy link
Contributor

@adamsaghy adamsaghy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR does NOT change the EmailReadPlatformServiceImpl.java‎ to use prepared statements.

@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch from 097d594 to 5946091 Compare February 3, 2026 04:18
@Saifulhuq01
Copy link
Contributor Author

This PR does NOT change the EmailReadPlatformServiceImpl.java‎ to use prepared statements.

Apologies @adamsaghy, I believe I accidentally reverted the changes while fixing the license header. I have just force-pushed the correct version with the prepared statements and helper method. Please verify

@Saifulhuq01 Saifulhuq01 requested a review from adamsaghy February 3, 2026 04:20
args.add(status);

if (sqlPlusLimit != null) {
sql += sqlPlusLimit;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is still using string concatenation...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is still using string concatenation...

Ah, I missed converting the sql variable to a StringBuilder. I'll update it to use sql.append(sqlPlusLimit) instead of string concatenation. Pushing the fix shortly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that is not the problem.
The PR title says: use prepared statements. However, you are not using that for the sqlPlusLimit... you are still just concatenate a string to the SQL query itself. Please rewrite it to use prepared statements only and provide the arguments only.

@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch from 5946091 to cb18b0e Compare February 3, 2026 10:31
Comment on lines +187 to +189
if (sqlPlusLimit != null) {
sql.append(sqlPlusLimit);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is:
you are not concatenating to the SQL the following: LIMIT ? but you are concatenating: LIMIT <whatever the user provided in query param> which is wrong...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is: you are not concatenating to the SQL the following: LIMIT ? but you are concatenating: LIMIT <whatever the user provided in query param> which is wrong...

The sqlGenerator.limit() method returns a literal string (e.g. 'LIMIT 10'). I've cleaned up the code to pass the Integer limit down, but sticking to the generator to ensure MSSQL compatibility.

@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch from cb18b0e to 23fb382 Compare February 3, 2026 10:45
Copy link
Contributor

@adamsaghy adamsaghy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I havent seen any existing test cases for this functionality.

Would you mind to write a unit test which tests thoroughly these changes?

You should focus whether the appropriate status is added to the sql and also the appropriate LIMIT added to the sql.

@Saifulhuq01
Copy link
Contributor Author

Saifulhuq01 commented Feb 3, 2026

I havent seen any existing test cases for this functionality.

Would you mind to write a unit test which tests thoroughly these changes?

You should focus whether the appropriate status is added to the sql and also the appropriate LIMIT added to the sql.

Done. I have added EmailReadPlatformServiceImplTest to verify the logic.

The new tests cover:

testRetrieveAllPendingWithLimit(): Verifies that the LIMIT clause is correctly appended when provided.

testRetrieveAllSentWithoutLimit(): Verifies that no LIMIT clause is added when the limit is null/zero, and checks that the correct status_enum parameter is bound.

I verified the tests locally: Test testRetrieveAllPendingWithLimit() PASSED Test testRetrieveAllSentWithoutLimit() PASSED

@Saifulhuq01 Saifulhuq01 requested a review from adamsaghy February 3, 2026 11:24
private JdbcTemplate jdbcTemplate;

@Mock
private DatabaseSpecificSQLGenerator sqlGenerator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please dont mock the sqlGenerator. Mocking this makes the tests are completely useless...

You can mock the DatabaseTypeResolver only... and you can easily test for Mysql and Postgres as well.

Please make sure to test for positive test cases, like limit is "10" and "1" and for negative where limit is "random text", "-5", "5.6"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please dont mock the sqlGenerator. Mocking this makes the tests are completely useless...

You can mock the DatabaseTypeResolver only... and you can easily test for Mysql and Postgres as well.

Please make sure to test for positive test cases, like limit is "10" and "1" and for negative where limit is "random text", "-5", "5.6"

Thanks for the detailed feedback, Adam. I have updated the PR with the following changes:

Refactoring: EmailReadPlatformServiceImpl now passes the Integer limit directly to the helper method instead of concatenating strings. This ensures limit is strictly an Integer, preventing injection of random text or floats by the compiler itself.

Testing Strategy: I removed the mock for DatabaseSpecificSQLGenerator. The new tests use the real generator and mock only the underlying DatabaseTypeResolver to simulate database dialects accurately.

Test Coverage: I added comprehensive test cases:

testRetrieveAllPendingWithMySQL: Verifies LIMIT 10 syntax for MySQL.

testRetrieveAllSentWithPostgres: Verifies LIMIT 5 syntax for PostgreSQL.

testRetrieveWithNegativeLimit & testRetrieveWithZeroLimit: Verifies that invalid limits (-5, 0) result in no LIMIT clause being appended.

Status: All 5 tests passed locally.

@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch 2 times, most recently from 986770c to 2c62dc8 Compare February 3, 2026 14:43
@Saifulhuq01 Saifulhuq01 requested a review from adamsaghy February 3, 2026 14:47
@Saifulhuq01 Saifulhuq01 force-pushed the fix-email-sql-injection branch from 2c62dc8 to dcbb3b7 Compare February 3, 2026 15:10
@adamsaghy adamsaghy merged commit f5951f1 into apache:develop Feb 6, 2026
36 checks passed
airajena pushed a commit to airajena/fineract that referenced this pull request Feb 9, 2026
…Statements (apache#5417)

* FINERACT-2461: Refactor EmailReadPlatformServiceImpl to use Prepared Statements
* FINERACT-2461: Add unit tests for EmailReadPlatformServiceImpl
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants