Skip to content

Commit

Permalink
Support for the login_hint parameter in the identity-first login page
Browse files Browse the repository at this point in the history
Closes keycloak#36649

Signed-off-by: Pedro Igor <[email protected]>
  • Loading branch information
pedroigor committed Jan 22, 2025
1 parent 495c065 commit 1bede99
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.function.Predicate;
import java.util.stream.Stream;

import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationFlowContext;
Expand All @@ -54,6 +55,7 @@
import org.keycloak.organization.forms.login.freemarker.model.OrganizationAwareRealmBean;
import org.keycloak.organization.protocol.mappers.oidc.OrganizationScope;
import org.keycloak.organization.utils.Organizations;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.sessions.AuthenticationSessionModel;

public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
Expand Down Expand Up @@ -293,6 +295,7 @@ private void unknownUserChallenge(AuthenticationFlowContext context, Organizatio
}

private void initialChallenge(AuthenticationFlowContext context) {
AuthenticationSessionModel authenticationSession = context.getAuthenticationSession();
UserModel user = context.getUser();

if (user == null) {
Expand All @@ -308,8 +311,14 @@ private void initialChallenge(AuthenticationFlowContext context) {
return attributes;
});

String loginHint = authenticationSession.getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);

if (loginHint != null) {
form.setFormData(new MultivaluedHashMap<>(Map.of(UserModel.USERNAME, loginHint)));
}

context.challenge(form.createLoginUsername());
} else if (isSSOAuthentication(context.getAuthenticationSession())) {
} else if (isSSOAuthentication(authenticationSession)) {
if (shouldUserSelectOrganization(context, user)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ public void login(String username, String password) {
passwordInput.clear();
passwordInput.sendKeys(password);

clickLink(submitButton);
clickSignIn();
}

public void loginUsername(String username) {
clearUsernameInputAndWaitIfNecessary();
usernameInput.sendKeys(username);
clickLink(submitButton);
clickSignIn();
}

private void clearUsernameInputAndWaitIfNecessary() {
Expand All @@ -115,19 +115,23 @@ public void login(String password) {
passwordInput.clear();
passwordInput.sendKeys(password);

clickSignIn();
}

public void clickSignIn() {
clickLink(submitButton);
}

public void missingPassword(String username) {
clearUsernameInputAndWaitIfNecessary();
usernameInput.sendKeys(username);
passwordInput.clear();
clickLink(submitButton);
clickSignIn();
}

public void missingUsername() {
clearUsernameInputAndWaitIfNecessary();
clickLink(submitButton);
clickSignIn();
}

public String getHtmlLanguage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;

import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
Expand Down Expand Up @@ -134,4 +139,23 @@ public void testForceReAuthenticationBeforeRequiredAction() {
oauth.maxAge(null);
}
}

@Test
public void testLoginHint() {
OrganizationRepresentation organization = createOrganization();
OrganizationResource organizationResource = testRealm().organizations().get(organization.getId());
UserRepresentation member = addMember(organizationResource);

// login hint populates the username field
oauth.clientId("broker-app");
String expectedUsername = URLEncoder.encode(member.getEmail(), StandardCharsets.UTF_8);
oauth.realm(bc.consumerRealmName());
driver.navigate().to(oauth.getLoginFormUrl() + "&" + OIDCLoginProtocol.LOGIN_HINT_PARAM + "=" + expectedUsername);
assertThat(loginPage.getUsername(), Matchers.equalTo(URLDecoder.decode(expectedUsername, StandardCharsets.UTF_8)));

// continue authenticating without setting the username
loginPage.clickSignIn();
loginPage.login(memberPassword);
appPage.assertCurrent();
}
}

0 comments on commit 1bede99

Please sign in to comment.