diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java index 0be7430a42..4d9e9cee1e 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java @@ -75,6 +75,10 @@ public class IdentityProviderAuthenticator implements Authenticator { } protected void redirect(AuthenticationFlowContext context, String providerId) { + redirect(context, providerId, null); + } + + protected void redirect(AuthenticationFlowContext context, String providerId, String loginHint) { Optional idp = context.getRealm().getIdentityProvidersStream() .filter(IdentityProviderModel::isEnabled) .filter(identityProvider -> Objects.equals(providerId, identityProvider.getAlias())) @@ -84,7 +88,7 @@ public class IdentityProviderAuthenticator implements Authenticator { 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. diff --git a/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java b/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java index 0d978dcb67..e0576eaa2f 100644 --- a/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java +++ b/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java @@ -59,6 +59,12 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator { } String domain = getEmailDomain(username); + + if (domain == null) { + context.attempted(); + return; + } + OrganizationProvider provider = getOrganizationProvider(); OrganizationModel organization = provider.getByDomainName(domain); @@ -74,7 +80,7 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator { return; } - redirect(context, identityProvider.getAlias()); + redirect(context, identityProvider.getAlias(), username); } private OrganizationProvider getOrganizationProvider() { diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java index 657916ed2f..9c8050d080 100755 --- a/services/src/main/java/org/keycloak/services/Urls.java +++ b/services/src/main/java/org/keycloak/services/Urls.java @@ -52,7 +52,7 @@ public class Urls { .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"); @@ -68,6 +68,9 @@ public class Urls { if (clientData != null) { uriBuilder.replaceQueryParam(Constants.CLIENT_DATA, clientData); } + if (loginHint != null) { + uriBuilder.replaceQueryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint); + } return uriBuilder.build(realmName, providerAlias); } @@ -87,7 +90,7 @@ public class Urls { } 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) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationBrokerSelfRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationBrokerSelfRegistrationTest.java index 09b77afd80..c724a0d843 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationBrokerSelfRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationBrokerSelfRegistrationTest.java @@ -26,6 +26,8 @@ import org.junit.Test; 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; @@ -56,6 +58,29 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization 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()); @@ -72,6 +97,22 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization 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 @@ -154,7 +195,6 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization 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); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationIdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationIdentityProviderTest.java index 6fed8aad92..f498ce725a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationIdentityProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationIdentityProviderTest.java @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.nullValue; 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; @@ -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