From d053b9afd67bc94a9ea8e3ba7da5ec2f6372dd21 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 7 Jul 2025 09:50:28 +0200 Subject: [PATCH] Do not add steps if feature disabled in default flows Allow login if a step is disabled even the authenticator is not enabled by profile Closes #40954 Signed-off-by: rmartinc --- .../utils/DefaultAuthenticationFlows.java | 64 +++++++++++-------- .../DefaultAuthenticationFlow.java | 3 +- .../keycloak/testsuite/forms/LoginTest.java | 13 ++++ 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index ae88b8ae70c2..dc9d60647a85 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -387,22 +387,26 @@ public static void browserFlow(RealmModel realm, boolean migrate) { realm.addAuthenticatorExecution(execution); // webauthn as disabled - execution = new AuthenticationExecutionModel(); - execution.setParentFlow(conditionalOTP.getId()); - execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); - execution.setAuthenticator("webauthn-authenticator"); - execution.setPriority(30); - execution.setAuthenticatorFlow(false); - realm.addAuthenticatorExecution(execution); + if (Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN)) { + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(conditionalOTP.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); + execution.setAuthenticator("webauthn-authenticator"); + execution.setPriority(30); + execution.setAuthenticatorFlow(false); + realm.addAuthenticatorExecution(execution); + } // recovery-codes as disabled - execution = new AuthenticationExecutionModel(); - execution.setParentFlow(conditionalOTP.getId()); - execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); - execution.setAuthenticator("auth-recovery-authn-code-form"); - execution.setPriority(40); - execution.setAuthenticatorFlow(false); - realm.addAuthenticatorExecution(execution); + if (Profile.isFeatureEnabled(Profile.Feature.RECOVERY_CODES)) { + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(conditionalOTP.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); + execution.setAuthenticator("auth-recovery-authn-code-form"); + execution.setPriority(40); + execution.setAuthenticatorFlow(false); + realm.addAuthenticatorExecution(execution); + } addOrganizationBrowserFlowStep(realm, browser); } @@ -669,22 +673,26 @@ public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) { realm.addAuthenticatorExecution(execution); // webauthn as disabled - execution = new AuthenticationExecutionModel(); - execution.setParentFlow(conditionalOTP.getId()); - execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); - execution.setAuthenticator("webauthn-authenticator"); - execution.setPriority(30); - execution.setAuthenticatorFlow(false); - realm.addAuthenticatorExecution(execution); + if (Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN)) { + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(conditionalOTP.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); + execution.setAuthenticator("webauthn-authenticator"); + execution.setPriority(30); + execution.setAuthenticatorFlow(false); + realm.addAuthenticatorExecution(execution); + } // recovery-codes as disabled - execution = new AuthenticationExecutionModel(); - execution.setParentFlow(conditionalOTP.getId()); - execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); - execution.setAuthenticator("auth-recovery-authn-code-form"); - execution.setPriority(40); - execution.setAuthenticatorFlow(false); - realm.addAuthenticatorExecution(execution); + if (Profile.isFeatureEnabled(Profile.Feature.RECOVERY_CODES)) { + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(conditionalOTP.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); + execution.setAuthenticator("auth-recovery-authn-code-form"); + execution.setPriority(40); + execution.setAuthenticatorFlow(false); + realm.addAuthenticatorExecution(execution); + } addOrganizationFirstBrokerFlowStep(realm, firstBrokerLogin); } diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java index dd53d3d8d5b4..8451f8d79752 100755 --- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java +++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java @@ -357,7 +357,8 @@ boolean isConditionalSubflowDisabled(AuthenticationExecutionModel model) { } private boolean isConditionalAuthenticator(AuthenticationExecutionModel model) { - return !model.isAuthenticatorFlow() && model.getAuthenticator() != null && createAuthenticator(getAuthenticatorFactory(model)) instanceof ConditionalAuthenticator; + return !model.isAuthenticatorFlow() && model.getAuthenticator() != null && model.isEnabled() + && createAuthenticator(getAuthenticatorFactory(model)) instanceof ConditionalAuthenticator; } private AuthenticatorFactory getAuthenticatorFactory(AuthenticationExecutionModel model) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java index 1691da805383..5546c07ced7b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java @@ -1005,6 +1005,19 @@ public void loginSuccessfulWithDynamicScope() { events.expectLogin().user(userId).assertEvent(); } + @Test + public void loginSuccessfulWithoutWebAuthn() { + testingClient.disableFeature(Profile.Feature.WEB_AUTHN); + try { + loginPage.open(); + loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + events.expectLogin().assertEvent(); + } finally { + testingClient.enableFeature(Profile.Feature.WEB_AUTHN); + } + } + @Test public void testExecuteActionIfSessionExists() { loginPage.open();