From 552c5f4b04689a90b604fdc4baa5b5288267cfdc Mon Sep 17 00:00:00 2001 From: airajena Date: Fri, 23 Jan 2026 01:00:38 +0530 Subject: [PATCH] FINERACT-2005: Prohibit password re-use with configurable global setting --- .../api/GlobalConfigurationConstants.java | 1 + .../domain/ConfigurationDomainService.java | 2 ++ .../domain/ConfigurationDomainServiceJpa.java | 11 ++++++ ...WritePlatformServiceJpaRepositoryImpl.java | 23 ++++++++---- .../UserAdministrationConfiguration.java | 5 +-- .../db/changelog/tenant/changelog-tenant.xml | 1 + ...ion_password_reuse_check_history_count.xml | 35 +++++++++++++++++++ 7 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0208_add_configuration_password_reuse_check_history_count.xml diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java index 979eea042ce..ce3cec1dcd6 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java @@ -79,6 +79,7 @@ public final class GlobalConfigurationConstants { public static final String ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY = "outstanding-interest-calculation-strategy-for-external-asset-transfer"; public static final String ALLOWED_LOAN_STATUSES_FOR_EXTERNAL_ASSET_TRANSFER = "allowed-loan-statuses-for-external-asset-transfer"; public static final String ALLOWED_LOAN_STATUSES_OF_DELAYED_SETTLEMENT_FOR_EXTERNAL_ASSET_TRANSFER = "allowed-loan-statuses-of-delayed-settlement-for-external-asset-transfer"; + public static final String PASSWORD_REUSE_CHECK_HISTORY_COUNT = "password-reuse-check-history-count"; private GlobalConfigurationConstants() {} } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index e7a98fb2cc2..83c21afd2b1 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -151,4 +151,6 @@ public interface ConfigurationDomainService { boolean isImmediateChargeAccrualPostMaturityEnabled(); String getAssetOwnerTransferOustandingInterestStrategy(); + + Integer getPasswordReuseRestrictionCount(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 26b48b2f067..94695f2f203 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -548,4 +548,15 @@ public String getAssetOwnerTransferOustandingInterestStrategy() { return getGlobalConfigurationPropertyData( GlobalConfigurationConstants.ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY).getStringValue(); } + + @Override + public Integer getPasswordReuseRestrictionCount() { + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData( + GlobalConfigurationConstants.PASSWORD_REUSE_CHECK_HISTORY_COUNT); + if (!property.isEnabled()) { + return null; + } + Long value = property.getValue(); + return value != null && value > 0 ? value.intValue() : 0; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java index 053f47f5e29..9c5ec5c80d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -48,7 +49,6 @@ import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; -import org.apache.fineract.useradministration.api.AppUserApiConstant; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUserPreviousPassword; import org.apache.fineract.useradministration.domain.AppUserPreviousPasswordRepository; @@ -83,6 +83,7 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit private final AppUserPreviousPasswordRepository appUserPreviewPasswordRepository; private final StaffRepositoryWrapper staffRepositoryWrapper; private final ClientRepositoryWrapper clientRepositoryWrapper; + private final ConfigurationDomainService configurationDomainService; @Override @Transactional @@ -269,12 +270,20 @@ private AppUserPreviousPassword getCurrentPasswordToSaveAsPreview(final AppUser AppUserPreviousPassword currentPasswordToSaveAsPreview = null; if (passWordEncodedValue != null) { - PageRequest pageRequest = PageRequest.of(0, AppUserApiConstant.numberOfPreviousPasswords, Sort.Direction.DESC, "removalDate"); - final List nLastUsedPasswords = this.appUserPreviewPasswordRepository.findByUserId(user.getId(), - pageRequest); - for (AppUserPreviousPassword aPreviewPassword : nLastUsedPasswords) { - if (aPreviewPassword.getPassword().equals(passWordEncodedValue)) { - throw new PasswordPreviouslyUsedException(); + final Integer passwordReuseRestrictionCount = this.configurationDomainService.getPasswordReuseRestrictionCount(); + if (passwordReuseRestrictionCount != null) { + List previousPasswords; + if (passwordReuseRestrictionCount == 0) { + previousPasswords = this.appUserPreviewPasswordRepository.findByUserId(user.getId(), + PageRequest.of(0, Integer.MAX_VALUE, Sort.Direction.DESC, "removalDate")); + } else { + PageRequest pageRequest = PageRequest.of(0, passwordReuseRestrictionCount, Sort.Direction.DESC, "removalDate"); + previousPasswords = this.appUserPreviewPasswordRepository.findByUserId(user.getId(), pageRequest); + } + for (AppUserPreviousPassword aPreviewPassword : previousPasswords) { + if (aPreviewPassword.getPassword().equals(passWordEncodedValue)) { + throw new PasswordPreviouslyUsedException(); + } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/starter/UserAdministrationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/starter/UserAdministrationConfiguration.java index f092ee0e385..9ef25cac86e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/starter/UserAdministrationConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/starter/UserAdministrationConfiguration.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.useradministration.starter; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; @@ -75,10 +76,10 @@ public AppUserWritePlatformService appUserWritePlatformService(PlatformSecurityC PlatformPasswordEncoder platformPasswordEncoder, AppUserRepository appUserRepository, OfficeRepositoryWrapper officeRepositoryWrapper, RoleRepository roleRepository, UserDataValidator fromApiJsonDeserializer, AppUserPreviousPasswordRepository appUserPreviewPasswordRepository, StaffRepositoryWrapper staffRepositoryWrapper, - ClientRepositoryWrapper clientRepositoryWrapper) { + ClientRepositoryWrapper clientRepositoryWrapper, ConfigurationDomainService configurationDomainService) { return new AppUserWritePlatformServiceJpaRepositoryImpl(context, userDomainService, platformPasswordEncoder, appUserRepository, officeRepositoryWrapper, roleRepository, fromApiJsonDeserializer, appUserPreviewPasswordRepository, staffRepositoryWrapper, - clientRepositoryWrapper); + clientRepositoryWrapper, configurationDomainService); } @Bean diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index ffab7d77f6b..4b3a6ed8cb8 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -226,4 +226,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0208_add_configuration_password_reuse_check_history_count.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0208_add_configuration_password_reuse_check_history_count.xml new file mode 100644 index 00000000000..3ade2313a4a --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0208_add_configuration_password_reuse_check_history_count.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + +