merge feature/update_code and feature/Backend-MFA-Product branch code
This commit is contained in:
+1
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.configuration.api;
|
||||
public final class GlobalConfigurationConstants {
|
||||
|
||||
public static final String MAKER_CHECKER = "maker-checker";
|
||||
public static final String MFA_FACTOR_ENFORCEMENT = "mfa-factor-enforcement";
|
||||
public static final String AMAZON_S3 = "amazon-s3";
|
||||
public static final String RESCHEDULE_FUTURE_REPAYMENTS = "reschedule-future-repayments";
|
||||
public static final String RESCHEDULE_REPAYMENTS_ON_HOLIDAYS = "reschedule-repayments-on-holidays";
|
||||
|
||||
+7
@@ -26,6 +26,13 @@ public interface ConfigurationDomainService {
|
||||
|
||||
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
||||
|
||||
/**
|
||||
* Global kill-switch for MFA-factor-backed login enforcement. When true, users with at least
|
||||
* one verified, non-disabled row in {@code m_user_mfa_factor} must complete a 2FA challenge
|
||||
* after password authentication. Default: false (off, safe rollback).
|
||||
*/
|
||||
boolean isMfaFactorEnforcementEnabled();
|
||||
|
||||
List<String> getAllowedLoanStatusesForExternalAssetTransfer();
|
||||
|
||||
List<String> getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer();
|
||||
|
||||
+59
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
@@ -35,6 +37,11 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
|
||||
@Table(name = "m_client_identifier", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "document_type_id", "document_key" }, name = "unique_identifier_key"),
|
||||
@UniqueConstraint(columnNames = { "client_id", "document_key", "active" }, name = "unique_active_client_identifier") })
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
public class ClientIdentifier extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@@ -57,6 +64,22 @@ public class ClientIdentifier extends AbstractAuditableWithUTCDateTimeCustom<Lon
|
||||
@Column(name = "active")
|
||||
private Integer active;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = Boolean.TRUE;
|
||||
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private Boolean isDeleted = Boolean.FALSE;
|
||||
|
||||
@Column(name = "deleted_by")
|
||||
private Long deletedBy;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private java.time.OffsetDateTime deletedAt;
|
||||
|
||||
@jakarta.persistence.Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Long version = 0L;
|
||||
|
||||
public static ClientIdentifier fromJson(final Client client, final CodeValue documentType, final JsonCommand command) {
|
||||
final String documentKey = command.stringValueOfParameterNamed("documentKey");
|
||||
final String description = command.stringValueOfParameterNamed("description");
|
||||
@@ -127,4 +150,40 @@ public class ClientIdentifier extends AbstractAuditableWithUTCDateTimeCustom<Lon
|
||||
public Long documentTypeId() {
|
||||
return this.documentType.getId();
|
||||
}
|
||||
|
||||
public Boolean getIsActive() {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
public void setIsActive(Boolean isActive) {
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
public Boolean getIsDeleted() {
|
||||
return this.isDeleted;
|
||||
}
|
||||
|
||||
public void setIsDeleted(Boolean isDeleted) {
|
||||
this.isDeleted = isDeleted;
|
||||
}
|
||||
|
||||
public Long getDeletedBy() {
|
||||
return this.deletedBy;
|
||||
}
|
||||
|
||||
public void setDeletedBy(Long deletedBy) {
|
||||
this.deletedBy = deletedBy;
|
||||
}
|
||||
|
||||
public java.time.OffsetDateTime getDeletedAt() {
|
||||
return this.deletedAt;
|
||||
}
|
||||
|
||||
public void setDeletedAt(java.time.OffsetDateTime deletedAt) {
|
||||
this.deletedAt = deletedAt;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
@@ -46,6 +48,11 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
|
||||
@Entity
|
||||
@Table(name = "m_client_kyc", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "client_id" }, name = "uq_m_client_kyc_client_id") })
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@@ -777,4 +784,56 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public String getForm15Fy() {
|
||||
return form15Fy;
|
||||
}
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = Boolean.TRUE;
|
||||
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private Boolean isDeleted = Boolean.FALSE;
|
||||
|
||||
@Column(name = "deleted_by")
|
||||
private Long deletedBy;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private java.time.OffsetDateTime deletedAt;
|
||||
|
||||
@jakarta.persistence.Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Long version = 0L;
|
||||
|
||||
public Boolean getIsActive() {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
public void setIsActive(Boolean isActive) {
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
public Boolean getIsDeleted() {
|
||||
return this.isDeleted;
|
||||
}
|
||||
|
||||
public void setIsDeleted(Boolean isDeleted) {
|
||||
this.isDeleted = isDeleted;
|
||||
}
|
||||
|
||||
public Long getDeletedBy() {
|
||||
return this.deletedBy;
|
||||
}
|
||||
|
||||
public void setDeletedBy(Long deletedBy) {
|
||||
this.deletedBy = deletedBy;
|
||||
}
|
||||
|
||||
public java.time.OffsetDateTime getDeletedAt() {
|
||||
return this.deletedAt;
|
||||
}
|
||||
|
||||
public void setDeletedAt(java.time.OffsetDateTime deletedAt) {
|
||||
this.deletedAt = deletedAt;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-2
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -39,6 +41,11 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_client_nominee")
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -60,8 +67,21 @@ public class ClientNominee extends AbstractAuditableWithUTCDateTimeCustom<Long>
|
||||
@Column(name = "percentage_share", precision = 5, scale = 2)
|
||||
private BigDecimal percentageShare;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private Boolean isActive;
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = Boolean.TRUE;
|
||||
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private Boolean isDeleted = Boolean.FALSE;
|
||||
|
||||
@Column(name = "deleted_by")
|
||||
private Long deletedBy;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private java.time.OffsetDateTime deletedAt;
|
||||
|
||||
@jakarta.persistence.Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Long version = 0L;
|
||||
|
||||
/** If nominee is a minor, guardian details */
|
||||
@Column(name = "nominee_is_minor")
|
||||
|
||||
+6
-6
@@ -26,21 +26,21 @@ import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface ClientNomineeRepository extends JpaRepository<ClientNominee, Long> {
|
||||
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId ORDER BY n.id")
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId AND n.isDeleted = false ORDER BY n.id")
|
||||
List<ClientNominee> findAllByClientId(@Param("clientId") Long clientId);
|
||||
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId AND n.id = :nomineeId")
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId AND n.id = :nomineeId AND n.isDeleted = false")
|
||||
Optional<ClientNominee> findByClientIdAndId(@Param("clientId") Long clientId, @Param("nomineeId") Long nomineeId);
|
||||
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId AND n.isActive = true")
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId AND n.isActive = true AND n.isDeleted = false")
|
||||
List<ClientNominee> findActiveByClientId(@Param("clientId") Long clientId);
|
||||
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.accountNo = :accountNo AND n.accountType = :accountType AND n.isActive = true")
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.accountNo = :accountNo AND n.accountType = :accountType AND n.isActive = true AND n.isDeleted = false")
|
||||
List<ClientNominee> findActiveByAccountNoAndAccountType(@Param("accountNo") String accountNo, @Param("accountType") String accountType);
|
||||
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId AND n.accountNo = :accountNo AND n.accountType = :accountType ORDER BY n.id")
|
||||
@Query("SELECT n FROM ClientNominee n WHERE n.client.id = :clientId AND n.accountNo = :accountNo AND n.accountType = :accountType AND n.isDeleted = false ORDER BY n.id")
|
||||
List<ClientNominee> findByClientIdAndAccountNoAndAccountType(@Param("clientId") Long clientId, @Param("accountNo") String accountNo, @Param("accountType") String accountType);
|
||||
|
||||
@Query("SELECT COUNT(n) FROM ClientNominee n WHERE n.accountNo = :accountNo AND n.accountType = :accountType")
|
||||
@Query("SELECT COUNT(n) FROM ClientNominee n WHERE n.accountNo = :accountNo AND n.accountType = :accountType AND n.isDeleted = false")
|
||||
long countByAccountNoAndAccountType(@Param("accountNo") String accountNo, @Param("accountType") String accountType);
|
||||
}
|
||||
|
||||
+7
@@ -54,4 +54,11 @@ public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecif
|
||||
@Modifying
|
||||
@Query("UPDATE Client client SET client.imageId = null WHERE client.id = :clientId")
|
||||
void removeImageId(@Param("clientId") Long clientId);
|
||||
|
||||
/**
|
||||
* Count clients whose cif_number begins with the given prefix. Used by the CIF number generator
|
||||
* (TTYYYYNNNNNN format) to compute the next sequence within a given (type, year) bucket.
|
||||
*/
|
||||
@Query("SELECT COUNT(c) FROM Client c WHERE c.cifNumber LIKE CONCAT(:prefix, '%')")
|
||||
long countByCifNumberPrefix(@Param("prefix") String prefix);
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,5 +23,5 @@ import org.springframework.data.domain.Pageable;
|
||||
|
||||
public interface SearchingClientRepository {
|
||||
|
||||
Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy);
|
||||
Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy, boolean activeOnly);
|
||||
}
|
||||
|
||||
+7
-1
@@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.core.jpa.CriteriaQueryFactory;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientIdentifier;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientStatus;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
@@ -51,7 +52,7 @@ public class SearchingClientRepositoryImpl implements SearchingClientRepository
|
||||
private final CriteriaQueryFactory criteriaQueryFactory;
|
||||
|
||||
@Override
|
||||
public Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy) {
|
||||
public Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy, boolean activeOnly) {
|
||||
/*
|
||||
* this whole thing can be replaced with Spring Data JPA 3+ with a findBy(Specification, Pageable) call but at
|
||||
* this point the upgrade is too costly
|
||||
@@ -79,6 +80,11 @@ public class SearchingClientRepositoryImpl implements SearchingClientRepository
|
||||
cb.like(r.get("displayName"), searchLikeValue), cb.like(r.get("externalId"), searchLikeValue),
|
||||
cb.like(r.get("mobileNo"), searchLikeValue), cb.like(identity.get("documentKey"), searchLikeValue)));
|
||||
|
||||
// Linking flows (nominee, family-member, guardian, signatory) only see ACTIVE clients
|
||||
if (activeOnly) {
|
||||
predicates.add(cb.equal(r.get("status"), ClientStatus.ACTIVE.getValue()));
|
||||
}
|
||||
|
||||
return cb.and(predicates.toArray(new Predicate[0]));
|
||||
};
|
||||
criteriaQueryFactory.applySpecificationToCriteria(root, spec, query);
|
||||
|
||||
+7
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.entityclassification.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -38,6 +40,11 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_client_eou")
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+7
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.entityclassification.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -34,6 +36,11 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_client_msme")
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+7
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.entityclassification.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -38,6 +40,11 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_client_mudra_borrower")
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+7
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.entityclassification.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -36,6 +38,11 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_client_non_priority_sector")
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+7
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.entityclassification.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -38,6 +40,11 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_client_priority_sector")
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+7
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.entityclassification.domain;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -38,6 +40,11 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_client_startup_india")
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "createdDate", column = @Column(name = "created_at", updatable = false, nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "modified_by", nullable = false)),
|
||||
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "modified_on", nullable = false))
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+17
-9
@@ -40,6 +40,7 @@ public final class AppUserData {
|
||||
private final String firstname;
|
||||
private final String lastname;
|
||||
private final String email;
|
||||
private final String mobileNo;
|
||||
private final Boolean passwordNeverExpires;
|
||||
|
||||
// import fields
|
||||
@@ -76,6 +77,7 @@ public final class AppUserData {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.email = email;
|
||||
this.mobileNo = null;
|
||||
this.passwordNeverExpires = passwordNeverExpires;
|
||||
this.roles = roleIds;
|
||||
this.sendPasswordToEmail = sendPasswordToEmail;
|
||||
@@ -91,30 +93,31 @@ public final class AppUserData {
|
||||
}
|
||||
|
||||
public static AppUserData template(final AppUserData user, final Collection<OfficeData> officesForDropdown) {
|
||||
return new AppUserData(user.id, user.username, user.email, user.officeId, user.officeName, user.firstname, user.lastname,
|
||||
user.availableRoles, user.selfServiceRoles, user.selectedRoles, officesForDropdown, user.staff, user.passwordNeverExpires,
|
||||
user.isSelfServiceUser);
|
||||
return new AppUserData(user.id, user.username, user.email, user.mobileNo, user.officeId, user.officeName, user.firstname,
|
||||
user.lastname, user.availableRoles, user.selfServiceRoles, user.selectedRoles, officesForDropdown, user.staff,
|
||||
user.passwordNeverExpires, user.isSelfServiceUser);
|
||||
}
|
||||
|
||||
public static AppUserData template(final Collection<OfficeData> offices, final Collection<RoleData> availableRoles,
|
||||
final Collection<RoleData> selfServiceRoles) {
|
||||
return new AppUserData(null, null, null, null, null, null, null, availableRoles, selfServiceRoles, null, offices, null, null, null);
|
||||
return new AppUserData(null, null, null, null, null, null, null, null, availableRoles, selfServiceRoles, null, offices, null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
public static AppUserData dropdown(final Long id, final String username) {
|
||||
return new AppUserData(id, username, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
return new AppUserData(id, username, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public static AppUserData instance(final Long id, final String username, final String email, final Long officeId,
|
||||
public static AppUserData instance(final Long id, final String username, final String email, final String mobileNo, final Long officeId,
|
||||
final String officeName, final String firstname, final String lastname, final Collection<RoleData> availableRoles,
|
||||
final Collection<RoleData> selfServiceRoles, final Collection<RoleData> selectedRoles, final StaffData staff,
|
||||
final Boolean passwordNeverExpire, final Boolean isSelfServiceUser) {
|
||||
return new AppUserData(id, username, email, officeId, officeName, firstname, lastname, availableRoles, selfServiceRoles,
|
||||
return new AppUserData(id, username, email, mobileNo, officeId, officeName, firstname, lastname, availableRoles, selfServiceRoles,
|
||||
selectedRoles, null, staff, passwordNeverExpire, isSelfServiceUser);
|
||||
}
|
||||
|
||||
private AppUserData(final Long id, final String username, final String email, final Long officeId, final String officeName,
|
||||
final String firstname, final String lastname, final Collection<RoleData> availableRoles,
|
||||
private AppUserData(final Long id, final String username, final String email, final String mobileNo, final Long officeId,
|
||||
final String officeName, final String firstname, final String lastname, final Collection<RoleData> availableRoles,
|
||||
final Collection<RoleData> selfServiceRoles, final Collection<RoleData> selectedRoles,
|
||||
final Collection<OfficeData> allowedOffices, final StaffData staff, final Boolean passwordNeverExpire,
|
||||
final Boolean isSelfServiceUser) {
|
||||
@@ -125,6 +128,7 @@ public final class AppUserData {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.email = email;
|
||||
this.mobileNo = mobileNo;
|
||||
this.allowedOffices = allowedOffices;
|
||||
this.availableRoles = availableRoles;
|
||||
this.selfServiceRoles = selfServiceRoles;
|
||||
@@ -134,6 +138,10 @@ public final class AppUserData {
|
||||
this.isSelfServiceUser = isSelfServiceUser;
|
||||
}
|
||||
|
||||
public String getMobileNo() {
|
||||
return this.mobileNo;
|
||||
}
|
||||
|
||||
public boolean hasIdentifyOf(final Long createdById) {
|
||||
return this.id.equals(createdById);
|
||||
}
|
||||
|
||||
+23
-2
@@ -134,6 +134,17 @@ public class AppUser extends AbstractPersistableCustom<Long> implements Platform
|
||||
@Column(name = "password_reset_required", nullable = false)
|
||||
private boolean passwordResetRequired;
|
||||
|
||||
@Column(name = "mobile_no", length = 16)
|
||||
private String mobileNo;
|
||||
|
||||
public String getMobileNo() {
|
||||
return this.mobileNo;
|
||||
}
|
||||
|
||||
public void setMobileNo(final String mobileNo) {
|
||||
this.mobileNo = mobileNo;
|
||||
}
|
||||
|
||||
public boolean isPasswordResetRequired() {
|
||||
return this.passwordResetRequired;
|
||||
}
|
||||
@@ -174,11 +185,14 @@ public class AppUser extends AbstractPersistableCustom<Long> implements Platform
|
||||
final String email = command.stringValueOfParameterNamed("email");
|
||||
final String firstname = command.stringValueOfParameterNamed("firstname");
|
||||
final String lastname = command.stringValueOfParameterNamed("lastname");
|
||||
final String mobileNo = command.stringValueOfParameterNamed("mobileNo");
|
||||
|
||||
final boolean isSelfServiceUser = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER);
|
||||
|
||||
return new AppUser(userOffice, user, allRoles, email, firstname, lastname, linkedStaff, passwordNeverExpire, isSelfServiceUser,
|
||||
clients, cannotChangePassword);
|
||||
final AppUser appUser = new AppUser(userOffice, user, allRoles, email, firstname, lastname, linkedStaff, passwordNeverExpire,
|
||||
isSelfServiceUser, clients, cannotChangePassword);
|
||||
appUser.setMobileNo(mobileNo);
|
||||
return appUser;
|
||||
}
|
||||
|
||||
protected AppUser() {
|
||||
@@ -321,6 +335,13 @@ public class AppUser extends AbstractPersistableCustom<Long> implements Platform
|
||||
this.email = newValue;
|
||||
}
|
||||
|
||||
final String mobileNoParamName = "mobileNo";
|
||||
if (command.isChangeInStringParameterNamed(mobileNoParamName, this.mobileNo)) {
|
||||
final String newValue = command.stringValueOfParameterNamed(mobileNoParamName);
|
||||
actualChanges.put(mobileNoParamName, newValue);
|
||||
this.mobileNo = newValue;
|
||||
}
|
||||
|
||||
final String passwordNeverExpire = "passwordNeverExpires";
|
||||
|
||||
if (command.hasParameter(passwordNeverExpire)
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.mfa.domain;
|
||||
|
||||
public enum MfaFactorType {
|
||||
SMS,
|
||||
EMAIL,
|
||||
TOTP,
|
||||
TOKEN;
|
||||
|
||||
public static MfaFactorType fromString(final String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
for (final MfaFactorType t : values()) {
|
||||
if (t.name().equalsIgnoreCase(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.mfa.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
/**
|
||||
* One row per (AppUser, factor type). Soft-disable via {@link #disabledOn}.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_user_mfa_factor", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "appuser_id", "factor_type" }, name = "uq_user_factor") })
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class UserMfaFactor extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "appuser_id", nullable = false)
|
||||
private AppUser appUser;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "factor_type", length = 20, nullable = false)
|
||||
private MfaFactorType factorType;
|
||||
|
||||
@Column(name = "destination", length = 255)
|
||||
private String destination;
|
||||
|
||||
@Column(name = "secret", length = 64)
|
||||
private String secret;
|
||||
|
||||
@Column(name = "serial", length = 64)
|
||||
private String serial;
|
||||
|
||||
@Column(name = "verified", nullable = false)
|
||||
private boolean verified;
|
||||
|
||||
@Column(name = "verified_on")
|
||||
private OffsetDateTime verifiedOn;
|
||||
|
||||
@Column(name = "otp_log_id")
|
||||
private Long otpLogId;
|
||||
|
||||
@Column(name = "created_on", nullable = false, updatable = false)
|
||||
private OffsetDateTime createdOn;
|
||||
|
||||
@Column(name = "disabled_on")
|
||||
private OffsetDateTime disabledOn;
|
||||
|
||||
@Column(name = "disabled_by")
|
||||
private Long disabledBy;
|
||||
|
||||
@PrePersist
|
||||
private void prePersist() {
|
||||
if (createdOn == null) {
|
||||
createdOn = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.mfa.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserMfaFactorRepository extends JpaRepository<UserMfaFactor, Long> {
|
||||
|
||||
List<UserMfaFactor> findByAppUser_IdAndDisabledOnIsNull(Long userId);
|
||||
|
||||
/** Returns every factor for the user — active and disabled — newest first. */
|
||||
List<UserMfaFactor> findByAppUser_IdOrderByIdDesc(Long userId);
|
||||
|
||||
Optional<UserMfaFactor> findByAppUser_IdAndFactorType(Long userId, MfaFactorType type);
|
||||
|
||||
/**
|
||||
* Returns true if the user has at least one verified, non-disabled MFA factor enrolled.
|
||||
* Used by the auth flow to decide whether to gate the login with a 2FA challenge.
|
||||
*/
|
||||
boolean existsByAppUser_IdAndVerifiedTrueAndDisabledOnIsNull(Long userId);
|
||||
}
|
||||
Reference in New Issue
Block a user