diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/UserPendingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/UserPendingWritePlatformServiceImpl.java index 16d140cb1..f23767e00 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/UserPendingWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/UserPendingWritePlatformServiceImpl.java @@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRu import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.infrastructure.core.service.PlatformEmailService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; @@ -82,6 +83,7 @@ public class UserPendingWritePlatformServiceImpl implements UserPendingWritePlat private final PasswordEncoder passwordEncoder; private final UserDataValidator fromApiJsonDeserializer; private final FromJsonHelper fromApiJsonHelper; + private final PlatformEmailService emailService; @Override @Transactional @@ -293,13 +295,40 @@ public class UserPendingWritePlatformServiceImpl implements UserPendingWritePlat appUser.updatePasswordResetRequired(true); } - // Set pre-hashed password + // AppUser.fromJson() already generated a random plaintext password here when the maker ticked + // "send password to email" (it ignores whatever password the maker typed in that case) -- the + // field still holds that plaintext at this point, since nothing has encoded it yet. If the + // maker instead supplied their own password, request.getPasswordHash() carries the bcrypt + // hash submitCreateUser() produced from it; that plaintext was redacted to "[PROTECTED]" + // immediately and is gone for good, so there is nothing to email in that case. + final boolean sendPasswordToEmail = Boolean.TRUE.equals(replay.booleanObjectValueOfParameterNamed("sendPasswordToEmail")); + final String unencodedPassword = appUser.getPassword(); + if (request.getPasswordHash() != null) { + // Maker-chosen password: already hashed, used as-is. appUser.updatePassword(request.getPasswordHash()); + } else { + // Auto-generated password (the normal "send password to email" case): this service's own + // generic PasswordEncoder, not the salted PlatformPasswordEncoder the live create-user flow + // uses -- consistent with how the maker-chosen-password branch above and submitCreateUser() + // already hash a password in this file. Encoding it here is not optional: AppUser's + // constructor stores whatever string it is given verbatim (see AppUser(Office, User, ...)), + // so skipping this would persist the plaintext password straight into m_appuser.password. + appUser.updatePassword(this.passwordEncoder.encode(unencodedPassword)); } this.appUserRepository.saveAndFlush(appUser); + // Mirrors JpaUserDomainService.create(): the welcome email the live create-user flow sends is + // the one thing this parallel, pending-request flow never did -- sendPasswordToEmail was + // accepted and stored in the payload but nothing here ever acted on it, so a user created this + // way never received their password. Only fires for the auto-generated password: there is no + // plaintext to send for a maker-chosen one, per the comment above. + if (sendPasswordToEmail && request.getPasswordHash() == null) { + this.emailService.sendToUserAccount(userOffice.getName(), appUser.getFirstname(), appUser.getEmail(), appUser.getUsername(), + unencodedPassword); + } + // MFA Enrollment final String otpDeliveryMethod = replay.stringValueOfParameterNamed("otpDeliveryMethod"); if (otpDeliveryMethod != null) {