Allow hide organization brokers when the user does not map to any organization during login

Closes #45422

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2026-01-15 11:19:43 -03:00 committed by GitHub
parent 45e59d8181
commit 37ff64446b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 72 additions and 3 deletions

View file

@ -42,6 +42,9 @@ The domain from the organization that you want to link with the identity provide
Hide on login page::
If this identity provider should be hidden in login pages when the user is authenticating in the scope of the organization.
Hide on login page when organization not resolved::
If enabled, the identity provider will be hidden on the login page when the organization cannot be resolved based on the user's email domain. Otherwise, the identity provider will be shown on the login page regardless of whether the organization is resolved or not. If 'Hide on login page' is also enabled, the identity provider will always be hidden on the login page.
Redirect when email domain matches::
If members should be automatically redirected to the identity provider when their email domain matches the domain set to the identity provider. If the domain is set to `Any`, members whose email domain matches *any* of the organization domains will be redirected to the identity provider.

View file

@ -3710,4 +3710,6 @@ smtpSocketWriteTimeoutHelp=The timeout in milliseconds for writing to the SMTP s
eventTypes.USER_SESSION_DELETED.name=User session deleted
eventTypes.USER_SESSION_DELETED.description=User session deleted
eventTypes.USER_SESSION_DELETED_ERROR.name=User session deleted error
eventTypes.USER_SESSION_DELETED_ERROR.description=User session deleted error
eventTypes.USER_SESSION_DELETED_ERROR.description=User session deleted error
hideOnLoginWhenOrgNotResolved=Hide on login page when organization not resolved
hideOnLoginWhenOrgNotResolvedHelp=If enabled, the identity provider will be hidden on the login page when the organization cannot be resolved based on the user's email domain. Otherwise, the identity provider will be shown on the login page regardless of whether the organization is resolved or not. If 'Hide on login page' is also enabled, the identity provider will always be hidden on the login page.

View file

@ -153,6 +153,14 @@ export const LinkIdentityProviderModal = ({
labelIcon={t("hideOnLoginPageHelp")}
defaultValue={true}
/>
<DefaultSwitchControl
name={convertAttributeNameToForm(
"config.kc.org.broker.login.hide-when-org-unknown",
)}
label={t("hideOnLoginWhenOrgNotResolved")}
labelIcon={t("hideOnLoginWhenOrgNotResolvedHelp")}
stringify
/>
<DefaultSwitchControl
name={convertAttributeNameToForm(
"config.kc.org.broker.redirect.mode.email-matches",

View file

@ -30,6 +30,7 @@ public interface OrganizationModel {
String ORGANIZATION_NAME_ATTRIBUTE = "kc.org.name";
String ORGANIZATION_DOMAIN_ATTRIBUTE = "kc.org.domain";
String ALIAS = "alias";
String HIDE_IDP_ON_LOGIN_WHEN_ORGANIZATION_UNKNOWN = "kc.org.broker.login.hide-when-org-unknown";
enum IdentityProviderRedirectMode {
EMAIL_MATCH("kc.org.broker.redirect.mode.email-matches");

View file

@ -18,6 +18,7 @@
package org.keycloak.organization.forms.login.freemarker.model;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
@ -61,6 +62,18 @@ public class OrganizationAwareIdentityProviderBean extends IdentityProviderBean
.map(idp -> createIdentityProvider(this.realm, this.baseURI, idp))
.sorted(IDP_COMPARATOR_INSTANCE).toList();
}
Predicate<IdentityProviderModel> defaultFilter = idp -> {
if (idp.isEnabled() && !Objects.equals(existingIDP, idp.getAlias())) {
if (organization == null) {
Map<String, String> config = idp.getConfig();
return !Boolean.parseBoolean(config.get(OrganizationModel.HIDE_IDP_ON_LOGIN_WHEN_ORGANIZATION_UNKNOWN));
}
return true;
}
return false;
};
if (onlyOrganizationBrokers) {
// we already have the organization, just fetch the organization's public enabled IDPs.
if (this.organization != null) {
@ -72,12 +85,12 @@ public class OrganizationAwareIdentityProviderBean extends IdentityProviderBean
}
// we don't have a specific organization - fetch public enabled IDPs linked to any org.
return session.identityProviders().getForLogin(ORG_ONLY, null)
.filter(idp -> idp.isEnabled() && !Objects.equals(existingIDP, idp.getAlias())) // re-check isEnabled as idp might have been wrapped.
.filter(defaultFilter) // re-check isEnabled as idp might have been wrapped.
.map(idp -> createIdentityProvider(this.realm, this.baseURI, idp))
.sorted(IDP_COMPARATOR_INSTANCE).toList();
}
return session.identityProviders().getForLogin(ALL, this.organization != null ? this.organization.getId() : null)
.filter(idp -> idp.isEnabled() && !Objects.equals(existingIDP, idp.getAlias())) // re-check isEnabled as idp might have been wrapped.
.filter(defaultFilter) // re-check isEnabled as idp might have been wrapped.
.map(idp -> createIdentityProvider(this.realm, this.baseURI, idp))
.sorted(IDP_COMPARATOR_INSTANCE).toList();
}

View file

@ -776,6 +776,48 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
Assert.assertFalse(loginPage.isSocialButtonPresent(org0Broker.getAlias()));
}
@Test
public void testDoNotShowBrokersIfOrganizationNotResolved() {
String org0Name = "org-0";
OrganizationResource org0 = testRealm().organizations().get(createOrganization(org0Name).getId());
IdentityProviderRepresentation org0Broker = org0.identityProviders().getIdentityProviders().get(0);
org0Broker.setHideOnLogin(false);
org0Broker.getConfig().put(OrganizationModel.HIDE_IDP_ON_LOGIN_WHEN_ORGANIZATION_UNKNOWN, Boolean.TRUE.toString());
org0Broker.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
testRealm().identityProviders().get(org0Broker.getAlias()).update(org0Broker);
// do not show if organization cannot be resolved
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername("user@unknowndomain.org");
Assert.assertFalse(loginPage.isSocialButtonPresent(org0Broker.getAlias()));
// show if organization can be resolved
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername("user@org-0.org");
Assert.assertTrue(loginPage.isSocialButtonPresent(org0Broker.getAlias()));
// show if the config is set to false
org0Broker.getConfig().put(OrganizationModel.HIDE_IDP_ON_LOGIN_WHEN_ORGANIZATION_UNKNOWN, Boolean.FALSE.toString());
testRealm().identityProviders().get(org0Broker.getAlias()).update(org0Broker);
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername("user@unknowndomain.org");
Assert.assertTrue(loginPage.isSocialButtonPresent(org0Broker.getAlias()));
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername("user@org-0.org");
Assert.assertTrue(loginPage.isSocialButtonPresent(org0Broker.getAlias()));
// hide if hide on login is set to true
org0Broker.setHideOnLogin(true);
testRealm().identityProviders().get(org0Broker.getAlias()).update(org0Broker);
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername("user@unknowndomain.org");
Assert.assertFalse(loginPage.isSocialButtonPresent(org0Broker.getAlias()));
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername("user@org-0.org");
Assert.assertFalse(loginPage.isSocialButtonPresent(org0Broker.getAlias()));
}
@Test
public void testLoginUsingBrokerWithoutDomain() {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());