keycloak-aplcache

Details

diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
index 1696a1d..17550f4 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
@@ -300,10 +300,27 @@ public class ConditionalOtpFormAuthenticator extends OTPFormAuthenticator {
                     && configModel.getConfig().size() <= 1) {
                 return true;
             }
+            if (containsConditionalOtpConfig(configModel.getConfig())
+                && voteForUserOtpControlAttribute(user, configModel.getConfig()) == ABSTAIN
+                && voteForUserRole(realm, user, configModel.getConfig()) == ABSTAIN
+                && voteForHttpHeaderMatchesPattern(requestHeaders, configModel.getConfig()) == ABSTAIN
+                && (voteForDefaultFallback(configModel.getConfig()) == SHOW_OTP
+                    || voteForDefaultFallback(configModel.getConfig()) == ABSTAIN)) {
+                return true;
+            }
         }
         return false;
     }
 
+    private boolean containsConditionalOtpConfig(Map config) {
+        return config.containsKey(OTP_CONTROL_USER_ATTRIBUTE)
+            || config.containsKey(SKIP_OTP_ROLE)
+            || config.containsKey(FORCE_OTP_ROLE)
+            || config.containsKey(SKIP_OTP_FOR_HTTP_HEADER)
+            || config.containsKey(FORCE_OTP_FOR_HTTP_HEADER)
+            || config.containsKey(DEFAULT_OTP_OUTCOME);
+    }
+
     @Override
     public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
         if (!isOTPRequired(session, realm, user)) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java
index 4f192e6..969a577 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java
@@ -176,6 +176,74 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
     }
     
     @Test
+    public void conditionalOTPNoDefaultWithChecks() {
+        configureRequiredActions();
+        configureOTP();
+        //prepare config - no configuration specified
+        Map<String, String> config = new HashMap<>();
+        config.put(OTP_CONTROL_USER_ATTRIBUTE, "noSuchUserSkipAttribute");
+        config.put(SKIP_OTP_ROLE, "no_such_otp_role");
+        config.put(FORCE_OTP_ROLE, "no_such_otp_role");
+        config.put(SKIP_OTP_FOR_HTTP_HEADER, "NoSuchHost: nolocalhost:65536");
+        config.put(FORCE_OTP_FOR_HTTP_HEADER, "NoSuchHost: nolocalhost:65536");
+        setConditionalOTPForm(config);
+
+        //test OTP is required
+        testRealmAccountManagementPage.navigateTo();
+        testRealmLoginPage.form().login(testUser);
+        testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
+
+        //verify that the page is login page, not totp setup
+        assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
+    }
+
+    @Test
+    public void conditionalOTPDefaultSkipWithChecks() {
+        //prepare config - default skip
+        Map<String, String> config = new HashMap<>();
+        config.put(OTP_CONTROL_USER_ATTRIBUTE, "noSuchUserSkipAttribute");
+        config.put(SKIP_OTP_ROLE, "no_such_otp_role");
+        config.put(FORCE_OTP_ROLE, "no_such_otp_role");
+        config.put(SKIP_OTP_FOR_HTTP_HEADER, "NoSuchHost: nolocalhost:65536");
+        config.put(FORCE_OTP_FOR_HTTP_HEADER, "NoSuchHost: nolocalhost:65536");
+        config.put(DEFAULT_OTP_OUTCOME, SKIP);
+
+        setConditionalOTPForm(config);
+
+        //test OTP is skipped
+        testRealmAccountManagementPage.navigateTo();
+        testRealmLoginPage.form().login(testUser);
+        assertCurrentUrlStartsWith(testRealmAccountManagementPage);
+    }
+    
+    @Test
+    public void conditionalOTPDefaultForceWithChecks() {
+
+        //prepare config - default force
+        Map<String, String> config = new HashMap<>();
+        config.put(OTP_CONTROL_USER_ATTRIBUTE, "noSuchUserSkipAttribute");
+        config.put(SKIP_OTP_ROLE, "no_such_otp_role");
+        config.put(FORCE_OTP_ROLE, "no_such_otp_role");
+        config.put(SKIP_OTP_FOR_HTTP_HEADER, "NoSuchHost: nolocalhost:65536");
+        config.put(FORCE_OTP_FOR_HTTP_HEADER, "NoSuchHost: nolocalhost:65536");
+        config.put(DEFAULT_OTP_OUTCOME, FORCE);
+        
+        setConditionalOTPForm(config);
+        
+        //test OTP is forced
+        testRealmAccountManagementPage.navigateTo();
+        testRealmLoginPage.form().login(testUser);
+        assertTrue(loginConfigTotpPage.isCurrent());
+
+        configureOTP();
+        testRealmLoginPage.form().login(testUser);
+        testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
+
+        //verify that the page is login page, not totp setup
+        assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
+    }
+    
+    @Test
     public void conditionalOTPUserAttributeSkip() {
         //prepare config - user attribute, default to force
         Map<String, String> config = new HashMap<>();