From 593afbb4e0a34e978b7b613108875a14f3e8fc7c Mon Sep 17 00:00:00 2001 From: Gilles Etchepareborde Date: Thu, 20 Jun 2024 15:06:59 +0200 Subject: [PATCH] This PR intends to always set the event type in order to prevent error when firing an error event. Closes #30453 Signed-off-by: Gilles Etchepareborde --- .../resources/LoginActionsService.java | 3 +- .../forms/ErrorEventAuthenticator.java | 123 ++++++++++++++++++ ...ycloak.authentication.AuthenticatorFactory | 1 + .../admin/authentication/ProvidersTest.java | 4 +- ...rrorEventOnCustomRegistrationFlowTest.java | 86 ++++++++++++ 5 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/ErrorEventAuthenticator.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ErrorEventOnCustomRegistrationFlowTest.java diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 422ad68deb..017878c608 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -446,10 +446,10 @@ public class LoginActionsService { AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm, client, tabId); processLocaleParam(authSession); + event.event(EventType.RESET_PASSWORD); // we allow applications to link to reset credentials without going through OAuth or SAML handshakes if (authSession == null && code == null && clientData == null) { if (!realm.isResetPasswordAllowed()) { - event.event(EventType.RESET_PASSWORD); event.error(Errors.NOT_ALLOWED); return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.RESET_CREDENTIAL_NOT_ALLOWED); @@ -458,7 +458,6 @@ public class LoginActionsService { return processResetCredentials(false, null, authSession, null); } - event.event(EventType.RESET_PASSWORD); return resetCredentials(authSessionId, code, execution, clientId, tabId, clientData); } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/ErrorEventAuthenticator.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/ErrorEventAuthenticator.java new file mode 100644 index 0000000000..e5f42ee79a --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/ErrorEventAuthenticator.java @@ -0,0 +1,123 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.forms; + +import org.keycloak.Config; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.List; + +public class ErrorEventAuthenticator implements Authenticator, AuthenticatorFactory { + public static final String PROVIDER_ID = "test-suite-fire-error-event"; + public static final String ERROR_MESSAGE = "fire-error-event"; + public static final String FAKE_USERID = "fake-userid"; + @Override + public void authenticate(AuthenticationFlowContext context) { + context.getEvent().user(FAKE_USERID); + context.getEvent().error(ERROR_MESSAGE); + + context.success(); + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + + } + + @Override + public void action(AuthenticationFlowContext context) { + } + + @Override + public String getDisplayType() { + return "Fire Error Event"; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public boolean isConfigurable() { + return false; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + + @Override + public String getHelpText() { + return "Testsuite Error event firer authenticator."; + } + + @Override + public List getConfigProperties() { + return null; + } + + @Override + public void close() { + + } + + @Override + public Authenticator create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory index 379af6eefe..19171bcf1c 100755 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -19,6 +19,7 @@ org.keycloak.testsuite.forms.PassThroughAuthenticator org.keycloak.testsuite.forms.SetClientNoteAuthenticator org.keycloak.testsuite.forms.PassThroughRegistration org.keycloak.testsuite.forms.ClickThroughAuthenticator +org.keycloak.testsuite.forms.ErrorEventAuthenticator org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory org.keycloak.testsuite.forms.UsernameOnlyAuthenticator diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java index 000ddc761a..37895fa71e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java @@ -198,6 +198,8 @@ public class ProvidersTest extends AbstractAuthenticationTest { addProviderInfo(result, "set-client-note-authenticator", "Set Client Note Authenticator", "Set client note of specified name with the specified value to the authenticationSession."); addProviderInfo(result, "testsuite-username", "Testsuite Username Only", "Testsuite Username authenticator. Username parameter sets username"); + addProviderInfo(result, "test-suite-fire-error-event", "Fire Error Event", + "Testsuite Error event firer authenticator."); addProviderInfo(result, "webauthn-authenticator", "WebAuthn Authenticator", "Authenticator for WebAuthn. Usually used for WebAuthn two-factor authentication"); addProviderInfo(result, "webauthn-authenticator-passwordless", "WebAuthn Passwordless Authenticator", "Authenticator for Passwordless WebAuthn authentication"); @@ -223,7 +225,7 @@ public class ProvidersTest extends AbstractAuthenticationTest { addProviderInfo(result, "conditional-level-of-authentication", "Condition - Level of Authentication", "Flow is executed only if the configured LOA or a higher one has been requested but not yet satisfied. After the flow is successfully finished, the LOA in the session will be updated to value prescribed by this condition."); - + addProviderInfo(result, "user-session-limits", "User session count limiter", "Configures how many concurrent sessions a single user is allowed to create for this realm and/or client"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ErrorEventOnCustomRegistrationFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ErrorEventOnCustomRegistrationFlowTest.java new file mode 100644 index 0000000000..12e7af5606 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ErrorEventOnCustomRegistrationFlowTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.forms; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.events.EventType; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.pages.LoginPasswordResetPage; +import org.keycloak.testsuite.util.FlowUtil; + + +public class ErrorEventOnCustomRegistrationFlowTest extends AbstractFlowTest { + + @Override + public void configureTestRealm(RealmRepresentation testRealm) {} + + @Before + public void setup() { + } + + @Page + protected LoginPasswordResetPage resetPasswordPage; + + @Rule + public AssertEvents events = new AssertEvents(this); + + // 30453 + @Test + public void resetLinkWithErrorEventDoNotFail() { + configureResetCredentialsFlow(); + + String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials?client_id=test-app"; + + driver.navigate().to(resetUri); + + events.expect(EventType.RESET_PASSWORD) + .error(ErrorEventAuthenticator.ERROR_MESSAGE) + .user(ErrorEventAuthenticator.FAKE_USERID) + .assertEvent(); + + resetPasswordPage.assertCurrent(); + + } + + private void configureResetCredentialsFlow() { + String newFlowAlias = "reset-credentials-custom"; + testingClient.server("test").run(session -> { + // Create a copy of the default reset credentials flow with the specified flow alias if it doesn't exist yet + if(session.getContext().getRealm().getFlowByAlias(newFlowAlias) == null) { + FlowUtil.inCurrentRealm(session).copyResetCredentialsFlow(newFlowAlias); + } + }); + + // add the custom the execution(s) + testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session) + .selectFlow(newFlowAlias) + .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, ErrorEventAuthenticator.PROVIDER_ID, 5) + ); + + // Bind the flow as the reset-credentials one + testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session) + .selectFlow(newFlowAlias) + .defineAsResetCredentialsFlow() + ); + } + +}