diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java index 447f4d27113..048dbb4047c 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java @@ -63,6 +63,8 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth // Flag is true if user was already set in the authContext before this authenticator was triggered. In this case we skip clearing of the user after unsuccessful password authentication public static final String USER_SET_BEFORE_USERNAME_PASSWORD_AUTH = "USER_SET_BEFORE_USERNAME_PASSWORD_AUTH"; + // What broker's should be hidden on login page + public static final String HIDDEN_BROKERS = "HIDDEN_BROKERS"; @Override public void action(AuthenticationFlowContext context) { diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetHiddenBrokerAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetHiddenBrokerAuthenticator.java new file mode 100644 index 00000000000..6b1f991e508 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetHiddenBrokerAuthenticator.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.authentication.authenticators.browser; + +import java.util.Arrays; +import java.util.Map; + +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.authenticators.browser.util.HiddenBrokerContext; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +public class SetHiddenBrokerAuthenticator implements Authenticator { + @Override + public void authenticate(AuthenticationFlowContext context) { + context.success(); + + AuthenticatorConfigModel config = context.getAuthenticatorConfig(); + if (config == null) return; + + Map map = config.getConfig(); + if (map == null) return; + + String mapValue = map.get(SetHiddenBrokerAuthenticatorFactory.HIDDEN_BROKER_CONFIG); + if (mapValue == null) return; + + String[] values = Constants.CFG_DELIMITER_PATTERN.split(mapValue); + if (values.length == 0) return; + + HiddenBrokerContext hiddenBrokerContext = new HiddenBrokerContext(); + hiddenBrokerContext.setHiddenBrokers(Arrays.stream(values).toList()); + hiddenBrokerContext.saveToAuthenticationSession(context.getAuthenticationSession()); + } + + @Override + public void action(AuthenticationFlowContext context) { + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetHiddenBrokerAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetHiddenBrokerAuthenticatorFactory.java new file mode 100644 index 00000000000..5ccdae8c8e1 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetHiddenBrokerAuthenticatorFactory.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.authentication.authenticators.browser; + +import java.util.List; + +import org.keycloak.Config; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; + +public class SetHiddenBrokerAuthenticatorFactory implements AuthenticatorFactory { + private static final SetHiddenBrokerAuthenticator INSTANCE = new SetHiddenBrokerAuthenticator(); + public static final String PROVIDER_ID = "set-hidden-broker"; + public static final String HIDDEN_BROKER_CONFIG = "hidden"; + + static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { + AuthenticationExecutionModel.Requirement.REQUIRED, + AuthenticationExecutionModel.Requirement.DISABLED}; + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public Authenticator create(KeycloakSession session) { + return INSTANCE; + } + + @Override + public String getDisplayType() { + return "Set hidden Broker"; + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public String getReferenceCategory() { + return null; + } + + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public boolean isUserSetupAllowed() { + return true; + } + + @Override + public String getHelpText() { + return "Hide log in via social buttons on login page"; + } + + @Override + public List getConfigProperties() { + ProviderConfigProperty hiddenBrokers = new ProviderConfigProperty( + HIDDEN_BROKER_CONFIG, + "Hidden brokers", + "What social brokers should be hidden on login page. Use alias", + ProviderConfigProperty.MULTIVALUED_STRING_TYPE, + null, + false, + true + ); + + return List.of(hiddenBrokers); + } + + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/util/HiddenBrokerContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/util/HiddenBrokerContext.java new file mode 100644 index 00000000000..2a6e5f984de --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/util/HiddenBrokerContext.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.authentication.authenticators.browser.util; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; +import org.keycloak.sessions.AuthenticationSessionModel; +import org.keycloak.util.JsonSerialization; + +public class HiddenBrokerContext { + + private Set hiddenBrokers = new LinkedHashSet<>(); + + public Set getHiddenBrokers() { + return hiddenBrokers; + } + + public void setHiddenBrokers(Collection hiddenBrokers) { + this.hiddenBrokers.clear(); + this.hiddenBrokers.addAll(hiddenBrokers); + } + + public void addHiddenBroker(String brokerAliasId) { + hiddenBrokers.add(brokerAliasId); + } + + // Save this context as note to authSession + public void saveToAuthenticationSession(AuthenticationSessionModel authSession) { + try { + String asString = JsonSerialization.writeValueAsString(this); + authSession.setAuthNote(AbstractUsernameFormAuthenticator.HIDDEN_BROKERS, asString); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + public static HiddenBrokerContext readFromAuthenticationSession(AuthenticationSessionModel authSession) { + String asString = authSession.getAuthNote(AbstractUsernameFormAuthenticator.HIDDEN_BROKERS); + if (asString == null) { + return null; + } else { + try { + return JsonSerialization.readValue(asString, HiddenBrokerContext.class); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + } +} diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java index c8319fa3d8b..c3c9e09e57f 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java @@ -32,6 +32,7 @@ import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator; import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext; +import org.keycloak.authentication.authenticators.browser.util.HiddenBrokerContext; import org.keycloak.common.Profile; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; @@ -73,15 +74,30 @@ public class IdentityProviderBean { if (this.providers == null) { String existingIDP = this.getExistingIDP(session, context); Set federatedIdentities = this.getLinkedBrokerAliases(session, realm, context); + List defaultProviders; if (federatedIdentities != null) { - this.providers = getFederatedIdentityProviders(federatedIdentities, existingIDP); + defaultProviders = getFederatedIdentityProviders(federatedIdentities, existingIDP); } else { - this.providers = searchForIdentityProviders(existingIDP); + defaultProviders = searchForIdentityProviders(existingIDP); } + this.providers = filterHiddenProviders(defaultProviders); } return this.providers; } + protected List filterHiddenProviders(List defaultProviders) { + AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); + if (authenticationSession == null) return defaultProviders; + + HiddenBrokerContext hiddenBrokerContext = HiddenBrokerContext.readFromAuthenticationSession(authenticationSession); + if (hiddenBrokerContext == null) return defaultProviders; + + return defaultProviders + .stream() + .filter(p -> !hiddenBrokerContext.getHiddenBrokers().contains(p.alias)) + .toList(); + } + public KeycloakSession getSession() { return this.session; } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory index f7a23dacbf4..62cfa44ae91 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -58,3 +58,4 @@ org.keycloak.authentication.authenticators.sessionlimits.UserSessionLimitsAuthen org.keycloak.authentication.authenticators.browser.RecoveryAuthnCodesFormAuthenticatorFactory org.keycloak.organization.authentication.authenticators.browser.OrganizationAuthenticatorFactory org.keycloak.authentication.authenticators.browser.PasskeysConditionalUIAuthenticatorFactory +org.keycloak.authentication.authenticators.browser.SetHiddenBrokerAuthenticatorFactory \ No newline at end of file