Handle reset password flow with logged in user
Closes #8887 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
parent
00bd6224fa
commit
c6d3e56cda
4 changed files with 52 additions and 7 deletions
|
@ -71,6 +71,10 @@ public class AuthenticatorUtil {
|
||||||
return "true".equals(authSession.getAuthNote(PASSWORD_VALIDATED));
|
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
|
* Set authentication session note for callbacks defined for {@link AuthenticationFlowCallbackFactory) factories
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.authentication.AuthenticatorUtil;
|
||||||
import org.keycloak.authentication.authenticators.util.AcrStore;
|
import org.keycloak.authentication.authenticators.util.AcrStore;
|
||||||
import org.keycloak.authentication.authenticators.util.AuthenticatorUtils;
|
import org.keycloak.authentication.authenticators.util.AuthenticatorUtils;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -61,6 +62,8 @@ public class CookieAuthenticator implements Authenticator {
|
||||||
authSession.setAuthNote(AuthenticationManager.FORCED_REAUTHENTICATION, "true");
|
authSession.setAuthNote(AuthenticationManager.FORCED_REAUTHENTICATION, "true");
|
||||||
context.setForwardedInfoMessage(Messages.REAUTHENTICATE);
|
context.setForwardedInfoMessage(Messages.REAUTHENTICATE);
|
||||||
context.attempted();
|
context.attempted();
|
||||||
|
} else if(AuthenticatorUtil.isForkedFlow(authSession)){
|
||||||
|
context.attempted();
|
||||||
} else {
|
} else {
|
||||||
int previouslyAuthenticatedLevel = acrStore.getHighestAuthenticatedLevelFromPreviousAuthentication();
|
int previouslyAuthenticatedLevel = acrStore.getHighestAuthenticatedLevelFromPreviousAuthentication();
|
||||||
AuthenticatorUtils.updateCompletedExecutions(context.getAuthenticationSession(), authResult.getSession(), context.getExecution().getId());
|
AuthenticatorUtils.updateCompletedExecutions(context.getAuthenticationSession(), authResult.getSession(), context.getExecution().getId());
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
|
@ -75,6 +76,15 @@ public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFa
|
||||||
return;
|
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();
|
Response challenge = context.form().createPasswordReset();
|
||||||
context.challenge(challenge);
|
context.challenge(challenge);
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,30 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
||||||
String username = "login-test";
|
String username = "login-test";
|
||||||
String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials";
|
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);
|
AccountHelper.logout(testRealm(), username);
|
||||||
WaitUtils.waitForPageToLoad();
|
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.
|
// 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";
|
String username = "login-test";
|
||||||
driver.navigate().to(resetUri);
|
driver.navigate().to(resetUri);
|
||||||
|
|
||||||
resetPasswordPage.assertCurrent();
|
if (!userAuthenticated) {
|
||||||
|
resetPasswordPage.assertCurrent();
|
||||||
resetPasswordPage.changePassword(username);
|
resetPasswordPage.changePassword(username);
|
||||||
|
}
|
||||||
|
|
||||||
loginPage.assertCurrent();
|
loginPage.assertCurrent();
|
||||||
assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
|
assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
|
||||||
|
@ -208,6 +232,10 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
updatePasswordPage.assertCurrent();
|
updatePasswordPage.assertCurrent();
|
||||||
|
|
||||||
|
if(userAuthenticated) {
|
||||||
|
updatePasswordPage.uncheckLogoutSessions();
|
||||||
|
}
|
||||||
|
|
||||||
updatePasswordPage.changePassword("resetPassword", "resetPassword");
|
updatePasswordPage.changePassword("resetPassword", "resetPassword");
|
||||||
|
|
||||||
event = events.expectRequiredAction(EventType.UPDATE_PASSWORD)
|
event = events.expectRequiredAction(EventType.UPDATE_PASSWORD)
|
||||||
|
@ -225,7 +253,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
||||||
public void resetPasswordLinkTestAppWithoutRedirectUriParam() throws IOException {
|
public void resetPasswordLinkTestAppWithoutRedirectUriParam() throws IOException {
|
||||||
String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials?client_id=test-app";
|
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"
|
// Link "Back to application" with the baseUrl of client "test-app"
|
||||||
infoPage.assertCurrent();
|
infoPage.assertCurrent();
|
||||||
|
@ -240,7 +268,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
||||||
public void resetPasswordLinkTestAppWithRedirectUriParam() throws IOException {
|
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();
|
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
|
// Should be directly redirected to "application because of "redirect_uri" parameter
|
||||||
appPage.assertCurrent();
|
appPage.assertCurrent();
|
||||||
|
|
Loading…
Reference in a new issue