FINERACT-2185: User management improvement
This commit is contained in:
committed by
Adam Saghy
parent
8d83b5547c
commit
79c006774e
+14
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.commands.domain;
|
package org.apache.fineract.commands.domain;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -41,8 +42,21 @@ public enum CommandProcessingResultType {
|
|||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public static CommandProcessingResultType fromInt(final Integer value) {
|
public static CommandProcessingResultType fromInt(final Integer value) {
|
||||||
CommandProcessingResultType transactionType = BY_ID.get(value);
|
CommandProcessingResultType transactionType = BY_ID.get(value);
|
||||||
return transactionType == null ? INVALID : transactionType;
|
return transactionType == null ? INVALID : transactionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isProcessed() {
|
||||||
|
return this == PROCESSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAwaitingApproval() {
|
||||||
|
return this == AWAITING_APPROVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRejected() {
|
||||||
|
return this == REJECTED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -136,9 +137,11 @@ public class CommandSource extends AbstractPersistableCustom<Long> {
|
|||||||
@Column(name = "loan_external_id", length = 100)
|
@Column(name = "loan_external_id", length = 100)
|
||||||
private ExternalId loanExternalId;
|
private ExternalId loanExternalId;
|
||||||
|
|
||||||
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker,
|
@Column(name = "is_sanitized", nullable = false)
|
||||||
String idempotencyKey, Integer status) {
|
private boolean sanitized;
|
||||||
|
|
||||||
|
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker,
|
||||||
|
String idempotencyKey, Integer status, boolean sanitized) {
|
||||||
return CommandSource.builder() //
|
return CommandSource.builder() //
|
||||||
.actionName(wrapper.actionName()) //
|
.actionName(wrapper.actionName()) //
|
||||||
.entityName(wrapper.entityName()) //
|
.entityName(wrapper.entityName()) //
|
||||||
@@ -159,31 +162,52 @@ public class CommandSource extends AbstractPersistableCustom<Long> {
|
|||||||
.transactionId(command.getTransactionId()) //
|
.transactionId(command.getTransactionId()) //
|
||||||
.creditBureauId(command.getCreditBureauId()) //
|
.creditBureauId(command.getCreditBureauId()) //
|
||||||
.organisationCreditBureauId(command.getOrganisationCreditBureauId()) //
|
.organisationCreditBureauId(command.getOrganisationCreditBureauId()) //
|
||||||
.loanExternalId(command.getLoanExternalId()).build(); //
|
.loanExternalId(command.getLoanExternalId()).sanitized(sanitized).build(); //
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPermissionCode() {
|
public String getPermissionCode() {
|
||||||
return this.actionName + "_" + this.entityName;
|
return this.actionName + "_" + this.entityName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markAsAwaitingApproval() {
|
@NotNull
|
||||||
this.status = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
|
public CommandProcessingResultType getStatusEnum() {
|
||||||
|
return CommandProcessingResultType.fromInt(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMarkedAsAwaitingApproval() {
|
public void setStatus(@NotNull CommandProcessingResultType status) {
|
||||||
return this.status.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue());
|
this.status = status.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markAsAwaitingApproval() {
|
||||||
|
setStatus(CommandProcessingResultType.AWAITING_APPROVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAwaitingApproval() {
|
||||||
|
return getStatusEnum().isAwaitingApproval();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProcessed() {
|
||||||
|
return getStatusEnum().isProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markAsChecked(final AppUser checker) {
|
public void markAsChecked(final AppUser checker) {
|
||||||
this.checker = checker;
|
this.checker = checker;
|
||||||
this.checkedOnDate = DateUtils.getAuditOffsetDateTime();
|
this.checkedOnDate = DateUtils.getAuditOffsetDateTime();
|
||||||
this.status = CommandProcessingResultType.PROCESSED.getValue();
|
setStatus(CommandProcessingResultType.PROCESSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChecked() {
|
||||||
|
return checker != null && isProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markAsRejected(final AppUser checker) {
|
public void markAsRejected(final AppUser checker) {
|
||||||
this.checker = checker;
|
this.checker = checker;
|
||||||
this.checkedOnDate = DateUtils.getAuditOffsetDateTime();
|
this.checkedOnDate = DateUtils.getAuditOffsetDateTime();
|
||||||
this.status = CommandProcessingResultType.REJECTED.getValue();
|
setStatus(CommandProcessingResultType.REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRejected() {
|
||||||
|
return checker != null && isRejected();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateForAudit(final CommandProcessingResult result) {
|
public void updateForAudit(final CommandProcessingResult result) {
|
||||||
|
|||||||
+19
-9
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.commands.domain;
|
package org.apache.fineract.commands.domain;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
|
import org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
|
||||||
@@ -45,6 +46,7 @@ public class CommandWrapper {
|
|||||||
private final Long organisationCreditBureauId;
|
private final Long organisationCreditBureauId;
|
||||||
private final String jobName;
|
private final String jobName;
|
||||||
private final ExternalId loanExternalId;
|
private final ExternalId loanExternalId;
|
||||||
|
private final Set<String> sanitizeJsonKeys;
|
||||||
|
|
||||||
private final String idempotencyKey;
|
private final String idempotencyKey;
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ public class CommandWrapper {
|
|||||||
final ExternalId loanExternalId) {
|
final ExternalId loanExternalId) {
|
||||||
return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl, productId, officeId,
|
return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl, productId, officeId,
|
||||||
groupId, clientId, loanId, savingsId, transactionId, creditBureauId, organisationCreditBureauId, idempotencyKey,
|
groupId, clientId, loanId, savingsId, transactionId, creditBureauId, organisationCreditBureauId, idempotencyKey,
|
||||||
loanExternalId);
|
loanExternalId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
|
private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
|
||||||
@@ -92,12 +94,14 @@ public class CommandWrapper {
|
|||||||
this.jobName = null;
|
this.jobName = null;
|
||||||
this.idempotencyKey = null;
|
this.idempotencyKey = null;
|
||||||
this.loanExternalId = null;
|
this.loanExternalId = null;
|
||||||
|
this.sanitizeJsonKeys = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
|
public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
|
||||||
final String actionName, final String entityName, final Long entityId, final Long subentityId, final String href,
|
final String actionName, final String entityName, final Long entityId, final Long subentityId, final String href,
|
||||||
final String json, final String transactionId, final Long productId, final Long templateId, final Long creditBureauId,
|
final String json, final String transactionId, final Long productId, final Long templateId, final Long creditBureauId,
|
||||||
final Long organisationCreditBureauId, final String jobName, final String idempotencyKey, final ExternalId loanExternalId) {
|
final Long organisationCreditBureauId, final String jobName, final String idempotencyKey, final ExternalId loanExternalId,
|
||||||
|
final Set<String> sanitizeJsonKeys) {
|
||||||
|
|
||||||
this.commandId = null;
|
this.commandId = null;
|
||||||
this.officeId = officeId;
|
this.officeId = officeId;
|
||||||
@@ -120,12 +124,14 @@ public class CommandWrapper {
|
|||||||
this.jobName = jobName;
|
this.jobName = jobName;
|
||||||
this.idempotencyKey = idempotencyKey;
|
this.idempotencyKey = idempotencyKey;
|
||||||
this.loanExternalId = loanExternalId;
|
this.loanExternalId = loanExternalId;
|
||||||
|
this.sanitizeJsonKeys = sanitizeJsonKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
|
private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
|
||||||
final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId, final Long groupId,
|
final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId, final Long groupId,
|
||||||
final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final Long creditBureauId,
|
final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final Long creditBureauId,
|
||||||
final Long organisationCreditBureauId, final String idempotencyKey, final ExternalId loanExternalId) {
|
final Long organisationCreditBureauId, final String idempotencyKey, final ExternalId loanExternalId,
|
||||||
|
final Set<String> sanitizeJsonKeys) {
|
||||||
|
|
||||||
this.commandId = commandId;
|
this.commandId = commandId;
|
||||||
this.officeId = officeId;
|
this.officeId = officeId;
|
||||||
@@ -147,6 +153,7 @@ public class CommandWrapper {
|
|||||||
this.jobName = null;
|
this.jobName = null;
|
||||||
this.idempotencyKey = idempotencyKey;
|
this.idempotencyKey = idempotencyKey;
|
||||||
this.loanExternalId = loanExternalId;
|
this.loanExternalId = loanExternalId;
|
||||||
|
this.sanitizeJsonKeys = sanitizeJsonKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCreate() {
|
public boolean isCreate() {
|
||||||
@@ -200,21 +207,24 @@ public class CommandWrapper {
|
|||||||
return isnoteResource;
|
return isnoteResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdateOfOwnUserDetails(final Long loggedInUserId) {
|
public boolean isChangeOfOwnUserDetails(final Long loggedInUserId) {
|
||||||
return isUserResource() && isUpdate() && loggedInUserId.equals(this.entityId);
|
return isUserResource() && loggedInUserId.equals(this.entityId) && (isUpdate() || isChangePasswordOperation());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdate() {
|
public boolean isUpdate() {
|
||||||
// permissions resource has special update which involves no resource.
|
// some resources have special update which involves no resource identifier.
|
||||||
return (isPermissionResource() && isUpdateOperation()) || (isCurrencyResource() && isUpdateOperation())
|
return isUpdateOperation() && (this.entityId != null || isPermissionResource() || isCurrencyResource() || isCacheResource()
|
||||||
|| (isCacheResource() && isUpdateOperation()) || (isWorkingDaysResource() && isUpdateOperation())
|
|| isWorkingDaysResource() || isPasswordPreferencesResource());
|
||||||
|| (isPasswordPreferencesResource() && isUpdateOperation()) || (isUpdateOperation() && (this.entityId != null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCacheResource() {
|
public boolean isCacheResource() {
|
||||||
return this.entityName.equalsIgnoreCase("CACHE");
|
return this.entityName.equalsIgnoreCase("CACHE");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isChangePasswordOperation() {
|
||||||
|
return this.actionName.equalsIgnoreCase("CHANGEPWD");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUpdateOperation() {
|
public boolean isUpdateOperation() {
|
||||||
return this.actionName.equalsIgnoreCase("UPDATE");
|
return this.actionName.equalsIgnoreCase("UPDATE");
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-6
@@ -20,6 +20,9 @@ package org.apache.fineract.commands.service;
|
|||||||
|
|
||||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||||
import org.apache.fineract.commands.domain.CommandSource;
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
@@ -28,10 +31,13 @@ import org.apache.fineract.commands.domain.CommandWrapper;
|
|||||||
import org.apache.fineract.commands.exception.CommandNotFoundException;
|
import org.apache.fineract.commands.exception.CommandNotFoundException;
|
||||||
import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException;
|
import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException;
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
@@ -49,8 +55,13 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CommandSourceService {
|
public class CommandSourceService {
|
||||||
|
|
||||||
|
public static final String COMMAND_MASK_VALUE = "***";
|
||||||
|
public static final String COMMAND_SANITIZE_ALL = "SANITIZE_ALL";
|
||||||
|
|
||||||
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
private final CommandSourceRepository commandSourceRepository;
|
private final CommandSourceRepository commandSourceRepository;
|
||||||
private final ErrorHandler errorHandler;
|
private final ErrorHandler errorHandler;
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||||
@@ -110,7 +121,8 @@ public class CommandSourceService {
|
|||||||
|
|
||||||
public CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
public CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||||
CommandSource commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey,
|
CommandSource commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey,
|
||||||
UNDER_PROCESSING.getValue());
|
UNDER_PROCESSING.getValue(), false);
|
||||||
|
sanitizeJson(commandSourceResult, wrapper.getSanitizeJsonKeys());
|
||||||
if (commandSourceResult.getCommandAsJson() == null) {
|
if (commandSourceResult.getCommandAsJson() == null) {
|
||||||
commandSourceResult.setCommandAsJson("{}");
|
commandSourceResult.setCommandAsJson("{}");
|
||||||
}
|
}
|
||||||
@@ -119,13 +131,51 @@ public class CommandSourceService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommandProcessingResult processCommand(NewCommandSourceHandler handler, JsonCommand command, CommandSource commandSource,
|
public CommandProcessingResult processCommand(NewCommandSourceHandler handler, JsonCommand command, CommandSource commandSource,
|
||||||
AppUser user, boolean isApprovedByChecker, boolean isMakerChecker) {
|
AppUser user, boolean isApprovedByChecker) {
|
||||||
final CommandProcessingResult result = handler.processCommand(command);
|
final CommandProcessingResult result = handler.processCommand(command);
|
||||||
boolean isRollback = !isApprovedByChecker && !user.isCheckerSuperUser() && (isMakerChecker || result.isRollbackTransaction());
|
|
||||||
if (isRollback) {
|
String permission = commandSource.getPermissionCode();
|
||||||
commandSource.markAsAwaitingApproval();
|
boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(permission);
|
||||||
throw new RollbackTransactionNotApprovedException(commandSource.getId(), commandSource.getResourceId());
|
if (isMakerChecker || result.isRollbackTransaction()) {
|
||||||
|
if (isApprovedByChecker || user.isCheckerSuperUser()) {
|
||||||
|
commandSource.markAsChecked(user);
|
||||||
|
} else {
|
||||||
|
if (commandSource.isSanitized()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.invalid.sanitization",
|
||||||
|
"Maker-checker command can not be sanitized, please change the permission configuration", permission);
|
||||||
|
}
|
||||||
|
commandSource.markAsAwaitingApproval();
|
||||||
|
throw new RollbackTransactionNotApprovedException(commandSource.getId(), commandSource.getResourceId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sanitizeJson(@NotNull CommandSource commandSource, Set<String> sanitizeKeys) {
|
||||||
|
if (sanitizeKeys == null || sanitizeKeys.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String commandAsJson = commandSource.getCommandAsJson();
|
||||||
|
if (commandAsJson == null || commandAsJson.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandAsJson);
|
||||||
|
if (!parsedCommand.isJsonObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String sanitizedJson;
|
||||||
|
if (sanitizeKeys.contains(COMMAND_SANITIZE_ALL)) {
|
||||||
|
sanitizedJson = "";
|
||||||
|
} else {
|
||||||
|
JsonObject jsonObject = parsedCommand.getAsJsonObject();
|
||||||
|
for (String key : sanitizeKeys) {
|
||||||
|
if (jsonObject.has(key)) {
|
||||||
|
jsonObject.addProperty(key, COMMAND_MASK_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sanitizedJson = jsonObject.toString();
|
||||||
|
}
|
||||||
|
commandSource.setCommandAsJson(sanitizedJson);
|
||||||
|
commandSource.setSanitized(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-2
@@ -18,7 +18,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.commands.service;
|
package org.apache.fineract.commands.service;
|
||||||
|
|
||||||
|
import static org.apache.fineract.useradministration.service.AppUserConstants.PASSWORD;
|
||||||
|
import static org.apache.fineract.useradministration.service.AppUserConstants.REPEAT_PASSWORD;
|
||||||
|
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
|
import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
@@ -49,18 +55,21 @@ public class CommandWrapperBuilder {
|
|||||||
private String jobName;
|
private String jobName;
|
||||||
private String idempotencyKey;
|
private String idempotencyKey;
|
||||||
private ExternalId loanExternalId;
|
private ExternalId loanExternalId;
|
||||||
|
private Set<String> sanitizeJsonKeys;
|
||||||
|
|
||||||
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "TODO: fix this!")
|
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "TODO: fix this!")
|
||||||
public CommandWrapper build() {
|
public CommandWrapper build() {
|
||||||
return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName,
|
return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName,
|
||||||
this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId,
|
this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId,
|
||||||
this.creditBureauId, this.organisationCreditBureauId, this.jobName, this.idempotencyKey, this.loanExternalId);
|
this.creditBureauId, this.organisationCreditBureauId, this.jobName, this.idempotencyKey, this.loanExternalId,
|
||||||
|
this.sanitizeJsonKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandWrapper build(String idempotencyKey) {
|
public CommandWrapper build(String idempotencyKey) {
|
||||||
return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName,
|
return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName,
|
||||||
this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId,
|
this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId,
|
||||||
this.creditBureauId, this.organisationCreditBureauId, this.jobName, idempotencyKey, this.loanExternalId);
|
this.creditBureauId, this.organisationCreditBureauId, this.jobName, idempotencyKey, this.loanExternalId,
|
||||||
|
this.sanitizeJsonKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder updateCreditBureau() {
|
public CommandWrapperBuilder updateCreditBureau() {
|
||||||
@@ -264,6 +273,16 @@ public class CommandWrapperBuilder {
|
|||||||
this.entityName = "USER";
|
this.entityName = "USER";
|
||||||
this.entityId = null;
|
this.entityId = null;
|
||||||
this.href = "/users/template";
|
this.href = "/users/template";
|
||||||
|
this.sanitizeJsonKeys = new HashSet<>(Arrays.asList(PASSWORD, REPEAT_PASSWORD));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder changeUserPassword(final Long userId) {
|
||||||
|
this.actionName = "CHANGEPWD";
|
||||||
|
this.entityName = "USER";
|
||||||
|
this.entityId = userId;
|
||||||
|
this.href = "/users/" + userId + "/pwd";
|
||||||
|
this.sanitizeJsonKeys = new HashSet<>(Arrays.asList(PASSWORD, REPEAT_PASSWORD));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +291,7 @@ public class CommandWrapperBuilder {
|
|||||||
this.entityName = "USER";
|
this.entityName = "USER";
|
||||||
this.entityId = userId;
|
this.entityId = userId;
|
||||||
this.href = "/users/" + userId;
|
this.href = "/users/" + userId;
|
||||||
|
this.sanitizeJsonKeys = new HashSet<>(Arrays.asList(PASSWORD, REPEAT_PASSWORD));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -55,7 +55,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
|||||||
boolean isApprovedByChecker = false;
|
boolean isApprovedByChecker = false;
|
||||||
|
|
||||||
// check if is update of own account details
|
// check if is update of own account details
|
||||||
if (wrapper.isUpdateOfOwnUserDetails(this.context.authenticatedUser(wrapper).getId())) {
|
if (wrapper.isChangeOfOwnUserDetails(this.context.authenticatedUser(wrapper).getId())) {
|
||||||
// then allow this operation to proceed.
|
// then allow this operation to proceed.
|
||||||
// maker checker doesnt mean anything here.
|
// maker checker doesnt mean anything here.
|
||||||
isApprovedByChecker = true; // set to true in case permissions have
|
isApprovedByChecker = true; // set to true in case permissions have
|
||||||
@@ -117,7 +117,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
|||||||
private CommandSource validateMakerCheckerTransaction(final Long makerCheckerId) {
|
private CommandSource validateMakerCheckerTransaction(final Long makerCheckerId) {
|
||||||
final CommandSource commandSource = this.commandSourceRepository.findById(makerCheckerId)
|
final CommandSource commandSource = this.commandSourceRepository.findById(makerCheckerId)
|
||||||
.orElseThrow(() -> new CommandNotFoundException(makerCheckerId));
|
.orElseThrow(() -> new CommandNotFoundException(makerCheckerId));
|
||||||
if (!commandSource.isMarkedAsAwaitingApproval()) {
|
if (!commandSource.isAwaitingApproval()) {
|
||||||
throw new CommandNotAwaitingApprovalException(makerCheckerId);
|
throw new CommandNotAwaitingApprovalException(makerCheckerId);
|
||||||
}
|
}
|
||||||
AppUser appUser = this.context.authenticatedUser();
|
AppUser appUser = this.context.authenticatedUser();
|
||||||
|
|||||||
+3
-8
@@ -117,16 +117,11 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
storeCommandIdInContext(commandSource); // Store command id as a request attribute
|
storeCommandIdInContext(commandSource); // Store command id as a request attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
|
|
||||||
if (isApprovedByChecker || (isMakerChecker && user.isCheckerSuperUser())) {
|
|
||||||
commandSource.markAsChecked(user);
|
|
||||||
}
|
|
||||||
setIdempotencyKeyStoreFlag(true);
|
setIdempotencyKeyStoreFlag(true);
|
||||||
|
|
||||||
final CommandProcessingResult result;
|
final CommandProcessingResult result;
|
||||||
try {
|
try {
|
||||||
result = commandSourceService.processCommand(findCommandHandler(wrapper), command, commandSource, user, isApprovedByChecker,
|
result = commandSourceService.processCommand(findCommandHandler(wrapper), command, commandSource, user, isApprovedByChecker);
|
||||||
isMakerChecker);
|
|
||||||
} catch (Throwable t) { // NOSONAR
|
} catch (Throwable t) { // NOSONAR
|
||||||
RuntimeException mappable = ErrorHandler.getMappable(t);
|
RuntimeException mappable = ErrorHandler.getMappable(t);
|
||||||
ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable);
|
ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable);
|
||||||
@@ -134,7 +129,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
commandSource.setResultStatusCode(statusCode);
|
commandSource.setResultStatusCode(statusCode);
|
||||||
commandSource.setResult(errorInfo.getMessage());
|
commandSource.setResult(errorInfo.getMessage());
|
||||||
if (statusCode != SC_OK) {
|
if (statusCode != SC_OK) {
|
||||||
commandSource.setStatus(ERROR.getValue());
|
commandSource.setStatus(ERROR);
|
||||||
}
|
}
|
||||||
if (!isEnclosingTransaction) { // TODO: temporary solution
|
if (!isEnclosingTransaction) { // TODO: temporary solution
|
||||||
commandSource = commandSourceService.saveResultNewTransaction(commandSource);
|
commandSource = commandSourceService.saveResultNewTransaction(commandSource);
|
||||||
@@ -148,7 +143,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
commandSource.setResultStatusCode(SC_OK);
|
commandSource.setResultStatusCode(SC_OK);
|
||||||
commandSource.updateForAudit(result);
|
commandSource.updateForAudit(result);
|
||||||
commandSource.setResult(toApiResultJsonSerializer.serializeResult(result));
|
commandSource.setResult(toApiResultJsonSerializer.serializeResult(result));
|
||||||
commandSource.setStatus(PROCESSED.getValue());
|
commandSource.setStatus(PROCESSED);
|
||||||
commandSource = commandSourceService.saveResultSameTransaction(commandSource);
|
commandSource = commandSourceService.saveResultSameTransaction(commandSource);
|
||||||
storeCommandIdInContext(commandSource); // Store command id as a request attribute
|
storeCommandIdInContext(commandSource); // Store command id as a request attribute
|
||||||
|
|
||||||
|
|||||||
+23
-19
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.useradministration.domain;
|
package org.apache.fineract.useradministration.domain;
|
||||||
|
|
||||||
|
import static org.apache.fineract.useradministration.service.AppUserConstants.PASSWORD;
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import jakarta.persistence.CascadeType;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
@@ -195,8 +197,27 @@ public class AppUser extends AbstractPersistableCustom<Long> implements Platform
|
|||||||
return organisationalRole;
|
return organisationalRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> changePassword(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder) {
|
||||||
|
// unencoded password provided
|
||||||
|
final Map<String, Object> actualChanges = new LinkedHashMap<>(1);
|
||||||
|
updatePassword(command, platformPasswordEncoder, actualChanges);
|
||||||
|
return actualChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePassword(JsonCommand command, PlatformPasswordEncoder platformPasswordEncoder, Map<String, Object> actualChanges) {
|
||||||
|
final String passwordParamName = PASSWORD;
|
||||||
|
if (command.hasParameter(passwordParamName)) {
|
||||||
|
if (command.isChangeInPasswordParameterNamed(passwordParamName, this.password, platformPasswordEncoder, getId())) {
|
||||||
|
final String passwordEncodedValue = command.passwordValueOfParameterNamed(passwordParamName, platformPasswordEncoder,
|
||||||
|
getId());
|
||||||
|
actualChanges.put(passwordParamName, true);
|
||||||
|
updatePassword(passwordEncodedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updatePassword(final String encodePassword) {
|
public void updatePassword(final String encodePassword) {
|
||||||
if (cannotChangePassword != null && cannotChangePassword == true) {
|
if (Boolean.TRUE.equals(cannotChangePassword)) {
|
||||||
throw new NoAuthorizationException("Password of this user may not be modified");
|
throw new NoAuthorizationException("Password of this user may not be modified");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,27 +244,10 @@ public class AppUser extends AbstractPersistableCustom<Long> implements Platform
|
|||||||
|
|
||||||
public Map<String, Object> update(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder,
|
public Map<String, Object> update(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder,
|
||||||
final Collection<Client> clients) {
|
final Collection<Client> clients) {
|
||||||
|
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
|
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
|
||||||
|
|
||||||
// unencoded password provided
|
// unencoded password provided
|
||||||
final String passwordParamName = "password";
|
updatePassword(command, platformPasswordEncoder, actualChanges);
|
||||||
final String passwordEncodedParamName = "passwordEncoded";
|
|
||||||
if (command.hasParameter(passwordParamName)) {
|
|
||||||
if (command.isChangeInPasswordParameterNamed(passwordParamName, this.password, platformPasswordEncoder, getId())) {
|
|
||||||
final String passwordEncodedValue = command.passwordValueOfParameterNamed(passwordParamName, platformPasswordEncoder,
|
|
||||||
getId());
|
|
||||||
updatePassword(passwordEncodedValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.hasParameter(passwordEncodedParamName)) {
|
|
||||||
if (command.isChangeInStringParameterNamed(passwordEncodedParamName, this.password)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed(passwordEncodedParamName);
|
|
||||||
updatePassword(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final String officeIdParamName = "officeId";
|
final String officeIdParamName = "officeId";
|
||||||
if (command.isChangeInLongParameterNamed(officeIdParamName, this.office.getId())) {
|
if (command.isChangeInLongParameterNamed(officeIdParamName, this.office.getId())) {
|
||||||
final Long newValue = command.longValueOfParameterNamed(officeIdParamName);
|
final Long newValue = command.longValueOfParameterNamed(officeIdParamName);
|
||||||
|
|||||||
+2
@@ -24,6 +24,8 @@ public final class AppUserConstants {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String PASSWORD = "password";
|
||||||
|
public static final String REPEAT_PASSWORD = "repeatPassword";
|
||||||
public static final String PASSWORD_NEVER_EXPIRES = "passwordNeverExpires";
|
public static final String PASSWORD_NEVER_EXPIRES = "passwordNeverExpires";
|
||||||
public static final String IS_SELF_SERVICE_USER = "isSelfServiceUser";
|
public static final String IS_SELF_SERVICE_USER = "isSelfServiceUser";
|
||||||
public static final String CLIENTS = "clients";
|
public static final String CLIENTS = "clients";
|
||||||
|
|||||||
+2
-2
@@ -58,8 +58,8 @@ public class IdempotencyCommandProcessFailedExceptionTest {
|
|||||||
public void testInconsistentStatus() {
|
public void testInconsistentStatus() {
|
||||||
IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper();
|
IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper();
|
||||||
CommandWrapper command = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
CommandWrapper command = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||||
null, null, null, null, null);
|
null, null, null, null, null, null);
|
||||||
CommandSource source = CommandSource.fullEntryFrom(command, JsonCommand.from("{}"), null, "dummy-key", null);
|
CommandSource source = CommandSource.fullEntryFrom(command, JsonCommand.from("{}"), null, "dummy-key", null, false);
|
||||||
IdempotentCommandProcessFailedException exception = new IdempotentCommandProcessFailedException(command, null, source);
|
IdempotentCommandProcessFailedException exception = new IdempotentCommandProcessFailedException(command, null, source);
|
||||||
Response result = mapper.toResponse(exception);
|
Response result = mapper.toResponse(exception);
|
||||||
assertEquals(500, result.getStatus());
|
assertEquals(500, result.getStatus());
|
||||||
|
|||||||
+1
-1
@@ -268,7 +268,7 @@ public class CenterImportHandler implements ImportHandler {
|
|||||||
String payload = gsonBuilder.create().toJson(calendarData);
|
String payload = gsonBuilder.create().toJson(calendarData);
|
||||||
CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(),
|
CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(),
|
||||||
result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(),
|
result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(),
|
||||||
result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null);
|
result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null, null);
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||||
.createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) //
|
.createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) //
|
||||||
.withJson(payload) //
|
.withJson(payload) //
|
||||||
|
|||||||
+1
-1
@@ -252,7 +252,7 @@ public class GroupImportHandler implements ImportHandler {
|
|||||||
|
|
||||||
CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(),
|
CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(),
|
||||||
result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(),
|
result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(),
|
||||||
result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null);
|
result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null, null);
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||||
.createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) //
|
.createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) //
|
||||||
.withJson(payload) //
|
.withJson(payload) //
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ public class InteropWrapperBuilder {
|
|||||||
|
|
||||||
public CommandWrapper build() {
|
public CommandWrapper build() {
|
||||||
return new CommandWrapper(null, null, null, null, null, actionName, entityName, null, null, href, json, null, null, null, null,
|
return new CommandWrapper(null, null, null, null, null, actionName, entityName, null, null, href, json, null, null, null, null,
|
||||||
null, null, null, null);
|
null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InteropWrapperBuilder withJson(final String json) {
|
public InteropWrapperBuilder withJson(final String json) {
|
||||||
|
|||||||
+22
-1
@@ -171,7 +171,7 @@ public class UsersApiResource {
|
|||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Path("{userId}")
|
@Path("{userId}")
|
||||||
@Operation(summary = "Update a User", description = "When updating a password you must provide the repeatPassword parameter also.")
|
@Operation(summary = "Update a User", description = "Updates the user")
|
||||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.PutUsersUserIdRequest.class)))
|
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.PutUsersUserIdRequest.class)))
|
||||||
@ApiResponses({
|
@ApiResponses({
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.PutUsersUserIdResponse.class))) })
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.PutUsersUserIdResponse.class))) })
|
||||||
@@ -190,6 +190,27 @@ public class UsersApiResource {
|
|||||||
return this.toApiJsonSerializer.serialize(result);
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{userId}/pwd")
|
||||||
|
@Operation(summary = "Change the password of a User", description = "When updating a password you must provide the repeatPassword parameter also.")
|
||||||
|
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.ChangePwdUsersUserIdRequest.class)))
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.ChangePwdUsersUserIdResponse.class))) })
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
public String changePassword(@PathParam("userId") @Parameter(description = "userId") final Long userId,
|
||||||
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||||
|
.changeUserPassword(userId) //
|
||||||
|
.withJson(apiRequestBodyAsJson) //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("{userId}")
|
@Path("{userId}")
|
||||||
@Operation(summary = "Delete a User", description = "Removes the user and the associated roles and permissions.")
|
@Operation(summary = "Delete a User", description = "Removes the user and the associated roles and permissions.")
|
||||||
|
|||||||
+37
@@ -151,6 +151,43 @@ final class UsersApiResourceSwagger {
|
|||||||
public Long resourceId;
|
public Long resourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Schema(description = "ChangePwdUsersUserIdRequest")
|
||||||
|
public static final class ChangePwdUsersUserIdRequest {
|
||||||
|
|
||||||
|
private ChangePwdUsersUserIdRequest() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(example = "password")
|
||||||
|
public String password;
|
||||||
|
@Schema(example = "repeatPassword")
|
||||||
|
public String repeatPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "ChangePwdUsersUserIdResponse")
|
||||||
|
public static final class ChangePwdUsersUserIdResponse {
|
||||||
|
|
||||||
|
private ChangePwdUsersUserIdResponse() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ChangePwdUsersUserIdResponseChanges {
|
||||||
|
|
||||||
|
private ChangePwdUsersUserIdResponseChanges() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(example = "true")
|
||||||
|
public boolean password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(example = "1")
|
||||||
|
public Long officeId;
|
||||||
|
@Schema(example = "11")
|
||||||
|
public Long resourceId;
|
||||||
|
public ChangePwdUsersUserIdResponseChanges changes;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(description = "PutUsersUserIdRequest")
|
@Schema(description = "PutUsersUserIdRequest")
|
||||||
public static final class PutUsersUserIdRequest {
|
public static final class PutUsersUserIdRequest {
|
||||||
|
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* 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.handler;
|
||||||
|
|
||||||
|
import org.apache.fineract.commands.annotation.CommandType;
|
||||||
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.useradministration.service.AppUserWritePlatformService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "USER", action = "CHANGEPWD")
|
||||||
|
public class ChangeUserPasswordCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final AppUserWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ChangeUserPasswordCommandHandler(final AppUserWritePlatformService writePlatformService) {
|
||||||
|
this.writePlatformService = writePlatformService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
final Long userId = command.entityId();
|
||||||
|
return this.writePlatformService.changeUserPassword(userId, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -25,6 +25,8 @@ public interface AppUserWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult createUser(JsonCommand command);
|
CommandProcessingResult createUser(JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult changeUserPassword(Long userId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult updateUser(Long userId, JsonCommand command);
|
CommandProcessingResult updateUser(Long userId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult deleteUser(Long userId);
|
CommandProcessingResult deleteUser(Long userId);
|
||||||
|
|||||||
+30
@@ -154,6 +154,36 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
@Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
|
||||||
|
public CommandProcessingResult changeUserPassword(final Long userId, final JsonCommand command) {
|
||||||
|
try {
|
||||||
|
this.context.authenticatedUser(new CommandWrapperBuilder().updateUser(null).build());
|
||||||
|
this.fromApiJsonDeserializer.validateForChangePassword(command.json(), this.context.authenticatedUser());
|
||||||
|
final AppUser userToUpdate = this.appUserRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId));
|
||||||
|
final AppUserPreviousPassword currentPasswordToSaveAsPreview = getCurrentPasswordToSaveAsPreview(userToUpdate, command);
|
||||||
|
final Map<String, Object> changes = userToUpdate.changePassword(command, this.platformPasswordEncoder);
|
||||||
|
if (!changes.isEmpty()) {
|
||||||
|
this.appUserRepository.saveAndFlush(userToUpdate);
|
||||||
|
if (currentPasswordToSaveAsPreview != null) {
|
||||||
|
this.appUserPreviewPasswordRepository.save(currentPasswordToSaveAsPreview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withEntityId(userId) //
|
||||||
|
.withOfficeId(userToUpdate.getOffice().getId()) //
|
||||||
|
.with(changes) //
|
||||||
|
.build();
|
||||||
|
} catch (final DataIntegrityViolationException dve) {
|
||||||
|
throw handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||||
|
} catch (final JpaSystemException | PersistenceException | AuthenticationServiceException dve) {
|
||||||
|
log.error("changeUserPassword: JpaSystemException | PersistenceException | AuthenticationServiceException", dve);
|
||||||
|
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||||
|
throw handleDataIntegrityIssues(command, throwable, dve);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
|
@Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
|
||||||
|
|||||||
+46
-26
@@ -19,6 +19,8 @@
|
|||||||
package org.apache.fineract.useradministration.service;
|
package org.apache.fineract.useradministration.service;
|
||||||
|
|
||||||
import static org.apache.fineract.useradministration.service.AppUserConstants.CLIENTS;
|
import static org.apache.fineract.useradministration.service.AppUserConstants.CLIENTS;
|
||||||
|
import static org.apache.fineract.useradministration.service.AppUserConstants.PASSWORD;
|
||||||
|
import static org.apache.fineract.useradministration.service.AppUserConstants.REPEAT_PASSWORD;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
@@ -49,8 +51,6 @@ public final class UserDataValidator {
|
|||||||
public static final String USERNAME = "username";
|
public static final String USERNAME = "username";
|
||||||
public static final String FIRSTNAME = "firstname";
|
public static final String FIRSTNAME = "firstname";
|
||||||
public static final String LASTNAME = "lastname";
|
public static final String LASTNAME = "lastname";
|
||||||
public static final String PASSWORD = "password";
|
|
||||||
public static final String REPEAT_PASSWORD = "repeatPassword";
|
|
||||||
public static final String EMAIL = "email";
|
public static final String EMAIL = "email";
|
||||||
public static final String OFFICE_ID = "officeId";
|
public static final String OFFICE_ID = "officeId";
|
||||||
public static final String NOT_SELECTED_ROLES = "notSelectedRoles";
|
public static final String NOT_SELECTED_ROLES = "notSelectedRoles";
|
||||||
@@ -61,9 +61,13 @@ public final class UserDataValidator {
|
|||||||
/**
|
/**
|
||||||
* The parameters supported for this command.
|
* The parameters supported for this command.
|
||||||
*/
|
*/
|
||||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(
|
private static final Set<String> CREATE_SUPPORTED_PARAMETERS = new HashSet<>(
|
||||||
Arrays.asList(USERNAME, FIRSTNAME, LASTNAME, PASSWORD, REPEAT_PASSWORD, EMAIL, OFFICE_ID, NOT_SELECTED_ROLES, ROLES,
|
Arrays.asList(USERNAME, FIRSTNAME, LASTNAME, PASSWORD, REPEAT_PASSWORD, EMAIL, OFFICE_ID, NOT_SELECTED_ROLES, ROLES,
|
||||||
SEND_PASSWORD_TO_EMAIL, STAFF_ID, PASSWORD_NEVER_EXPIRES, AppUserConstants.IS_SELF_SERVICE_USER, CLIENTS));
|
SEND_PASSWORD_TO_EMAIL, STAFF_ID, PASSWORD_NEVER_EXPIRES, AppUserConstants.IS_SELF_SERVICE_USER, CLIENTS));
|
||||||
|
private static final Set<String> UPDATE_SUPPORTED_PARAMETERS = new HashSet<>(
|
||||||
|
Arrays.asList(USERNAME, FIRSTNAME, LASTNAME, PASSWORD, REPEAT_PASSWORD, EMAIL, OFFICE_ID, NOT_SELECTED_ROLES, ROLES,
|
||||||
|
SEND_PASSWORD_TO_EMAIL, STAFF_ID, PASSWORD_NEVER_EXPIRES, AppUserConstants.IS_SELF_SERVICE_USER, CLIENTS));
|
||||||
|
private static final Set<String> CHANGE_PASSWORD_SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList(PASSWORD, REPEAT_PASSWORD));
|
||||||
public static final String PASSWORD_NEVER_EXPIRE = "passwordNeverExpire";
|
public static final String PASSWORD_NEVER_EXPIRE = "passwordNeverExpire";
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
@@ -84,7 +88,7 @@ public final class UserDataValidator {
|
|||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||||
|
|
||||||
}.getType();
|
}.getType();
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CREATE_SUPPORTED_PARAMETERS);
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
|
||||||
@@ -106,16 +110,7 @@ public final class UserDataValidator {
|
|||||||
final String email = this.fromApiJsonHelper.extractStringNamed(EMAIL, element);
|
final String email = this.fromApiJsonHelper.extractStringNamed(EMAIL, element);
|
||||||
baseDataValidator.reset().parameter(EMAIL).value(email).notBlank().notExceedingLengthOf(100);
|
baseDataValidator.reset().parameter(EMAIL).value(email).notBlank().notExceedingLengthOf(100);
|
||||||
} else {
|
} else {
|
||||||
final String password = this.fromApiJsonHelper.extractStringNamed(PASSWORD, element);
|
validatePassword(baseDataValidator, element);
|
||||||
final String repeatPassword = this.fromApiJsonHelper.extractStringNamed(REPEAT_PASSWORD, element);
|
|
||||||
final PasswordValidationPolicy validationPolicy = this.passwordValidationPolicy.findActivePasswordValidationPolicy();
|
|
||||||
final String regex = validationPolicy.getRegex();
|
|
||||||
final String description = validationPolicy.getDescription();
|
|
||||||
baseDataValidator.reset().parameter(PASSWORD).value(password).matchesRegularExpression(regex, description);
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(password)) {
|
|
||||||
baseDataValidator.reset().parameter(PASSWORD).value(password).equalToParameter(REPEAT_PASSWORD, repeatPassword);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
baseDataValidator.reset().parameter(SEND_PASSWORD_TO_EMAIL).value(sendPasswordToEmail).trueOrFalseRequired(false);
|
baseDataValidator.reset().parameter(SEND_PASSWORD_TO_EMAIL).value(sendPasswordToEmail).trueOrFalseRequired(false);
|
||||||
@@ -196,6 +191,27 @@ public final class UserDataValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void validateForChangePassword(final String json, AppUser authenticatedUser) {
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
throw new InvalidJsonException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||||
|
|
||||||
|
}.getType();
|
||||||
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CHANGE_PASSWORD_SUPPORTED_PARAMETERS);
|
||||||
|
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
|
||||||
|
|
||||||
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
|
||||||
|
validatePassword(baseDataValidator, element);
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
validateFieldLevelACL(json, authenticatedUser);
|
||||||
|
}
|
||||||
|
|
||||||
public void validateForUpdate(final String json, AppUser authenticatedUser) {
|
public void validateForUpdate(final String json, AppUser authenticatedUser) {
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
throw new InvalidJsonException();
|
throw new InvalidJsonException();
|
||||||
@@ -204,7 +220,7 @@ public final class UserDataValidator {
|
|||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||||
|
|
||||||
}.getType();
|
}.getType();
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_SUPPORTED_PARAMETERS);
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
|
||||||
@@ -247,17 +263,7 @@ public final class UserDataValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(PASSWORD, element)) {
|
if (this.fromApiJsonHelper.parameterExists(PASSWORD, element)) {
|
||||||
final String password = this.fromApiJsonHelper.extractStringNamed(PASSWORD, element);
|
validatePassword(baseDataValidator, element);
|
||||||
final String repeatPassword = this.fromApiJsonHelper.extractStringNamed(REPEAT_PASSWORD, element);
|
|
||||||
|
|
||||||
final PasswordValidationPolicy validationPolicy = this.passwordValidationPolicy.findActivePasswordValidationPolicy();
|
|
||||||
final String regex = validationPolicy.getRegex();
|
|
||||||
final String description = validationPolicy.getDescription();
|
|
||||||
baseDataValidator.reset().parameter(PASSWORD).value(password).matchesRegularExpression(regex, description);
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(password)) {
|
|
||||||
baseDataValidator.reset().parameter(PASSWORD).value(password).equalToParameter(REPEAT_PASSWORD, repeatPassword);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(PASSWORD_NEVER_EXPIRE, element)) {
|
if (this.fromApiJsonHelper.parameterExists(PASSWORD_NEVER_EXPIRE, element)) {
|
||||||
@@ -291,4 +297,18 @@ public final class UserDataValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
validateFieldLevelACL(json, authenticatedUser);
|
validateFieldLevelACL(json, authenticatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validatePassword(DataValidatorBuilder baseDataValidator, JsonElement element) {
|
||||||
|
final String password = this.fromApiJsonHelper.extractStringNamed(PASSWORD, element);
|
||||||
|
final String repeatPassword = this.fromApiJsonHelper.extractStringNamed(REPEAT_PASSWORD, element);
|
||||||
|
|
||||||
|
final PasswordValidationPolicy validationPolicy = this.passwordValidationPolicy.findActivePasswordValidationPolicy();
|
||||||
|
final String regex = validationPolicy.getRegex();
|
||||||
|
final String description = validationPolicy.getDescription();
|
||||||
|
DataValidatorBuilder validator = baseDataValidator.reset().parameter(PASSWORD).value(password).matchesRegularExpression(regex,
|
||||||
|
description);
|
||||||
|
if (StringUtils.isNotBlank(password)) {
|
||||||
|
validator.equalToParameter(REPEAT_PASSWORD, repeatPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,4 +191,5 @@
|
|||||||
<include file="parts/0170_days_in_year_custom_strategy.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0170_days_in_year_custom_strategy.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0171_loan_capitalized_income.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0171_loan_capitalized_income.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0172_create_loan_capitalized_income_balance.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0172_create_loan_capitalized_income_balance.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0173_user_change_pwd.xml" relativeToChangelogFile="true" />
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+9
@@ -89,4 +89,13 @@
|
|||||||
<column name="can_maker_checker" valueBoolean="false"/>
|
<column name="can_maker_checker" valueBoolean="false"/>
|
||||||
</insert>
|
</insert>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
<changeSet author="fineract" id="3">
|
||||||
|
<insert tableName="m_permission">
|
||||||
|
<column name="grouping" value="portfolio"/>
|
||||||
|
<column name="code" value="DEACTIVATEOVERDUE_LOANCHARGE"/>
|
||||||
|
<column name="entity_name" value="LOANCHARGE"/>
|
||||||
|
<column name="action_name" value="DEACTIVATEOVERDUE"/>
|
||||||
|
<column name="can_maker_checker" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<addColumn tableName="m_portfolio_command_source">
|
||||||
|
<column name="is_sanitized" type="boolean" defaultValueBoolean="false" >
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="fineract" id="2">
|
||||||
|
<insert tableName="m_permission">
|
||||||
|
<column name="grouping" value="authorisation"/>
|
||||||
|
<column name="code" value="CHANGEPWD_USER"/>
|
||||||
|
<column name="entity_name" value="USER"/>
|
||||||
|
<column name="action_name" value="CHANGEPWD"/>
|
||||||
|
<column name="can_maker_checker" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="fineract" id="3">
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_role_permission (role_id, permission_id)
|
||||||
|
SELECT mrp.role_id, mp_changepwd.id
|
||||||
|
FROM m_role_permission mrp
|
||||||
|
LEFT JOIN m_permission mp_update ON mp_update.id = mrp.permission_id
|
||||||
|
LEFT JOIN m_permission mp_changepwd ON mp_changepwd.action_name = 'CHANGEPWD'
|
||||||
|
AND mp_changepwd.entity_name = 'USER'
|
||||||
|
WHERE mp_update.action_name = 'UPDATE'
|
||||||
|
AND mp_update.entity_name = 'USER';
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -39423,6 +39423,40 @@ Request body:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<a id="users_changepwd" name="users_changepwd" class="old-syle-anchor"> </a>
|
||||||
|
<div class="method-section">
|
||||||
|
<div class="method-description">
|
||||||
|
<h4>Change the password of a User</h4>
|
||||||
|
<p>
|
||||||
|
<b>Note:</b> When updating a password you must provide the
|
||||||
|
repeatPassword parameter also.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="method-example">
|
||||||
|
<code class="method-declaration">
|
||||||
|
POST https://DomainName/api/v1/users/{userId)/pwd
|
||||||
|
</code>
|
||||||
|
<code class="method-request">
|
||||||
|
POST users/3/pwd
|
||||||
|
Content-Type: application/json
|
||||||
|
Request body:
|
||||||
|
{
|
||||||
|
"password": "window75",
|
||||||
|
"repeatPassword": "window75"
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
<code class="method-response">
|
||||||
|
{
|
||||||
|
"officeId": 1,
|
||||||
|
"resourceId": 3,
|
||||||
|
"changes": {
|
||||||
|
"passwordEncoded": "abc3326b1bb376351c7baeb4175f5e0504e33aadf6a158474a6d71de1befae51"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a id="users_update" name="users_update" class="old-syle-anchor"> </a>
|
<a id="users_update" name="users_update" class="old-syle-anchor"> </a>
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<div class="method-description">
|
<div class="method-description">
|
||||||
@@ -39451,8 +39485,7 @@ Request body:
|
|||||||
"officeId": 1,
|
"officeId": 1,
|
||||||
"resourceId": 3,
|
"resourceId": 3,
|
||||||
"changes": {
|
"changes": {
|
||||||
"firstname": "Test",
|
"firstname": "Test"
|
||||||
"passwordEncoded": "abc3326b1bb376351c7baeb4175f5e0504e33aadf6a158474a6d71de1befae51"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</code>
|
</code>
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@ public class CommandServiceStepDefinitions implements En {
|
|||||||
|
|
||||||
public DummyCommand() {
|
public DummyCommand() {
|
||||||
super(null, null, null, null, null, null, null, null, null, null, "{}", null, null, null, null, null, null,
|
super(null, null, null, null, null, null, null, null, null, null, "{}", null, null, null, null, null, null,
|
||||||
UUID.randomUUID().toString(), null);
|
UUID.randomUUID().toString(), null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+4
@@ -29,6 +29,7 @@ import org.apache.fineract.commands.domain.CommandSource;
|
|||||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.infrastructure.codes.exception.CodeNotFoundException;
|
import org.apache.fineract.infrastructure.codes.exception.CodeNotFoundException;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||||
@@ -48,6 +49,9 @@ import org.mockito.MockitoAnnotations;
|
|||||||
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
|
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
|
||||||
public class CommandSourceServiceTest {
|
public class CommandSourceServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ConfigurationDomainService configurationDomainService;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private CommandSourceRepository commandSourceRepository;
|
private CommandSourceRepository commandSourceRepository;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ public class IdempotencyKeyResolverTest {
|
|||||||
public void testIPKResolveFromWrapper() {
|
public void testIPKResolveFromWrapper() {
|
||||||
String idk = "idk";
|
String idk = "idk";
|
||||||
CommandWrapper wrapper = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
CommandWrapper wrapper = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||||
null, null, null, idk, null);
|
null, null, null, idk, null, null);
|
||||||
String resolvedIdk = underTest.resolve(wrapper);
|
String resolvedIdk = underTest.resolve(wrapper);
|
||||||
Assertions.assertEquals(idk, resolvedIdk);
|
Assertions.assertEquals(idk, resolvedIdk);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -117,7 +117,7 @@ public class SynchronousCommandProcessingServiceTest {
|
|||||||
when(commandSource.getStatus()).thenReturn(CommandProcessingResultType.PROCESSED.getValue());
|
when(commandSource.getStatus()).thenReturn(CommandProcessingResultType.PROCESSED.getValue());
|
||||||
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser);
|
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser);
|
||||||
|
|
||||||
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false, false))
|
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false))
|
||||||
.thenReturn(commandProcessingResult);
|
.thenReturn(commandProcessingResult);
|
||||||
|
|
||||||
CommandProcessingResult actualCommandProcessingResult = underTest.executeCommand(commandWrapper, jsonCommand, false);
|
CommandProcessingResult actualCommandProcessingResult = underTest.executeCommand(commandWrapper, jsonCommand, false);
|
||||||
@@ -163,8 +163,7 @@ public class SynchronousCommandProcessingServiceTest {
|
|||||||
|
|
||||||
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(initialCommandSource);
|
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(initialCommandSource);
|
||||||
|
|
||||||
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false, false))
|
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false)).thenThrow(runtimeException);
|
||||||
.thenThrow(runtimeException);
|
|
||||||
|
|
||||||
assertThrows(RuntimeException.class, () -> {
|
assertThrows(RuntimeException.class, () -> {
|
||||||
underTest.executeCommand(commandWrapper, jsonCommand, false);
|
underTest.executeCommand(commandWrapper, jsonCommand, false);
|
||||||
|
|||||||
+37
-1
@@ -28,6 +28,8 @@ import io.restassured.specification.ResponseSpecification;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.apache.fineract.client.models.ChangePwdUsersUserIdRequest;
|
||||||
|
import org.apache.fineract.client.models.ChangePwdUsersUserIdResponse;
|
||||||
import org.apache.fineract.client.models.GetOfficesResponse;
|
import org.apache.fineract.client.models.GetOfficesResponse;
|
||||||
import org.apache.fineract.client.models.GetUsersUserIdResponse;
|
import org.apache.fineract.client.models.GetUsersUserIdResponse;
|
||||||
import org.apache.fineract.client.models.PostUsersRequest;
|
import org.apache.fineract.client.models.PostUsersRequest;
|
||||||
@@ -183,7 +185,7 @@ public class UserAdministrationTest extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testApplicationUserCanChangeOwnPassword() {
|
public void testApplicationUserCanUpdateOwnPassword() {
|
||||||
// Admin creates a new user with an empty role
|
// Admin creates a new user with an empty role
|
||||||
Integer roleId = RolesHelper.createRole(requestSpec, responseSpec);
|
Integer roleId = RolesHelper.createRole(requestSpec, responseSpec);
|
||||||
String originalPassword = "QwE!5rTy#9uP0";
|
String originalPassword = "QwE!5rTy#9uP0";
|
||||||
@@ -215,6 +217,40 @@ public class UserAdministrationTest extends IntegrationTest {
|
|||||||
GetUsersUserIdResponse ok = ok(newFineractClient(simpleUsername, updatedPassword).users.retrieveOne31(userId));
|
GetUsersUserIdResponse ok = ok(newFineractClient(simpleUsername, updatedPassword).users.retrieveOne31(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApplicationUserCanChangeOwnPassword() {
|
||||||
|
// Admin creates a new user with an empty role
|
||||||
|
Integer roleId = RolesHelper.createRole(requestSpec, responseSpec);
|
||||||
|
String originalPassword = "QwE!5rTy#9uP0";
|
||||||
|
String simpleUsername = Utils.uniqueRandomStringGenerator("NotificationUser", 4);
|
||||||
|
GetOfficesResponse headOffice = OfficeHelper.getHeadOffice(requestSpec, responseSpec);
|
||||||
|
PostUsersRequest createUserRequest = new PostUsersRequest().username(simpleUsername)
|
||||||
|
.firstname(Utils.randomStringGenerator("NotificationFN", 4)).lastname(Utils.randomStringGenerator("NotificationLN", 4))
|
||||||
|
.email("whatever@mifos.org").password(originalPassword).repeatPassword(originalPassword).sendPasswordToEmail(false)
|
||||||
|
.officeId(headOffice.getId()).roles(List.of(Long.valueOf(roleId)));
|
||||||
|
|
||||||
|
PostUsersResponse userCreationResponse = UserHelper.createUser(requestSpec, responseSpec, createUserRequest);
|
||||||
|
Long userId = userCreationResponse.getResourceId();
|
||||||
|
Assertions.assertNotNull(userId);
|
||||||
|
|
||||||
|
// User changes its own password
|
||||||
|
|
||||||
|
String updatedPassword = "pX268-4Pfv|kF6";
|
||||||
|
ChangePwdUsersUserIdResponse changePwdUsersUserIdResponse = ok(newFineractClient(simpleUsername, originalPassword).users
|
||||||
|
.changePassword(userId, new ChangePwdUsersUserIdRequest().password(updatedPassword).repeatPassword(updatedPassword)));
|
||||||
|
Assertions.assertNotNull(changePwdUsersUserIdResponse.getResourceId());
|
||||||
|
|
||||||
|
// From then on the originalPassword is not working anymore
|
||||||
|
CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
ok(newFineractClient(simpleUsername, originalPassword).users.retrieveOne31(userId));
|
||||||
|
});
|
||||||
|
Assertions.assertEquals(401, callFailedRuntimeException.getResponse().raw().code());
|
||||||
|
Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("Unauthorized"));
|
||||||
|
|
||||||
|
// The update password is still working perfectly
|
||||||
|
GetUsersUserIdResponse ok = ok(newFineractClient(simpleUsername, updatedPassword).users.retrieveOne31(userId));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testApplicationUserShallNotBeAbleToChangeItsOwnRoles() {
|
public void testApplicationUserShallNotBeAbleToChangeItsOwnRoles() {
|
||||||
// Admin creates a new user with one role assigned
|
// Admin creates a new user with one role assigned
|
||||||
|
|||||||
Reference in New Issue
Block a user