feat: add ability to hide social broker logins in flow

Signed-off-by: Spliterash <me@spliterash.ru>
This commit is contained in:
Spliterash 2026-01-27 15:04:00 +03:00
parent eac504cce5
commit fe86d2d441
6 changed files with 275 additions and 2 deletions

View file

@ -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) {

View file

@ -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<String, String> 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() {
}
}

View file

@ -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<ProviderConfigProperty> 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() {
}
}

View file

@ -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<String> hiddenBrokers = new LinkedHashSet<>();
public Set<String> getHiddenBrokers() {
return hiddenBrokers;
}
public void setHiddenBrokers(Collection<String> 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);
}
}
}
}

View file

@ -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<String> federatedIdentities = this.getLinkedBrokerAliases(session, realm, context);
List<IdentityProvider> 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<IdentityProvider> filterHiddenProviders(List<IdentityProvider> 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;
}

View file

@ -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