[
https://issues.apache.org/jira/browse/FINERACT-2961?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Mandib Chaulagain updated FINERACT-2961:
----------------------------------------
Description:
If you set a user's password to the one they already have, Fineract treats it
as a real change. It saves a new hash, adds a row to the password history,
updates the password's last-updated date and returns {{"password": true}} in
the response. This happens whether the password reuse check is on or off,
because the reuse check only looks at the password history, and the current
password only goes into the history once it has been replaced.
The problem is in {{{}JsonCommand.isChangeInPasswordParameterNamed{}}}, which
checks whether the submitted password is different from the stored one. It
hashes the submitted password and compares that hash with the stored one using
{{{}equals(){}}}. Fineract uses bcrypt, which adds a new random salt every time
it hashes, so the two hashes are never equal and the method always returns
true. It's the same root cause as FINERACT-2956, just in a different method,
and PR #6670 doesn't touch it.
Steps to reproduce, with the reuse check
({{{}password-reuse-check-history-count{}}}) turned off:
1. Create a user with password A on {{{}POST /v1/users{}}}:
!image-2026-10-16-19-44-54-613.png!
I got user id 5 back:
!image-2026-10-16-19-45-27-140.png!
2. Set the user's last-updated date to a date in the past, so it's easy to see
if it changes, and check what's stored:
{code:sql}
UPDATE m_appuser SET last_time_password_updated = DATE '2020-01-01' WHERE id =
5;
SELECT password, last_time_password_updated FROM m_appuser WHERE id = 5;
SELECT id, removal_date FROM m_appuser_previous_password WHERE user_id = 5;
{code}
!image-2026-10-16-19-47-52-513.png!
3. Send the same password A again on {{{}POST /v1/users/5/pwd{}}}:
!image-2026-10-16-19-50-04-971.png!
Response:
!image-2026-10-16-19-50-23-067.png!
4. Run the two SELECTs from step 2 again:
!image-2026-10-16-19-56-22-322.png!
!image-2026-10-16-19-54-04-455.png!
I did steps 2 to 4 twice, and each time one more row was added to the history.
The request returns 200 with {{"password": true}} under {{{}changes{}}}. In the
database, the hash is different, {{last_time_password_updated}} has moved from
2020-01-01 to today, and there's a new history row with the same password in it.
Since the password didn't actually change, I'd expect none of this to happen:
no password change in the response, and the stored hash, the history and
{{last_time_password_updated}} left as they were.
was:
If you set a user's password to the one they already have, Fineract treats it
as a real change. It saves a new hash, adds a row to the password history,
updates the password's last-updated date and returns {{"password": true}} in
the response. This only happens when the password reuse check is off, which is
the default. When the check is on, the fix in FINERACT-2956 rejects the current
password before it gets this far.
The problem is in {{JsonCommand.isChangeInPasswordParameterNamed}}, which
checks whether the submitted password is different from the stored one. It
hashes the submitted password and compares that hash with the stored one using
{{equals()}}. Fineract uses bcrypt, which adds a new random salt every time it
hashes, so the two hashes are never equal and the method always returns true.
It's the same root cause as FINERACT-2956, just in a different method, and PR
#6670 doesn't touch it.
Steps to reproduce, with the reuse check
({{password-reuse-check-history-count}}) turned off:
1. Create a user with password A on {{POST /v1/users}}:
!image-2026-10-16-19-44-54-613.png!
I got user id 5 back:
!image-2026-10-16-19-45-27-140.png!
2. Set the user's last-updated date to a date in the past, so it's easy to see
if it changes, and check what's stored:
{code:sql}
UPDATE m_appuser SET last_time_password_updated = DATE '2020-01-01' WHERE id =
5;
SELECT password, last_time_password_updated FROM m_appuser WHERE id = 5;
SELECT id, removal_date FROM m_appuser_previous_password WHERE user_id = 5;
{code}
!image-2026-10-16-19-47-52-513.png!
3. Send the same password A again on {{POST /v1/users/5/pwd}}:
!image-2026-10-16-19-50-04-971.png!
Response:
!image-2026-10-16-19-50-23-067.png!
4. Run the two SELECTs from step 2 again:
!image-2026-10-16-19-56-22-322.png!
!image-2026-10-16-19-54-04-455.png!
I did steps 2 to 4 twice, and each time one more row was added to the history.
The request returns 200 with {{"password": true}} under {{changes}}. In the
database, the hash is different, {{last_time_password_updated}} has moved from
2020-01-01 to today, and there's a new history row with the same password in it.
Since the password didn't actually change, I'd expect none of this to happen:
no password change in the response, and the stored hash, the history and
{{last_time_password_updated}} left as they were.
> Submitting the current password is always treated as a password change
> ----------------------------------------------------------------------
>
> Key: FINERACT-2961
> URL: https://issues.apache.org/jira/browse/FINERACT-2961
> Project: Apache Fineract
> Issue Type: Bug
> Components: Security, User Management
> Affects Versions: 1.15.0
> Environment: FINERACT-2956 branch @ 1a9b0b2920 (PR #6670, on top of
> develop @ dd48e89d37)
> Reporter: Mandib Chaulagain
> Assignee: Mandib Chaulagain
> Priority: Minor
> Fix For: 1.17.0
>
> Attachments: image-2026-10-16-19-44-54-613.png,
> image-2026-10-16-19-45-27-140.png, image-2026-10-16-19-47-52-513.png,
> image-2026-10-16-19-50-04-971.png, image-2026-10-16-19-50-23-067.png,
> image-2026-10-16-19-54-04-455.png, image-2026-10-16-19-56-22-322.png
>
>
> If you set a user's password to the one they already have, Fineract treats it
> as a real change. It saves a new hash, adds a row to the password history,
> updates the password's last-updated date and returns {{"password": true}} in
> the response. This happens whether the password reuse check is on or off,
> because the reuse check only looks at the password history, and the current
> password only goes into the history once it has been replaced.
> The problem is in {{{}JsonCommand.isChangeInPasswordParameterNamed{}}}, which
> checks whether the submitted password is different from the stored one. It
> hashes the submitted password and compares that hash with the stored one
> using {{{}equals(){}}}. Fineract uses bcrypt, which adds a new random salt
> every time it hashes, so the two hashes are never equal and the method always
> returns true. It's the same root cause as FINERACT-2956, just in a different
> method, and PR #6670 doesn't touch it.
> Steps to reproduce, with the reuse check
> ({{{}password-reuse-check-history-count{}}}) turned off:
> 1. Create a user with password A on {{{}POST /v1/users{}}}:
> !image-2026-10-16-19-44-54-613.png!
> I got user id 5 back:
> !image-2026-10-16-19-45-27-140.png!
> 2. Set the user's last-updated date to a date in the past, so it's easy to
> see if it changes, and check what's stored:
> {code:sql}
> UPDATE m_appuser SET last_time_password_updated = DATE '2020-01-01' WHERE id
> = 5;
> SELECT password, last_time_password_updated FROM m_appuser WHERE id = 5;
> SELECT id, removal_date FROM m_appuser_previous_password WHERE user_id = 5;
> {code}
> !image-2026-10-16-19-47-52-513.png!
> 3. Send the same password A again on {{{}POST /v1/users/5/pwd{}}}:
> !image-2026-10-16-19-50-04-971.png!
> Response:
> !image-2026-10-16-19-50-23-067.png!
> 4. Run the two SELECTs from step 2 again:
> !image-2026-10-16-19-56-22-322.png!
> !image-2026-10-16-19-54-04-455.png!
> I did steps 2 to 4 twice, and each time one more row was added to the history.
> The request returns 200 with {{"password": true}} under {{{}changes{}}}. In
> the database, the hash is different, {{last_time_password_updated}} has moved
> from 2020-01-01 to today, and there's a new history row with the same
> password in it.
> Since the password didn't actually change, I'd expect none of this to happen:
> no password change in the response, and the stored hash, the history and
> {{last_time_password_updated}} left as they were.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)