Flow steps back when changing locale or refreshing page on 'Try another way page'

closes #30520

Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
mposolda 2024-06-21 10:04:58 +02:00 committed by Marek Posolda
parent 592c2250fc
commit 6a9e60bba0
3 changed files with 62 additions and 7 deletions

View file

@ -90,6 +90,9 @@ public class AuthenticationProcessor {
public static final String BROKER_USER_ID = "broker.user.id"; public static final String BROKER_USER_ID = "broker.user.id";
public static final String FORWARDED_PASSIVE_LOGIN = "forwarded.passive.login"; public static final String FORWARDED_PASSIVE_LOGIN = "forwarded.passive.login";
// Boolean flag, which is true when authentication-selector screen should be rendered (typically displayed when user clicked on 'try another way' link)
public static final String AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED = "auth.selector.screen.rendered";
protected static final Logger logger = Logger.getLogger(AuthenticationProcessor.class); protected static final Logger logger = Logger.getLogger(AuthenticationProcessor.class);
protected RealmModel realm; protected RealmModel realm;
protected UserSessionModel userSession; protected UserSessionModel userSession;
@ -429,7 +432,7 @@ public class AuthenticationProcessor {
this.challenge = challenge; this.challenge = challenge;
} }
@Override @Override
public void failure(AuthenticationFlowError error, Response challenge, String eventDetails, String userErrorMessage) { public void failure(AuthenticationFlowError error, Response challenge, String eventDetails, String userErrorMessage) {
this.error = error; this.error = error;
@ -1164,7 +1167,7 @@ public class AuthenticationProcessor {
if (!authenticatedUser.isEnabled()) throw new AuthenticationFlowException(AuthenticationFlowError.USER_DISABLED); if (!authenticatedUser.isEnabled()) throw new AuthenticationFlowException(AuthenticationFlowError.USER_DISABLED);
if (authenticatedUser.getServiceAccountClientLink() != null) throw new AuthenticationFlowException(AuthenticationFlowError.UNKNOWN_USER); if (authenticatedUser.getServiceAccountClientLink() != null) throw new AuthenticationFlowException(AuthenticationFlowError.UNKNOWN_USER);
} }
protected Response authenticationComplete() { protected Response authenticationComplete() {
// attachSession(); // Session will be attached after requiredActions + consents are finished. // attachSession(); // Session will be attached after requiredActions + consents are finished.
AuthenticationManager.setClientScopesInSession(authenticationSession); AuthenticationManager.setClientScopesInSession(authenticationSession);

View file

@ -93,16 +93,14 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (inputData.containsKey("tryAnotherWay")) { if (inputData.containsKey("tryAnotherWay")) {
logger.trace("User clicked on link 'Try Another Way'"); logger.trace("User clicked on link 'Try Another Way'");
List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model); processor.getAuthenticationSession().setAuthNote(AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, "true");
return createSelectAuthenticatorsScreen(model);
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, null, null);
result.setAuthenticationSelections(selectionOptions);
return result.form().createSelectAuthenticator();
} }
// check if the user has switched to a new authentication execution, and if so switch to it. // check if the user has switched to a new authentication execution, and if so switch to it.
if (authExecId != null && !authExecId.isEmpty()) { if (authExecId != null && !authExecId.isEmpty()) {
processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED);
List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model); List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model);
// Check if switch to the requested authentication execution is allowed // Check if switch to the requested authentication execution is allowed
@ -222,10 +220,35 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
} }
} }
/**
* Create screen where user can select from multiple authentication methods (Usually displayed when user clicks on 'try another way' link during authentication)
*
* @param executionModel Last execution (should be typically available in the methods)
* @return response with the screen to be displayed to the user
*/
private Response createSelectAuthenticatorsScreen(AuthenticationExecutionModel executionModel) {
List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(executionModel);
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(executionModel, null, null);
result.setAuthenticationSelections(selectionOptions);
return result.form().createSelectAuthenticator();
}
@Override @Override
public Response processFlow() { public Response processFlow() {
logger.debugf("processFlow: %s", flow.getAlias()); logger.debugf("processFlow: %s", flow.getAlias());
if (Boolean.parseBoolean(processor.getAuthenticationSession().getAuthNote(AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED))) {
logger.tracef("Refreshed page on authentication selector screen");
String lastExecutionId = processor.getAuthenticationSession().getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
if (lastExecutionId != null) {
AuthenticationExecutionModel executionModel = processor.getRealm().getAuthenticationExecutionById(lastExecutionId);
if (executionModel != null) {
return createSelectAuthenticatorsScreen(executionModel);
}
}
}
//separate flow elements into required and alternative elements //separate flow elements into required and alternative elements
List<AuthenticationExecutionModel> requiredList = new ArrayList<>(); List<AuthenticationExecutionModel> requiredList = new ArrayList<>();
List<AuthenticationExecutionModel> alternativeList = new ArrayList<>(); List<AuthenticationExecutionModel> alternativeList = new ArrayList<>();

View file

@ -159,6 +159,35 @@ public class MultiFactorAuthenticationTest extends AbstractTestRealmKeycloakTest
} }
} }
// Issue https://github.com/keycloak/keycloak/issues/30520
@Test
public void testChangingLocaleOnAuthenticationSelectorScreen() {
try {
configureBrowserFlowWithAlternativeCredentials();
loginUsernameOnlyPage.open();
loginUsernameOnlyPage.login("user-with-one-configured-otp");
passwordPage.assertCurrent();
passwordPage.assertTryAnotherWayLinkAvailability(true);
passwordPage.clickTryAnotherWayLink();
selectAuthenticatorPage.assertCurrent();
Assert.assertEquals(Arrays.asList(SelectAuthenticatorPage.PASSWORD, SelectAuthenticatorPage.AUTHENTICATOR_APPLICATION), selectAuthenticatorPage.getAvailableLoginMethods());
// Switch locale. Should be still on "selectAuthenticatorPage"
selectAuthenticatorPage.openLanguage("Deutsch");
selectAuthenticatorPage.assertCurrent();
Assert.assertEquals(Arrays.asList("Passwort", "Authenticator-Anwendung"), selectAuthenticatorPage.getAvailableLoginMethods());
// Change language back
selectAuthenticatorPage.openLanguage("English");
selectAuthenticatorPage.assertCurrent();
Assert.assertEquals(Arrays.asList(SelectAuthenticatorPage.PASSWORD, SelectAuthenticatorPage.AUTHENTICATOR_APPLICATION), selectAuthenticatorPage.getAvailableLoginMethods());
} finally {
BrowserFlowTest.revertFlows(testRealm(), "browser - alternative");
}
}
private void configureBrowserFlowWithAlternativeCredentials() { private void configureBrowserFlowWithAlternativeCredentials() {
configureBrowserFlowWithAlternativeCredentials(testingClient); configureBrowserFlowWithAlternativeCredentials(testingClient);
} }