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<>();