Automatically fill username when authenticating to through a broker

Closes #28848

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-04-17 11:23:08 -03:00 committed by Alexander Schwartz
parent 1e3837421e
commit f0f8a88489
5 changed files with 68 additions and 11 deletions

View file

@ -75,6 +75,10 @@ public class IdentityProviderAuthenticator implements Authenticator {
} }
protected void redirect(AuthenticationFlowContext context, String providerId) { 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() Optional<IdentityProviderModel> idp = context.getRealm().getIdentityProvidersStream()
.filter(IdentityProviderModel::isEnabled) .filter(IdentityProviderModel::isEnabled)
.filter(identityProvider -> Objects.equals(providerId, identityProvider.getAlias())) .filter(identityProvider -> Objects.equals(providerId, identityProvider.getAlias()))
@ -84,7 +88,7 @@ public class IdentityProviderAuthenticator implements Authenticator {
String clientId = context.getAuthenticationSession().getClient().getClientId(); String clientId = context.getAuthenticationSession().getClient().getClientId();
String tabId = context.getAuthenticationSession().getTabId(); String tabId = context.getAuthenticationSession().getTabId();
String clientData = AuthenticationProcessor.getClientData(context.getSession(), context.getAuthenticationSession()); 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) Response response = Response.seeOther(location)
.build(); .build();
// will forward the request to the IDP with prompt=none if the IDP accepts forwards with prompt=none. // will forward the request to the IDP with prompt=none if the IDP accepts forwards with prompt=none.

View file

@ -59,6 +59,12 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
} }
String domain = getEmailDomain(username); String domain = getEmailDomain(username);
if (domain == null) {
context.attempted();
return;
}
OrganizationProvider provider = getOrganizationProvider(); OrganizationProvider provider = getOrganizationProvider();
OrganizationModel organization = provider.getByDomainName(domain); OrganizationModel organization = provider.getByDomainName(domain);
@ -74,7 +80,7 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
return; return;
} }
redirect(context, identityProvider.getAlias()); redirect(context, identityProvider.getAlias(), username);
} }
private OrganizationProvider getOrganizationProvider() { private OrganizationProvider getOrganizationProvider() {

View file

@ -52,7 +52,7 @@ public class Urls {
.build(realmName, providerAlias); .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") UriBuilder uriBuilder = realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
.path(IdentityBrokerService.class, "performLogin"); .path(IdentityBrokerService.class, "performLogin");
@ -68,6 +68,9 @@ public class Urls {
if (clientData != null) { if (clientData != null) {
uriBuilder.replaceQueryParam(Constants.CLIENT_DATA, clientData); uriBuilder.replaceQueryParam(Constants.CLIENT_DATA, clientData);
} }
if (loginHint != null) {
uriBuilder.replaceQueryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
}
return uriBuilder.build(realmName, providerAlias); return uriBuilder.build(realmName, providerAlias);
} }
@ -87,7 +90,7 @@ public class Urls {
} }
public static URI identityProviderAuthnRequest(URI baseURI, String providerAlias, String realmName) { 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) { public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId, String tabId, String clientData) {

View file

@ -26,6 +26,8 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationResource; import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile.Feature; 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.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
@ -56,6 +58,29 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization
assertBrokerRegistration(organization); 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 @Test
public void testDefaultAuthenticationMechanismIfNotOrganizationMember() { public void testDefaultAuthenticationMechanismIfNotOrganizationMember() {
testRealm().organizations().get(createOrganization().getId()); testRealm().organizations().get(createOrganization().getId());
@ -72,6 +97,22 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization
Assert.assertTrue(loginPage.isPasswordInputPresent()); 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 @Test
public void testLinkExistingAccount() { public void testLinkExistingAccount() {
// create a realm user in the consumer realm // create a realm user in the consumer realm
@ -154,7 +195,6 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization
waitForPage(driver, "sign in to", true); waitForPage(driver, "sign in to", true);
Assert.assertTrue("Driver should be on the provider realm page right now", Assert.assertTrue("Driver should be on the provider realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/")); driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
// login to the organization identity provider and run the configured first broker login flow // login to the organization identity provider and run the configured first broker login flow
loginPage.login(bc.getUserEmail(), bc.getUserPassword()); loginPage.login(bc.getUserEmail(), bc.getUserPassword());
waitForPage(driver, "update account information", false); waitForPage(driver, "update account information", false);

View file

@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.nullValue;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.Response.Status;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationIdentityProviderResource; import org.keycloak.admin.client.resource.OrganizationIdentityProviderResource;
import org.keycloak.admin.client.resource.OrganizationResource; import org.keycloak.admin.client.resource.OrganizationResource;
@ -38,16 +39,19 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
public void testUpdate() { public void testUpdate() {
OrganizationRepresentation organization = createOrganization(); OrganizationRepresentation organization = createOrganization();
OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(organization.getId()).identityProvider(); OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(organization.getId()).identityProvider();
IdentityProviderRepresentation idpRepresentation = orgIdPResource.toRepresentation(); IdentityProviderRepresentation actual = orgIdPResource.toRepresentation();
assertThat(idpRepresentation.getAlias(), equalTo(bc.getIDPAlias())); IdentityProviderRepresentation expected = actual;
assertThat(expected.getAlias(), equalTo(bc.getIDPAlias()));
String displayName = "My Org Broker";
//update //update
idpRepresentation.setDisplayName(displayName); expected.setDisplayName("My Org Broker");
try (Response response = orgIdPResource.update(idpRepresentation)) { expected.getConfig().put("test", "value");
try (Response response = orgIdPResource.update(expected)) {
assertThat(response.getStatus(), equalTo(Response.Status.NO_CONTENT.getStatusCode())); 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 @Test