Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;

import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
import static org.keycloak.services.validation.Validation.FIELD_PASSWORD;
import static org.keycloak.services.validation.Validation.FIELD_USERNAME;

Expand Down Expand Up @@ -200,14 +199,7 @@ private boolean validateUser(AuthenticationFlowContext context, UserModel user,
if (!enabledUser(context, user)) {
return false;
}
String rememberMe = inputData.getFirst("rememberMe");
boolean remember = context.getRealm().isRememberMe() && rememberMe != null && rememberMe.equalsIgnoreCase("on");
if (remember) {
context.getAuthenticationSession().setAuthNote(Details.REMEMBER_ME, "true");
context.getEvent().detail(Details.REMEMBER_ME, "true");
} else {
context.getAuthenticationSession().removeAuthNote(Details.REMEMBER_ME);
}
AuthenticatorUtils.processRememberMe(context, inputData);
context.setUser(user);
return true;
}
Expand Down Expand Up @@ -249,7 +241,7 @@ private boolean badPasswordHandler(AuthenticationFlowContext context, UserModel
}

protected boolean isDisabledByBruteForce(AuthenticationFlowContext context, UserModel user) {
String bruteForceError = getDisabledByBruteForceEventError(context, user);
String bruteForceError = AuthenticatorUtils.getDisabledByBruteForceEventError(context, user);
if (bruteForceError != null) {
context.getEvent().user(user);
context.getEvent().error(bruteForceError);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.authenticators.util.AuthenticatorUtils;
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.WebAuthnPasswordlessCredentialProvider;
Expand Down Expand Up @@ -112,6 +113,11 @@ public void action(AuthenticationFlowContext context) {
return;
}

// process rememberMe if present
if (formData.containsKey("rememberMe")) {
AuthenticatorUtils.processRememberMe(context, formData);
}

// user selected a webauthn credential, proceed with webauthn authentication
super.action(context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
import java.io.IOException;
import java.util.Map;

import jakarta.ws.rs.core.MultivaluedMap;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.common.util.Time;
import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.Constants;
Expand Down Expand Up @@ -138,4 +141,22 @@ public static void setupReauthenticationInUsernamePasswordFormError(Authenticati
}
}

/**
* Process the <em>rememberMe</em> input for authentication. If the inputData contains
* the <em>rememberMe</em> attribute set to <em>on</em> and the realm is
* configured with the rememberMe option, the auth note is added to the
* authentication session; otherwise, the note is removed from the auth session.
* @param context The flow context
* @param inputData The form data
*/
public static void processRememberMe(AuthenticationFlowContext context, MultivaluedMap<String, String> inputData) {
Comment thread
rmartinc marked this conversation as resolved.
String rememberMe = inputData.getFirst("rememberMe");
boolean remember = context.getRealm().isRememberMe() && rememberMe != null && rememberMe.equalsIgnoreCase("on");
if (remember) {
context.getAuthenticationSession().setAuthNote(Details.REMEMBER_ME, "true");
context.getEvent().detail(Details.REMEMBER_ME, "true");
} else {
context.getAuthenticationSession().removeAuthNote(Details.REMEMBER_ME);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,12 @@ public EventAssertion withoutDetails(String... keys) {
return this;
}

/**
* Return the event associated to the assertion.
*
* @return the asserted {@link EventRepresentation}
*/
public EventRepresentation getEvent() {
return event;
}
Comment thread
mabartos marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,11 @@ public RealmConfigBuilder webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegist
return this;
}

public RealmConfigBuilder webAuthnPolicyPasswordlessPasskeysEnabled(Boolean enabled) {
rep.setWebAuthnPolicyPasswordlessPasskeysEnabled(enabled);
return this;
}

public RealmConfigBuilder webAuthnPolicyAcceptableAaguids(List<String> aaguids) {
rep.setWebAuthnPolicyAcceptableAaguids(aaguids);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.keycloak.testframework.ui.page;

import java.util.Optional;

import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;

import org.openqa.selenium.By;
Expand All @@ -41,6 +43,12 @@ public abstract class AbstractLoginPage extends AbstractPage {
@FindBy(id = "kc-attempted-username") // Username during re-authentication
private WebElement attemptedUsernameLabel;

@FindBy(className = "pf-m-info")
private WebElement loginInfoMessage;

@FindBy(className = "pf-m-danger")
private WebElement loginErrorMessage;

public AbstractLoginPage(ManagedWebDriver driver) {
super(driver);
}
Expand Down Expand Up @@ -76,4 +84,19 @@ public String getAttemptedUsername() {
}
}

public Optional<String> getInfoMessage() {
try {
return Optional.of(loginInfoMessage.getText());
} catch (NoSuchElementException e) {
return Optional.empty();
}
}

public Optional<String> getErrorMessage() {
try {
return Optional.of(loginErrorMessage.getText());
} catch (NoSuchElementException e) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.keycloak.testframework.ui.page;

import java.util.Optional;

import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;

import org.openqa.selenium.By;
Expand Down Expand Up @@ -30,8 +32,8 @@ public class LoginPage extends AbstractLoginPage {
@FindBy(id = "input-error-username")
private WebElement userNameInputError;

@FindBy(className = "pf-m-danger")
private WebElement loginErrorMessage;
@FindBy(id = "input-error-password")
private WebElement passwordInputError;

public LoginPage(ManagedWebDriver driver) {
super(driver);
Expand All @@ -44,6 +46,11 @@ public void fillLogin(String username, String password) {
passwordInput.sendKeys(password);
}

public void fillPassword(String password) {
passwordInput.clear();
passwordInput.sendKeys(password);
}

public void submit() {
submitButton.click();
}
Expand Down Expand Up @@ -86,6 +93,10 @@ public String getUsername() {
return usernameInput.getAttribute("value");
}

public String getUsernameAutocomplete() {
return usernameInput.getDomAttribute("autocomplete");
}

public void clearUsernameInput() {
usernameInput.clear();
}
Expand All @@ -98,12 +109,11 @@ public String getUsernameInputError() {
}
}

public String getError() {
public Optional<String> getPasswordInputError() {
try {
return loginErrorMessage.getText();
return Optional.of(passwordInputError.getText());
} catch (NoSuchElementException e) {
return null;
return Optional.empty();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;

import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

Expand All @@ -13,14 +14,37 @@ public class LoginUsernamePage extends AbstractLoginPage {
@FindBy(css = "[type=submit]")
private WebElement submitButton;

@FindBy(id = "input-error-username")
private WebElement userNameInputError;

@FindBy(id = "rememberMe")
private WebElement rememberMe;

public LoginUsernamePage(ManagedWebDriver driver) {
super(driver);
}

public void fillLoginWithUsernameOnly(String username) {
usernameInput.clear();
usernameInput.sendKeys(username);
}

public String getUsername() {
return usernameInput.getAttribute("value");
}

public String getUsernameAutocomplete() {
return usernameInput.getDomAttribute("autocomplete");
}

public String getUsernameInputError() {
try {
return userNameInputError.getText();
} catch (NoSuchElementException e) {
return null;
}
}

public void submit() {
submitButton.click();
}
Expand All @@ -29,4 +53,15 @@ public void submit() {
public String getExpectedPageId() {
return "login-login-username";
}

public void rememberMe(boolean value) {
boolean selected = isRememberMe();
if ((value && !selected) || !value && selected) {
rememberMe.click();
}
}

public boolean isRememberMe() {
return rememberMe.isSelected();
Comment thread
mabartos marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ public void testTimeoutWhenReUsingPreviousAuthenticationSession() {
oauth.openLoginForm();
driver.cookies().add(authSessionCookie);
oauth.fillLoginForm("bob", "bob");
Assert.assertEquals("Your login attempt timed out. Login will start from the beginning.", loginPage.getError());
Assert.assertEquals("Your login attempt timed out. Login will start from the beginning.", loginPage.getErrorMessage().orElse(null));
} finally {
realmResource.remove();
oauth.realm(origRealm);
Expand Down
Loading
Loading