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

Reverts commits 2afd4aaf8 and 11a0d502b (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 by
82336cbe9 after 11a0d502b landed, 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:
2026-10-06 10:02:34 +05:30
co-authored by Claude Sonnet 5
parent 760fff8d3e
commit 88157ee9d6
12 changed files with 48 additions and 188 deletions
@@ -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;
@@ -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;
}