Skip to content

Commit

Permalink
Automatically fill username when authenticating to through a broker
Browse files Browse the repository at this point in the history
Closes keycloak#28848

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
  • Loading branch information
pedroigor committed Apr 17, 2024
1 parent 1e38374 commit c8a92f8
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ public void authenticate(AuthenticationFlowContext context) {
}

protected void redirect(AuthenticationFlowContext context, String providerId) {
redirect(context, providerId, null);
}

protected void redirect(AuthenticationFlowContext context, String providerId, String loginHint) {
Optional<IdentityProviderModel> idp = context.getRealm().getIdentityProvidersStream()
.filter(IdentityProviderModel::isEnabled)
.filter(identityProvider -> Objects.equals(providerId, identityProvider.getAlias()))
Expand All @@ -84,7 +88,7 @@ protected void redirect(AuthenticationFlowContext context, String providerId) {
String clientId = context.getAuthenticationSession().getClient().getClientId();
String tabId = context.getAuthenticationSession().getTabId();
String clientData = AuthenticationProcessor.getClientData(context.getSession(), context.getAuthenticationSession());
URI location = Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId, tabId, clientData);
URI location = Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId, tabId, clientData, loginHint);
Response response = Response.seeOther(location)
.build();
// will forward the request to the IDP with prompt=none if the IDP accepts forwards with prompt=none.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public void action(AuthenticationFlowContext context) {
}

String domain = getEmailDomain(username);

if (domain == null) {
context.attempted();
return;
}

OrganizationProvider provider = getOrganizationProvider();
OrganizationModel organization = provider.getByDomainName(domain);

Expand All @@ -74,7 +80,7 @@ public void action(AuthenticationFlowContext context) {
return;
}

redirect(context, identityProvider.getAlias());
redirect(context, identityProvider.getAlias(), username);
}

private OrganizationProvider getOrganizationProvider() {
Expand Down
7 changes: 5 additions & 2 deletions services/src/main/java/org/keycloak/services/Urls.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static URI identityProviderAuthnResponse(URI baseUri, String providerAlia
.build(realmName, providerAlias);
}

public static URI identityProviderAuthnRequest(URI baseUri, String providerAlias, String realmName, String accessCode, String clientId, String tabId, String clientData) {
public static URI identityProviderAuthnRequest(URI baseUri, String providerAlias, String realmName, String accessCode, String clientId, String tabId, String clientData, String loginHint) {
UriBuilder uriBuilder = realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
.path(IdentityBrokerService.class, "performLogin");

Expand All @@ -68,6 +68,9 @@ public static URI identityProviderAuthnRequest(URI baseUri, String providerAlias
if (clientData != null) {
uriBuilder.replaceQueryParam(Constants.CLIENT_DATA, clientData);
}
if (loginHint != null) {
uriBuilder.replaceQueryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
}

return uriBuilder.build(realmName, providerAlias);
}
Expand All @@ -87,7 +90,7 @@ public static URI identityProviderRetrieveToken(URI baseUri, String providerAlia
}

public static URI identityProviderAuthnRequest(URI baseURI, String providerAlias, String realmName) {
return identityProviderAuthnRequest(baseURI, providerAlias, realmName, null, null, null, null);
return identityProviderAuthnRequest(baseURI, providerAlias, realmName, null, null, null, null, null);
}

public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId, String tabId, String clientData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile.Feature;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
Expand Down Expand Up @@ -56,6 +58,29 @@ public void testBrokerRegistration() {
assertBrokerRegistration(organization);
}

@Test
public void testLoginHint() {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
IdentityProviderRepresentation idp = organization.identityProvider().toRepresentation();
idp.getConfig().put(IdentityProviderModel.LOGIN_HINT, "true");
organization.identityProvider().update(idp).close();

oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
log.debug("Logging in");
Assert.assertFalse(loginPage.isPasswordInputPresent());
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
loginPage.loginUsername(bc.getUserEmail());

// user automatically redirected to the organization identity provider
waitForPage(driver, "sign in to", true);
Assert.assertTrue("Driver should be on the provider realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
// check if the username is automatically filled
Assert.assertEquals(bc.getUserEmail(), loginPage.getUsername());
}


@Test
public void testDefaultAuthenticationMechanismIfNotOrganizationMember() {
testRealm().organizations().get(createOrganization().getId());
Expand All @@ -72,6 +97,22 @@ public void testDefaultAuthenticationMechanismIfNotOrganizationMember() {
Assert.assertTrue(loginPage.isPasswordInputPresent());
}

@Test
public void testTryLoginWithUsernameNotAnEmail() {
testRealm().organizations().get(createOrganization().getId());
oauth.clientId("broker-app");

// login with email only
loginPage.open(bc.consumerRealmName());
log.debug("Logging in");
Assert.assertFalse(loginPage.isPasswordInputPresent());
loginPage.loginUsername("user");

// check if the login page is shown
Assert.assertTrue(loginPage.isUsernameInputPresent());
Assert.assertTrue(loginPage.isPasswordInputPresent());
}

@Test
public void testLinkExistingAccount() {
// create a realm user in the consumer realm
Expand Down Expand Up @@ -154,7 +195,6 @@ private void assertBrokerRegistration(OrganizationResource organization) {
waitForPage(driver, "sign in to", true);
Assert.assertTrue("Driver should be on the provider realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));

// login to the organization identity provider and run the configured first broker login flow
loginPage.login(bc.getUserEmail(), bc.getUserPassword());
waitForPage(driver, "update account information", false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationIdentityProviderResource;
import org.keycloak.admin.client.resource.OrganizationResource;
Expand All @@ -38,16 +39,19 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
public void testUpdate() {
OrganizationRepresentation organization = createOrganization();
OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(organization.getId()).identityProvider();
IdentityProviderRepresentation idpRepresentation = orgIdPResource.toRepresentation();
assertThat(idpRepresentation.getAlias(), equalTo(bc.getIDPAlias()));
IdentityProviderRepresentation actual = orgIdPResource.toRepresentation();
IdentityProviderRepresentation expected = actual;
assertThat(expected.getAlias(), equalTo(bc.getIDPAlias()));

String displayName = "My Org Broker";
//update
idpRepresentation.setDisplayName(displayName);
try (Response response = orgIdPResource.update(idpRepresentation)) {
expected.setDisplayName("My Org Broker");
expected.getConfig().put("test", "value");
try (Response response = orgIdPResource.update(expected)) {
assertThat(response.getStatus(), equalTo(Response.Status.NO_CONTENT.getStatusCode()));
}
assertThat(orgIdPResource.toRepresentation().getDisplayName(), equalTo(displayName));
actual = orgIdPResource.toRepresentation();
assertThat(expected.getDisplayName(), equalTo(actual.getDisplayName()));
Assert.assertEquals(expected.getConfig().get("test"), actual.getConfig().get("test"));
}

@Test
Expand Down

0 comments on commit c8a92f8

Please sign in to comment.