merge code feature/Backend-DSC-MFA this branch date 15-05-2026
This commit is contained in:
+6
-20
@@ -85,7 +85,6 @@ public class AuthenticationApiResource {
|
||||
private final ClientReadPlatformService clientReadPlatformService;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final UserMfaFactorRepository userMfaFactorRepository;
|
||||
private final org.apache.fineract.useradministration.dsc.domain.BankUserDscRepository bankUserDscRepository;
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -148,26 +147,13 @@ public class AuthenticationApiResource {
|
||||
// safe rollout (the per-user gate naturally lets unenrolled users in) and instant
|
||||
// rollback (flip the c_configuration row off and everyone is unchallenged).
|
||||
final boolean enforcementEnabled = configurationDomainService.isMfaFactorEnforcementEnabled();
|
||||
// DSC factor check is wrapped so a misconfigured/missing m_bank_user_dsc table can't
|
||||
// bring down the entire login path. If the lookup throws (table missing, columns
|
||||
// renamed, JDBC down), we log and fall back to "no DSC factor enrolled" — the user
|
||||
// can still authenticate via their OTP factors (or no 2FA if none enrolled).
|
||||
boolean hasActiveDsc = false;
|
||||
if (enforcementEnabled) {
|
||||
try {
|
||||
hasActiveDsc = bankUserDscRepository
|
||||
.existsByAppUser_IdAndActiveTrueAndDeletedFalseAndCertStatusAndDscExpiryDateGreaterThanEqual(
|
||||
principal.getId(),
|
||||
org.apache.fineract.useradministration.dsc.domain.CertStatus.ACTIVE,
|
||||
java.time.LocalDate.now());
|
||||
} catch (final Exception e) {
|
||||
log.warn("DSC factor lookup failed for user {} during login — falling back to non-DSC: {}",
|
||||
principal.getUsername(), e.getMessage());
|
||||
}
|
||||
}
|
||||
// m_user_mfa_factor is the single source of truth for "does this user require 2FA?"
|
||||
// DSC enrolment writes a row there via DscEnrollmentWriteServiceImpl, and revoking the
|
||||
// DSC or disabling it from the Access Control UI flips disabled_on on the same row.
|
||||
// This keeps the disable flow honest — flipping a factor off in the UI actually stops
|
||||
// the challenge at login, instead of being silently overridden by a stale cert mapping.
|
||||
final boolean userHasFactors = enforcementEnabled
|
||||
&& (userMfaFactorRepository.existsByAppUser_IdAndVerifiedTrueAndDisabledOnIsNull(principal.getId())
|
||||
|| hasActiveDsc);
|
||||
&& userMfaFactorRepository.existsByAppUser_IdAndVerifiedTrueAndDisabledOnIsNull(principal.getId());
|
||||
final boolean isTwoFactorRequired = this.twoFactorEnabled && enforcementEnabled && userHasFactors
|
||||
&& !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
|
||||
Long userId = principal.getId();
|
||||
|
||||
+5
-18
@@ -63,7 +63,6 @@ public class TwoFactorAuthenticationFilter extends GenericFilterBean {
|
||||
private final TwoFactorService twoFactorService;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final UserMfaFactorRepository userMfaFactorRepository;
|
||||
private final org.apache.fineract.useradministration.dsc.domain.BankUserDscRepository bankUserDscRepository;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
@@ -93,24 +92,12 @@ public class TwoFactorAuthenticationFilter extends GenericFilterBean {
|
||||
// has no enrolled factors (or the global kill-switch is off).
|
||||
final boolean hasBypass = user.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
|
||||
final boolean enforcementOn = configurationDomainService.isMfaFactorEnforcementEnabled();
|
||||
// DSC factor check wrapped: a misconfigured/missing m_bank_user_dsc must not bring
|
||||
// down every protected request. Fall back to "no DSC factor" if the lookup throws.
|
||||
boolean hasActiveDsc = false;
|
||||
if (enforcementOn) {
|
||||
try {
|
||||
hasActiveDsc = bankUserDscRepository
|
||||
.existsByAppUser_IdAndActiveTrueAndDeletedFalseAndCertStatusAndDscExpiryDateGreaterThanEqual(
|
||||
user.getId(),
|
||||
org.apache.fineract.useradministration.dsc.domain.CertStatus.ACTIVE,
|
||||
java.time.LocalDate.now());
|
||||
} catch (final Exception e) {
|
||||
log.warn("DSC factor lookup failed in 2FA filter for user {} — ignoring: {}",
|
||||
user.getUsername(), e.getMessage());
|
||||
}
|
||||
}
|
||||
// m_user_mfa_factor is the single source of truth — mirrors AuthenticationApiResource.
|
||||
// Disabling a factor row from the UI flips disabled_on, which makes this check fall
|
||||
// through to "no challenge needed". Without that single-table behaviour the filter and
|
||||
// the login response would disagree, leaving users stuck in a 2FA loop.
|
||||
final boolean userHasFactors = enforcementOn
|
||||
&& (userMfaFactorRepository.existsByAppUser_IdAndVerifiedTrueAndDisabledOnIsNull(user.getId())
|
||||
|| hasActiveDsc);
|
||||
&& userMfaFactorRepository.existsByAppUser_IdAndVerifiedTrueAndDisabledOnIsNull(user.getId());
|
||||
final boolean shouldChallenge = !hasBypass && enforcementOn && userHasFactors;
|
||||
|
||||
if (shouldChallenge) {
|
||||
|
||||
Reference in New Issue
Block a user