Allow to configure if users are automatically redirected when the email domain matches an organization

Closes #30050

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-05-31 13:26:22 -03:00 committed by Alexander Schwartz
parent c99cddb0de
commit 4c39fcc79d
6 changed files with 78 additions and 10 deletions

View file

@ -3191,4 +3191,6 @@ linkUpdatedSuccessful=Identity provider link successfully updated
linkUpdateError=Could not update link to identity provider\: {{error}}
noResultsFound=No results found
linkedOrganization=Linked organization
send=Send
send=Send
redirectWhenEmailMatches=Redirect when email domain matches
redirectWhenEmailMatchesHelp=Automatically redirect the user to this identity provider when the email domain matches the domain

View file

@ -145,6 +145,14 @@ export const LinkIdentityProviderModal = ({
labelIcon={t("shownOnLoginPageHelp")}
stringify
/>
<DefaultSwitchControl
name={convertAttributeNameToForm(
"config.kc.org.broker.redirect.mode.email-matches",
)}
label={t("redirectWhenEmailMatches")}
labelIcon={t("redirectWhenEmailMatchesHelp")}
stringify
/>
</Form>
</FormProvider>
</Modal>

View file

@ -29,6 +29,24 @@ public interface OrganizationModel {
String ORGANIZATION_DOMAIN_ATTRIBUTE = "kc.org.domain";
String BROKER_PUBLIC = "kc.org.broker.public";
enum IdentityProviderRedirectMode {
EMAIL_MATCH("kc.org.broker.redirect.mode.email-matches");
private final String key;
IdentityProviderRedirectMode(String key) {
this.key = key;
}
public boolean isSet(IdentityProviderModel broker) {
return Boolean.parseBoolean(broker.getConfig().get(key));
}
public String getKey() {
return key;
}
}
String getId();
void setName(String name);

View file

@ -32,6 +32,7 @@ import org.keycloak.http.HttpRequest;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.OrganizationModel.IdentityProviderRedirectMode;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
@ -109,14 +110,8 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
List<IdentityProviderModel> brokers = organization.getIdentityProviders().toList();
for (IdentityProviderModel broker : brokers) {
String idpDomain = broker.getConfig().get(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
if (emailDomain.equals(idpDomain)) {
// redirect the user using the broker that matches the email domain
redirect(context, broker.getAlias(), username);
return;
}
if (redirect(context, brokers, username, emailDomain)) {
return;
}
if (!hasPublicBrokers(brokers)) {
@ -195,4 +190,20 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return realm.isOrganizationsEnabled();
}
protected boolean redirect(AuthenticationFlowContext context, List<IdentityProviderModel> brokers, String username, String emailDomain) {
for (IdentityProviderModel broker : brokers) {
if (IdentityProviderRedirectMode.EMAIL_MATCH.isSet(broker)) {
String idpDomain = broker.getConfig().get(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
if (emailDomain.equals(idpDomain)) {
// redirect the user using the broker that matches the email domain
redirect(context, broker.getAlias(), username);
return true;
}
}
}
return false;
}
}

View file

@ -34,6 +34,7 @@ import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.OrganizationModel.IdentityProviderRedirectMode;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ErrorRepresentation;
@ -498,6 +499,32 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
assertEquals(bc.getIDPAlias(), federatedIdentities.get(0).getIdentityProvider());
}
@Test
public void testDoNotRedirectToIdentityProviderAssociatedWithOrganizationDomain() {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
IdentityProviderRepresentation idp = organization.identityProviders().get(bc.getIDPAlias()).toRepresentation();
idp.getConfig().put(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE, "neworg.org");
idp.getConfig().put(IdentityProviderRedirectMode.EMAIL_MATCH.getKey(), Boolean.FALSE.toString());
idp.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
testRealm().identityProviders().get(bc.getIDPAlias()).update(idp);
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
log.debug("Logging in");
Assert.assertFalse(loginPage.isPasswordInputPresent());
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
Assert.assertFalse(loginPage.isSocialButtonPresent(idp.getAlias()));
loginPage.loginUsername(bc.getUserEmail());
// user not automatically redirected to the organization identity provider
waitForPage(driver, "sign in to", true);
Assert.assertTrue("Driver should be on the consumer realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
Assert.assertFalse(loginPage.isPasswordInputPresent());
Assert.assertTrue(driver.getPageSource().contains("Your email domain matches the " + organizationName + " organization but you don't have an account yet."));
Assert.assertTrue(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
}
@Test
public void testLoginUsingBrokerWithoutDomain() {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());

View file

@ -32,6 +32,7 @@ import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.models.OrganizationModel;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.OrganizationModel.IdentityProviderRedirectMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.GroupRepresentation;
@ -117,6 +118,7 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
}
// set the idp domain to the first domain used to create the org.
broker.getConfig().put(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE, orgDomains[0]);
broker.getConfig().put(IdentityProviderRedirectMode.EMAIL_MATCH.getKey(), Boolean.TRUE.toString());
testRealm.identityProviders().create(broker).close();
testCleanup.addCleanup(testRealm.identityProviders().get(broker.getAlias())::remove);
testRealm.organizations().get(id).identityProviders().addIdentityProvider(broker.getAlias()).close();
@ -201,7 +203,7 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
assertFalse(driver.getPageSource().contains("kc.org"));
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), email, "Firstname", "Lastname");
assertThat(appPage.getRequestType(),is(AppPage.RequestType.AUTH_RESPONSE));
assertIsMember(email, organization);
}