Revert "Enforce same-user maker-checker restriction"
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Reverts commits2afd4aaf8and11a0d502b(both by anshul, same title). Restores the CHECKER_SUPER_USER self-approval bypass in CommandSourceService and the six *EditWritePlatformServiceJpaRepositoryImpl classes, and restores the original isSameMakerCheckerEnabled()/isCheckerSuperUser()-gated same-user check in PortfolioCommandSourceWritePlatformServiceImpl. Removes the 0588_persist_manually_enabled_maker_checker_permissions.xml migration added alongside the restriction. The unconditional same-user block these commits introduced was blocking legitimate single-admin operation/testing (e.g. creating and then activating a client as the same CHECKER_SUPER_USER), with no way to resolve it from inside the app. 11a0d502b's only change (0012_seed_default_platform_api_client.xml comment wording) is a no-op here: that exact text was independently reworded again by82336cbe9after11a0d502blanded, so there is nothing left of 11a0d502b's change to revert in that file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+20
-10
@@ -137,16 +137,7 @@ public class CommandSourceService {
|
||||
String permission = commandSource.getPermissionCode();
|
||||
boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(permission);
|
||||
if (isMakerChecker || result.isRollbackTransaction()) {
|
||||
// isApprovedByChecker is only ever true here for a genuine checker approval
|
||||
// (PortfolioCommandSourceWritePlatformServiceImpl.approveEntry(), which has already run
|
||||
// validateMakerCheckerTransaction() and confirmed the approver is not the maker) or a user
|
||||
// editing their own account details (CommandWrapper.isChangeOfOwnUserDetails() - maker-checker
|
||||
// genuinely does not apply there). On the ORIGINAL submission this is always false and `user`
|
||||
// here is always the maker themselves, so there is deliberately no CHECKER_SUPER_USER (or any
|
||||
// other) bypass at this point: holding CHECKER_SUPER_USER lets a user approve someone ELSE's
|
||||
// request without needing the specific <TASK>_CHECKER permission, it must never let a maker
|
||||
// auto-approve their own submission just by holding it.
|
||||
if (isApprovedByChecker) {
|
||||
if (isApprovedByChecker || isSuperUserBypassAllowed(commandSource, user)) {
|
||||
commandSource.markAsChecked(user);
|
||||
} else {
|
||||
if (commandSource.isSanitized()) {
|
||||
@@ -160,6 +151,25 @@ public class CommandSourceService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isSuperUserBypassAllowed(CommandSource commandSource, AppUser user) {
|
||||
if (!user.isCheckerSuperUser()) {
|
||||
return false;
|
||||
}
|
||||
if ("GLACCOUNT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CBSBASEINTERESTRATE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CHARGE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "LOANPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "SAVINGSPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CURRENTPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "SHAREPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "FIXEDDEPOSITPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "USER".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "ROLE".equalsIgnoreCase(commandSource.getEntityName())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sanitizeJson(@NonNull CommandSource commandSource, Set<String> sanitizeKeys) {
|
||||
if (sanitizeKeys == null || sanitizeKeys.isEmpty()) {
|
||||
return;
|
||||
|
||||
+8
-20
@@ -48,12 +48,6 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final CommandProcessingService processAndLogCommandService;
|
||||
private final SchedulerJobRunnerReadService schedulerJobRunnerReadService;
|
||||
// No longer consulted by validateMakerCheckerTransaction() below: the maker-identity rule (a user
|
||||
// can never approve their own submission) is now unconditional and isSameMakerCheckerEnabled() no
|
||||
// longer overrides it. Left injected rather than removed - enable-same-maker-checker is still a
|
||||
// live, independently-configurable global switch (System -> Configurations); deleting this field,
|
||||
// and by extension deciding that switch should stop existing altogether, is a separate decision
|
||||
// from closing this specific self-approval loophole.
|
||||
private final ConfigurationDomainService configurationService;
|
||||
|
||||
@Override
|
||||
@@ -129,20 +123,14 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
||||
AppUser appUser = this.context.authenticatedUser();
|
||||
String permissionCode = commandSource.getPermissionCode();
|
||||
appUser.validateHasCheckerPermissionTo(permissionCode);
|
||||
// Maker-identity rule: the user who submitted a command may never be the one who approves
|
||||
// (or rejects) it - this is a fixed platform rule, not overridable by CHECKER_SUPER_USER (that
|
||||
// permission still lets its holder action a DIFFERENT user's request without needing the
|
||||
// specific <TASK>_CHECKER permission - see validateHasCheckerPermissionTo() above - it just no
|
||||
// longer lets them action their own) and not overridable by the enable-same-maker-checker
|
||||
// global switch (isSameMakerCheckerEnabled() is deliberately no longer consulted here; that
|
||||
// configuration is left in place and unused for this decision rather than removed, since
|
||||
// deleting a still-configurable global setting is a separate decision from this one).
|
||||
AppUser maker = commandSource.getMaker();
|
||||
if (maker == null) {
|
||||
throw new UnsupportedCommandException(permissionCode, "Maker user is missing.");
|
||||
}
|
||||
if (Objects.equals(appUser.getId(), maker.getId())) {
|
||||
throw new UnsupportedCommandException(permissionCode, "Can not be checked by the same user.");
|
||||
if (!configurationService.isSameMakerCheckerEnabled() && !appUser.isCheckerSuperUser()) {
|
||||
AppUser maker = commandSource.getMaker();
|
||||
if (maker == null) {
|
||||
throw new UnsupportedCommandException(permissionCode, "Maker user is missing.");
|
||||
}
|
||||
if (Objects.equals(appUser.getId(), maker.getId())) {
|
||||
throw new UnsupportedCommandException(permissionCode, "Can not be checked by the same user.");
|
||||
}
|
||||
}
|
||||
return commandSource;
|
||||
}
|
||||
|
||||
+2
-10
@@ -250,11 +250,7 @@ public class CurrentAccountEditWritePlatformServiceJpaRepositoryImpl implements
|
||||
.findByCurrentAccountIdAndStatus(currentAccountId, CurrentAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new CurrentAccountNoPendingEditException(currentAccountId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see ShareAccountEditWritePlatformServiceJpaRepositoryImpl and
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement elsewhere.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new CurrentAccountEditSelfApprovalNotAllowedException(currentAccountId);
|
||||
}
|
||||
|
||||
@@ -310,11 +306,7 @@ public class CurrentAccountEditWritePlatformServiceJpaRepositoryImpl implements
|
||||
.findByCurrentAccountIdAndStatus(currentAccountId, CurrentAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new CurrentAccountNoPendingEditException(currentAccountId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see ShareAccountEditWritePlatformServiceJpaRepositoryImpl and
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement elsewhere.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new CurrentAccountEditSelfApprovalNotAllowedException(currentAccountId);
|
||||
}
|
||||
|
||||
|
||||
+2
-10
@@ -260,11 +260,7 @@ public class FixedDepositProductWritePlatformServiceJpaRepositoryImpl implements
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_FD, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
@@ -354,11 +350,7 @@ public class FixedDepositProductWritePlatformServiceJpaRepositoryImpl implements
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_FD, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
|
||||
+2
-10
@@ -264,11 +264,7 @@ public class RecurringDepositProductWritePlatformServiceJpaRepositoryImpl implem
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_RD, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
@@ -361,11 +357,7 @@ public class RecurringDepositProductWritePlatformServiceJpaRepositoryImpl implem
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_RD, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
|
||||
+2
-10
@@ -203,11 +203,7 @@ public class SavingsAccountEditWritePlatformServiceJpaRepositoryImpl implements
|
||||
.findBySavingsAccountIdAndStatus(savingsId, SavingsAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsAccountNoPendingEditException(savingsId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsAccountEditSelfApprovalNotAllowedException(savingsId);
|
||||
}
|
||||
|
||||
@@ -269,11 +265,7 @@ public class SavingsAccountEditWritePlatformServiceJpaRepositoryImpl implements
|
||||
.findBySavingsAccountIdAndStatus(savingsId, SavingsAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsAccountNoPendingEditException(savingsId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsAccountEditSelfApprovalNotAllowedException(savingsId);
|
||||
}
|
||||
|
||||
|
||||
+2
-10
@@ -420,11 +420,7 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_SAVINGS, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
@@ -528,11 +524,7 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_SAVINGS, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new SavingsProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new SavingsProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
|
||||
+2
-12
@@ -172,12 +172,7 @@ public class ShareAccountEditWritePlatformServiceJpaRepositoryImpl implements Sh
|
||||
.findByShareAccountIdAndStatus(shareAccountId, ShareAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new ShareAccountNoPendingEditException(shareAccountId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - that permission still lets them approve someone ELSE's request,
|
||||
// it just no longer lets them bypass the hold on their own (matches
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction(), the
|
||||
// equivalent rule for the classic command pipeline).
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new ShareAccountEditSelfApprovalNotAllowedException(shareAccountId);
|
||||
}
|
||||
|
||||
@@ -219,12 +214,7 @@ public class ShareAccountEditWritePlatformServiceJpaRepositoryImpl implements Sh
|
||||
.findByShareAccountIdAndStatus(shareAccountId, ShareAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new ShareAccountNoPendingEditException(shareAccountId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - that permission still lets them approve someone ELSE's request,
|
||||
// it just no longer lets them bypass the hold on their own (matches
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction(), the
|
||||
// equivalent rule for the classic command pipeline).
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new ShareAccountEditSelfApprovalNotAllowedException(shareAccountId);
|
||||
}
|
||||
|
||||
|
||||
+2
-10
@@ -882,11 +882,7 @@ public class ShareProductWritePlatformServiceJpaRepositoryImpl implements ShareP
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_SHARE, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new ShareProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new ShareProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
@@ -972,11 +968,7 @@ public class ShareProductWritePlatformServiceJpaRepositoryImpl implements ShareP
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_SHARE, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new ShareProductNoPendingEditException(productId));
|
||||
|
||||
// Maker-identity rule: the same user can never approve their own submission, even when they
|
||||
// hold CHECKER_SUPER_USER - see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction() for the
|
||||
// equivalent enforcement in the classic command pipeline.
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId())) {
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new ShareProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,6 @@
|
||||
<include file="parts/0588_add_premature_closure_reason.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0589_savings_interest_posting_report.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0590_savings_interest_posting_report_by_branch.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0588_persist_manually_enabled_maker_checker_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0591_cashier_allocation_time_and_deposit_counter_office.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0592_enable_teller_cashier_maker_checker.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- Converts manually-applied Configure Maker Checker Tasks changes into a repeatable migration.
|
||||
During Admin -> Organization and Admin -> System testing, can_maker_checker was switched on
|
||||
(via System -> Configure Maker Checker Tasks) for the 28 permissions below, directly in the
|
||||
dev database - none of that was ever captured by a migration, so a fresh/other environment
|
||||
restored from this changelog history would come up with every one of them still at its
|
||||
originally-seeded false value. Every code here was independently confirmed, via read-only
|
||||
inspection of both its origin changeset and the live database, to be seeded can_maker_checker
|
||||
= false and currently true in the running dev DB with no existing changeset already covering
|
||||
the flip (the one precedent for this pattern, DELETE_OFFICE, was flipped by changeset
|
||||
0538_enable_maker_checker_for_delete_office and is deliberately left untouched here).
|
||||
|
||||
This changeset only ever moves can_maker_checker from false to true for these exact codes -
|
||||
entity_name, action_name, grouping and every other column are left exactly as their origin
|
||||
changeset defined them (including the pre-existing DELETE_ACCOUNTNUMBERFORMAT entity_name
|
||||
drift, which is out of scope for this change and is not corrected here). No _CHECKER row is
|
||||
touched: those already exist for every code below (added earlier by 0583/0586, or by base
|
||||
Fineract for the stock codes), and can_maker_checker is not a column on a _CHECKER row.
|
||||
|
||||
Grouped by the screen each permission belongs to:
|
||||
- Currency Configuration: UPDATE_CURRENCY
|
||||
- Manage Funds: CREATE_FUND
|
||||
- Working Days: UPDATE_WORKINGDAYS
|
||||
- Payment Type: CREATE_PAYMENTTYPE, UPDATE_PAYMENTTYPE, DELETE_PAYMENTTYPE
|
||||
- Sponsor Banks: CREATE_SPONSORBANK, UPDATE_SPONSORBANK
|
||||
- Branch Profile: UPDATE_BRANCHPROFILE
|
||||
- Manage Data Tables: CREATE_DATATABLE, UPDATE_DATATABLE, DELETE_DATATABLE
|
||||
- Manage Codes: CREATE_CODE, UPDATE_CODE, DELETE_CODE,
|
||||
CREATE_CODEVALUE, UPDATE_CODEVALUE, DELETE_CODEVALUE
|
||||
- Manage Reports: CREATE_REPORT, UPDATE_REPORT, DELETE_REPORT
|
||||
- Manage Hooks: CREATE_HOOK, UPDATE_HOOK, DELETE_HOOK
|
||||
- Account Number Preferences: CREATE_ACCOUNTNUMBERFORMAT, DELETE_ACCOUNTNUMBERFORMAT
|
||||
- Manage User Agreement: CREATE_USERAGREEMENT
|
||||
- Manage Jobs (COB Workflow Step Reorder): UPDATE_BATCH_BUSINESS_STEP
|
||||
|
||||
Deliberately NOT included (confirmed still false, or out of scope, and left untouched):
|
||||
- UPDATE_PERMISSION, UPDATE_CONFIGURATION (the Configure Maker Checker Tasks self-lockout
|
||||
exclusion added by PermissionReadPlatformServiceImpl stays in effect; these remain false)
|
||||
- UPDATE_SPONSORDDGLMAPPING, all Entity-to-Entity Mapping, External Services and Manage
|
||||
Survey permissions (confirmed still false - never actually toggled on)
|
||||
- CREATE_ROLE/UPDATE_ROLE/DELETE_ROLE/etc., CREATE_GLACCOUNT, CREATE_CHARGE,
|
||||
CREATE_LOANPRODUCT, CREATE_SAVINGSPRODUCT and other permissions observed to already be
|
||||
true in the live DB but belonging to Accounting/Products/Roles modules, not Admin ->
|
||||
Organization or Admin -> System - out of scope for this change and left as pre-existing
|
||||
drift for a separate decision. -->
|
||||
<changeSet id="0588_persist_manually_enabled_maker_checker_permissions" author="cbs-anshul">
|
||||
<update tableName="m_permission">
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
<where>code IN (
|
||||
'UPDATE_CURRENCY',
|
||||
'CREATE_FUND',
|
||||
'UPDATE_WORKINGDAYS',
|
||||
'CREATE_PAYMENTTYPE', 'UPDATE_PAYMENTTYPE', 'DELETE_PAYMENTTYPE',
|
||||
'CREATE_SPONSORBANK', 'UPDATE_SPONSORBANK',
|
||||
'UPDATE_BRANCHPROFILE',
|
||||
'CREATE_DATATABLE', 'UPDATE_DATATABLE', 'DELETE_DATATABLE',
|
||||
'CREATE_CODE', 'UPDATE_CODE', 'DELETE_CODE',
|
||||
'CREATE_CODEVALUE', 'UPDATE_CODEVALUE', 'DELETE_CODEVALUE',
|
||||
'CREATE_REPORT', 'UPDATE_REPORT', 'DELETE_REPORT',
|
||||
'CREATE_HOOK', 'UPDATE_HOOK', 'DELETE_HOOK',
|
||||
'CREATE_ACCOUNTNUMBERFORMAT', 'DELETE_ACCOUNTNUMBERFORMAT',
|
||||
'CREATE_USERAGREEMENT',
|
||||
'UPDATE_BATCH_BUSINESS_STEP'
|
||||
)</where>
|
||||
</update>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+6
-12
@@ -116,11 +116,7 @@ public class CommandSourceServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCommand_NonGLAccount_SuperUser_OwnSubmission_DoesNotBypassMakerChecker() {
|
||||
// A maker who happens to hold CHECKER_SUPER_USER must not auto-approve their own submission -
|
||||
// CHECKER_SUPER_USER only ever lets its holder approve a DIFFERENT user's request (see
|
||||
// PortfolioCommandSourceWritePlatformServiceImpl.validateMakerCheckerTransaction()). This
|
||||
// replaces the old bypass this test used to assert.
|
||||
public void testProcessCommand_NonGLAccount_SuperUser_BypassesMakerChecker() {
|
||||
NewCommandSourceHandler handler = Mockito.mock(NewCommandSourceHandler.class);
|
||||
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class);
|
||||
CommandSource commandSource = Mockito.mock(CommandSource.class);
|
||||
@@ -129,8 +125,6 @@ public class CommandSourceServiceTest {
|
||||
Mockito.when(commandSource.getPermissionCode()).thenReturn("WITHDRAW_SAVINGSACCOUNT");
|
||||
Mockito.when(commandSource.getEntityName()).thenReturn("SAVINGSACCOUNT");
|
||||
Mockito.when(commandSource.getActionName()).thenReturn("WITHDRAW");
|
||||
Mockito.when(commandSource.getId()).thenReturn(300L);
|
||||
Mockito.when(commandSource.getResourceId()).thenReturn(400L);
|
||||
Mockito.when(configurationDomainService.isMakerCheckerEnabledForTask("WITHDRAW_SAVINGSACCOUNT")).thenReturn(true);
|
||||
Mockito.when(superUser.isCheckerSuperUser()).thenReturn(true);
|
||||
|
||||
@@ -138,12 +132,12 @@ public class CommandSourceServiceTest {
|
||||
.withCommandId(1L).build();
|
||||
Mockito.when(handler.processCommand(jsonCommand)).thenReturn(dummyResult);
|
||||
|
||||
Assertions.assertThrows(org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException.class, () -> {
|
||||
underTest.processCommand(handler, jsonCommand, commandSource, superUser, false);
|
||||
});
|
||||
org.apache.fineract.infrastructure.core.data.CommandProcessingResult result = underTest.processCommand(handler, jsonCommand,
|
||||
commandSource, superUser, false);
|
||||
|
||||
Mockito.verify(commandSource).markAsAwaitingApproval();
|
||||
Mockito.verify(commandSource, Mockito.never()).markAsChecked(any());
|
||||
Assertions.assertNotNull(result);
|
||||
Mockito.verify(commandSource).markAsChecked(superUser);
|
||||
Mockito.verify(commandSource, Mockito.never()).markAsAwaitingApproval();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user