diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java index 7eba2e2ef1..c678cb33db 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java @@ -71,6 +71,10 @@ public class AuthenticatorUtil { return "true".equals(authSession.getAuthNote(PASSWORD_VALIDATED)); } + public static boolean isForkedFlow(AuthenticationSessionModel authSession) { + return authSession.getAuthNote(AuthenticationProcessor.FORKED_FROM) != null; + } + /** * Set authentication session note for callbacks defined for {@link AuthenticationFlowCallbackFactory) factories * diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java index 7171494827..295999b29f 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java @@ -19,6 +19,7 @@ package org.keycloak.authentication.authenticators.browser; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.authentication.authenticators.util.AcrStore; import org.keycloak.authentication.authenticators.util.AuthenticatorUtils; import org.keycloak.models.Constants; @@ -61,6 +62,8 @@ public class CookieAuthenticator implements Authenticator { authSession.setAuthNote(AuthenticationManager.FORCED_REAUTHENTICATION, "true"); context.setForwardedInfoMessage(Messages.REAUTHENTICATE); context.attempted(); + } else if(AuthenticatorUtil.isForkedFlow(authSession)){ + context.attempted(); } else { int previouslyAuthenticatedLevel = acrStore.getHighestAuthenticatedLevelFromPreviousAuthentication(); AuthenticatorUtils.updateCompletedExecutions(context.getAuthenticationSession(), authResult.getSession(), context.getExecution().getId()); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java index 33ab4b0fec..6e268655c1 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java @@ -33,6 +33,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.FormMessage; import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.messages.Messages; import org.keycloak.services.validation.Validation; @@ -75,6 +76,15 @@ public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFa return; } + AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(context.getSession(), context.getRealm(), true); + //skip user choice if sso session exists + if (authResult != null) { + context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, authResult.getUser().getUsername()); + context.setUser(authResult.getUser()); + context.success(); + return; + } + Response challenge = context.form().createPasswordReset(); context.challenge(challenge); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index d706f85b2f..eab356585c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -160,7 +160,30 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { String username = "login-test"; String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials"; - openResetPasswordUrlAndDoFlow(resetUri, "account", oauth.AUTH_SERVER_ROOT + "/realms/test/account/"); + openResetPasswordUrlAndDoFlow(resetUri, "account", oauth.AUTH_SERVER_ROOT + "/realms/test/account/", false); + + AccountHelper.logout(testRealm(), username); + WaitUtils.waitForPageToLoad(); + + TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage); + testAppHelper.login(username, "resetPassword"); + + appPage.assertCurrent(); + + assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + } + + @Test + public void resetPasswordLoggedUser() throws IOException { + String username = "login-test"; + loginPage.open(); + loginPage.login(username, "password"); + + events.expectLogin().user(userId).detail(Details.USERNAME, username).assertEvent(); + + String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials"; + + openResetPasswordUrlAndDoFlow(resetUri, "account", oauth.AUTH_SERVER_ROOT + "/realms/test/account/", true); AccountHelper.logout(testRealm(), username); WaitUtils.waitForPageToLoad(); @@ -174,13 +197,14 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { } // Starts by opening "reset-password-url". Then go through the successful reset-password flow for the particular user. After user confirms new password, this method ends. - private void openResetPasswordUrlAndDoFlow(String resetUri, String expectedClientId, String expectedRedirectUri) throws IOException { + private void openResetPasswordUrlAndDoFlow(String resetUri, String expectedClientId, String expectedRedirectUri, boolean userAuthenticated) throws IOException { String username = "login-test"; driver.navigate().to(resetUri); - resetPasswordPage.assertCurrent(); - - resetPasswordPage.changePassword(username); + if (!userAuthenticated) { + resetPasswordPage.assertCurrent(); + resetPasswordPage.changePassword(username); + } loginPage.assertCurrent(); assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage()); @@ -208,6 +232,10 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { updatePasswordPage.assertCurrent(); + if(userAuthenticated) { + updatePasswordPage.uncheckLogoutSessions(); + } + updatePasswordPage.changePassword("resetPassword", "resetPassword"); event = events.expectRequiredAction(EventType.UPDATE_PASSWORD) @@ -225,7 +253,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { public void resetPasswordLinkTestAppWithoutRedirectUriParam() throws IOException { String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials?client_id=test-app"; - openResetPasswordUrlAndDoFlow(resetUri, "test-app", null); + openResetPasswordUrlAndDoFlow(resetUri, "test-app", null, false); // Link "Back to application" with the baseUrl of client "test-app" infoPage.assertCurrent(); @@ -240,7 +268,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { public void resetPasswordLinkTestAppWithRedirectUriParam() throws IOException { String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials?client_id=test-app&redirect_uri=" + oauth.getRedirectUri(); - openResetPasswordUrlAndDoFlow(resetUri, "test-app", oauth.getRedirectUri()); + openResetPasswordUrlAndDoFlow(resetUri, "test-app", oauth.getRedirectUri(), false); // Should be directly redirected to "application because of "redirect_uri" parameter appPage.assertCurrent();