Filter out non-user authentication IdPs from account and login (#43798)

Closes #43553

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-10-31 12:40:04 +01:00 committed by GitHub
parent f92adda310
commit 1048c8d9c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 364 additions and 64 deletions

View File

@ -44,6 +44,13 @@ public interface IdentityProvidersResource {
@Produces(MediaType.APPLICATION_JSON)
List<IdentityProviderRepresentation> findAll();
@GET
@Path("instances")
@Produces(MediaType.APPLICATION_JSON)
List<IdentityProviderRepresentation> find(@QueryParam("type") String type, @QueryParam("capability") String capability,
@QueryParam("search") String search, @QueryParam("briefRepresentation") Boolean briefRepresentation,
@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults);
@GET
@Path("instances")
@Produces(MediaType.APPLICATION_JSON)

View File

@ -103,6 +103,7 @@ export const UserIdentityProviderLinks = ({
first: first!,
max: max!,
realmOnly: false,
capability: "USER_LINKING",
};
if (search) {
params.search = search;

View File

@ -13,6 +13,8 @@ export interface PaginatedQuery {
export interface IdentityProvidersQuery extends PaginatedQuery {
search?: string;
realmOnly?: boolean;
type?: string;
capability?: string;
}
export class IdentityProviders extends Resource<{ realm?: string }> {

View File

@ -20,6 +20,7 @@ package org.keycloak.models.cache.infinispan;
import static org.keycloak.models.utils.KeycloakModelUtils.runOnRealm;
import org.keycloak.Config;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.Profile;
@ -918,7 +919,7 @@ public class RealmAdapter implements CachedRealmModel {
@Override
public Stream<IdentityProviderModel> getIdentityProvidersStream() {
return runOnRealm(session, this, (session) -> session.identityProviders().getAllStream());
return runOnRealm(session, this, (session) -> session.identityProviders().getAllStream(IdentityProviderQuery.userAuthentication()));
}
@Override

View File

@ -23,6 +23,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.common.Profile;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderStorageProvider;
@ -181,7 +183,7 @@ public class InfinispanIdentityProviderStorageProvider implements IdentityProvid
if (cached == null) {
Long loaded = realmCache.getCache().getCurrentRevision(cacheKey);
long count = idpDelegate.getAllStream(Map.of(), 0, 1).count();
long count = idpDelegate.getAllStream(IdentityProviderQuery.userAuthentication(), 0, 1).count();
cached = new CachedCount(loaded, getRealm(), cacheKey, count);
realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision());
}
@ -287,8 +289,8 @@ public class InfinispanIdentityProviderStorageProvider implements IdentityProvid
}
@Override
public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) {
return idpDelegate.getAllStream(attrs, first, max).map(this::createOrganizationAwareIdentityProviderModel);
public Stream<IdentityProviderModel> getAllStream(IdentityProviderQuery query, Integer first, Integer max) {
return idpDelegate.getAllStream(query, first, max).map(this::createOrganizationAwareIdentityProviderModel);
}
@Override

View File

@ -35,8 +35,11 @@ import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.hibernate.Session;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.models.IdentityProviderType;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.util.IdentityProviderTypeUtil;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.IdentityProviderMapperModel;
@ -218,16 +221,27 @@ public class JpaIdentityProviderStorageProvider implements IdentityProviderStora
}
@Override
public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) {
public Stream<IdentityProviderModel> getAllStream(IdentityProviderQuery query, Integer first, Integer max) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<IdentityProviderEntity> query = builder.createQuery(IdentityProviderEntity.class);
Root<IdentityProviderEntity> idp = query.from(IdentityProviderEntity.class);
CriteriaQuery<IdentityProviderEntity> cq = builder.createQuery(IdentityProviderEntity.class);
Root<IdentityProviderEntity> idp = cq.from(IdentityProviderEntity.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(builder.equal(idp.get("realmId"), getRealm().getId()));
if (attrs != null) {
for (Map.Entry<String, String> entry : attrs.entrySet()) {
List<String> includedProviderFactories = null;
if (query.getType() != null && query.getType() != IdentityProviderType.ANY) {
includedProviderFactories = IdentityProviderTypeUtil.listFactoriesByType(session, query.getType());
} else if (query.getCapability() != null) {
includedProviderFactories = IdentityProviderTypeUtil.listFactoriesByCapability(session, query.getCapability());
}
if (includedProviderFactories != null) {
predicates.add(builder.in(idp.get("providerId")).value(includedProviderFactories));
}
if (query.getOptions() != null) {
for (Map.Entry<String, String> entry : query.getOptions().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (StringUtil.isBlank(key)) {
@ -290,8 +304,8 @@ public class JpaIdentityProviderStorageProvider implements IdentityProviderStora
}
}
query.orderBy(builder.asc(idp.get(ALIAS)));
TypedQuery<IdentityProviderEntity> typedQuery = em.createQuery(query.select(idp).where(predicates.toArray(Predicate[]::new)));
cq.orderBy(builder.asc(idp.get(ALIAS)));
TypedQuery<IdentityProviderEntity> typedQuery = em.createQuery(cq.select(idp).where(predicates.toArray(Predicate[]::new)));
return closing(paginateQuery(typedQuery, first, max).getResultStream()).map(this::toModel);
}

View File

@ -1316,7 +1316,7 @@ public class RealmAdapter implements StorageProviderRealmModel, JpaModel<RealmEn
@Override
public Stream<IdentityProviderModel> getIdentityProvidersStream() {
return session.identityProviders().getAllStream();
return session.identityProviders().getAllStream(IdentityProviderQuery.userAuthentication());
}
@Override

View File

@ -17,8 +17,7 @@
package org.keycloak.migration.migrators;
import java.util.Map;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.AuthenticationFlowModel;
@ -69,7 +68,7 @@ public class MigrateTo1_7_0 implements Migration {
DefaultAuthenticationFlows.migrateFlows(realm);
AuthenticationFlowModel firstBrokerLoginFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW);
session.identityProviders().getAllStream(Map.of(IdentityProviderModel.FIRST_BROKER_LOGIN_FLOW_ID, ""), null, null)
session.identityProviders().getAllStream(IdentityProviderQuery.userAuthentication().with(IdentityProviderModel.FIRST_BROKER_LOGIN_FLOW_ID, ""), null, null)
.forEach(provider -> {
provider.setFirstBrokerLoginFlowId(firstBrokerLoginFlow.getId());
session.identityProviders().update(provider);

View File

@ -17,9 +17,8 @@
package org.keycloak.migration.migrators;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
@ -55,7 +54,7 @@ public class MigrateTo2_2_0 implements Migration {
private void addIdentityProviderAuthenticator(KeycloakSession session, RealmModel realm) {
String defaultProvider = session.identityProviders()
.getAllStream(Map.of(IdentityProviderModel.ENABLED, "true", IdentityProviderModel.AUTHENTICATE_BY_DEFAULT, "true"), 0, 1)
.getAllStream(IdentityProviderQuery.userAuthentication().with(IdentityProviderModel.ENABLED, "true").with(IdentityProviderModel.AUTHENTICATE_BY_DEFAULT, "true"), 0, 1)
.map(IdentityProviderModel::getAlias)
.findFirst().orElse(null);
DefaultAuthenticationFlows.addIdentityProviderAuthenticator(realm, defaultProvider);

View File

@ -0,0 +1,69 @@
package org.keycloak.broker.provider.util;
import org.keycloak.models.IdentityProviderCapability;
import org.keycloak.models.IdentityProviderType;
import org.keycloak.broker.provider.ClientAssertionIdentityProvider;
import org.keycloak.broker.provider.ExchangeExternalToken;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.JWTAuthorizationGrantProvider;
import org.keycloak.broker.provider.UserAuthenticationIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderFactory;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class IdentityProviderTypeUtil {
private IdentityProviderTypeUtil() {
}
public static List<String> listFactoriesByCapability(KeycloakSession session, IdentityProviderCapability capability) {
Set<IdentityProviderType> types = Arrays.stream(IdentityProviderType.values()).filter(t -> t.getCapabilities().contains(capability)).collect(Collectors.toSet());
return listFactoriesByTypes(session, types);
}
public static List<String> listFactoriesByType(KeycloakSession session, IdentityProviderType type) {
return listFactoriesByTypes(session, Set.of(type));
}
private static List<String> listFactoriesByTypes(KeycloakSession session, Set<IdentityProviderType> types) {
KeycloakSessionFactory sf = session.getKeycloakSessionFactory();
Stream<ProviderFactory> factories = sf.getProviderFactoriesStream(IdentityProvider.class);
if (types.contains(IdentityProviderType.ANY) || types.contains(IdentityProviderType.USER_AUTHENTICATION)) {
factories = Stream.concat(factories, sf.getProviderFactoriesStream(SocialIdentityProvider.class));
}
Set<Class<?>> typeClasses = types.stream().map(IdentityProviderTypeUtil::toTypeClass).collect(Collectors.toSet());
return factories.filter(f -> typeClasses.stream().anyMatch(t -> t.isAssignableFrom(getType(f))))
.map(ProviderFactory::getId)
.toList();
}
private static Class<?> getType(ProviderFactory<?> f) {
try {
return f.getClass().getMethod("create", KeycloakSession.class, IdentityProviderModel.class).getReturnType();
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private static Class<?> toTypeClass(IdentityProviderType type) {
return switch (type) {
case USER_AUTHENTICATION -> UserAuthenticationIdentityProvider.class;
case CLIENT_ASSERTION -> ClientAssertionIdentityProvider.class;
case EXCHANGE_EXTERNAL_TOKEN -> ExchangeExternalToken.class;
case JWT_AUTHORIZATION_GRANT -> JWTAuthorizationGrantProvider.class;
case ANY -> IdentityProvider.class;
};
}
}

View File

@ -561,7 +561,7 @@ public class ModelToRepresentation {
}
if (export) {
List<IdentityProviderRepresentation> identityProviders = session.identityProviders().getAllStream()
List<IdentityProviderRepresentation> identityProviders = session.identityProviders().getAllStream(IdentityProviderQuery.any())
.map(provider -> toRepresentation(realm, provider, export)).collect(Collectors.toList());
rep.setIdentityProviders(identityProviders);
List<IdentityProviderMapperRepresentation> identityProviderMappers = session.identityProviders().getMappersStream()

View File

@ -0,0 +1,7 @@
package org.keycloak.models;
public enum IdentityProviderCapability {
USER_LINKING
}

View File

@ -0,0 +1,65 @@
package org.keycloak.models;
import java.util.HashMap;
import java.util.Map;
public class IdentityProviderQuery {
IdentityProviderType type;
IdentityProviderCapability capability;
Map<String, String> options;
public static IdentityProviderQuery any() {
IdentityProviderQuery query = new IdentityProviderQuery();
query.type = IdentityProviderType.ANY;
return query;
}
public static IdentityProviderQuery userAuthentication() {
IdentityProviderQuery query = new IdentityProviderQuery();
query.type = IdentityProviderType.USER_AUTHENTICATION;
return query;
}
public static IdentityProviderQuery type(IdentityProviderType type) {
IdentityProviderQuery query = new IdentityProviderQuery();
query.type = type;
return query;
}
public static IdentityProviderQuery capability(IdentityProviderCapability capability) {
IdentityProviderQuery query = new IdentityProviderQuery();
query.capability = capability;
return query;
}
public IdentityProviderQuery with(String key, String value) {
if (this.options == null) {
this.options = new HashMap<>();
}
this.options.put(key, value);
return this;
}
public IdentityProviderQuery with(Map<String, String> options) {
if (this.options == null) {
this.options = new HashMap<>(options);
} else {
this.options.putAll(options);
}
return this;
}
public IdentityProviderType getType() {
return type;
}
public IdentityProviderCapability getCapability() {
return capability;
}
public Map<String, String> getOptions() {
return options;
}
}

View File

@ -93,12 +93,28 @@ public interface IdentityProviderStorageProvider extends Provider {
}
/**
* Returns all identity providers in the current realm.
*
* @return a non-null stream of {@link IdentityProviderModel}s.
* @deprecated Use {@link #getAllStream(IdentityProviderQuery)}
*/
@Deprecated
default Stream<IdentityProviderModel> getAllStream() {
return getAllStream(Map.of(), null, null);
return getAllStream(IdentityProviderQuery.userAuthentication(), null, null);
}
/**
* Returns all identity providers in the current realm of a given type.
* @param query the query of identity provider to return
* @return a non-null stream of {@link IdentityProviderModel}s that match the specified type
*/
default Stream<IdentityProviderModel> getAllStream(IdentityProviderQuery query) {
return getAllStream(query, null, null);
}
/**
* @deprecated Use {@link #getAllStream(IdentityProviderQuery, Integer, Integer)}
*/
@Deprecated
default Stream<IdentityProviderModel> getAllStream(Map<String, String> options, Integer first, Integer max) {
return getAllStream(IdentityProviderQuery.userAuthentication().with(options), first, max);
}
/**
@ -113,12 +129,12 @@ public interface IdentityProviderStorageProvider extends Provider {
* option</li>
* </ul>
*
* @param options a {@link Map} containing identity provider search options that must be matched.
* @param query the query for identity providers to match.
* @param first the position of the first result to be processed (pagination offset). Ignored if negative or {@code null}.
* @param max the maximum number of results to be returned. Ignored if negative or {@code null}.
* @return a non-null stream of {@link IdentityProviderModel}s that match the search criteria.
*/
Stream<IdentityProviderModel> getAllStream(Map<String, String> options, Integer first, Integer max);
Stream<IdentityProviderModel> getAllStream(IdentityProviderQuery query, Integer first, Integer max);
/**
* Returns all identity providers associated with the organization with the provided id.
@ -129,7 +145,7 @@ public interface IdentityProviderStorageProvider extends Provider {
* @return a non-null stream of {@link IdentityProviderModel}s that match the search criteria.
*/
default Stream<IdentityProviderModel> getByOrganization(String orgId, Integer first, Integer max) {
return getAllStream(Map.of(IdentityProviderModel.ORGANIZATION_ID, orgId != null ? orgId : ""), first, max);
return getAllStream(IdentityProviderQuery.userAuthentication().with(IdentityProviderModel.ORGANIZATION_ID, orgId != null ? orgId : ""), first, max);
}
/**
@ -168,7 +184,7 @@ public interface IdentityProviderStorageProvider extends Provider {
// fetch all realm-only IDPs - i.e. those not associated with orgs.
Map<String, String> searchOptions = LoginFilter.getLoginSearchOptions();
searchOptions.put(IdentityProviderModel.ORGANIZATION_ID, null);
result = Stream.concat(result, getAllStream(searchOptions, null, null));
result = Stream.concat(result, getAllStream(IdentityProviderQuery.userAuthentication().with(searchOptions), null, null));
}
if (mode == FetchMode.ORG_ONLY || mode == FetchMode.ALL) {
// fetch IDPs associated with organizations.
@ -179,7 +195,7 @@ public interface IdentityProviderStorageProvider extends Provider {
} else {
searchOptions.put(IdentityProviderModel.ORGANIZATION_ID_NOT_NULL, "");
}
result = Stream.concat(result, getAllStream(searchOptions, null, null));
result = Stream.concat(result, getAllStream(IdentityProviderQuery.userAuthentication().with(searchOptions), null, null));
}
return result;
}
@ -198,7 +214,7 @@ public interface IdentityProviderStorageProvider extends Provider {
* otherwise.
*/
default boolean isIdentityFederationEnabled() {
return getAllStream(Map.of(), 0, 1).findFirst().isPresent();
return getAllStream(IdentityProviderQuery.userAuthentication(), 0, 1).findFirst().isPresent();
}
/**

View File

@ -0,0 +1,32 @@
package org.keycloak.models;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import static org.keycloak.models.IdentityProviderCapability.USER_LINKING;
public enum IdentityProviderType {
ANY,
USER_AUTHENTICATION(USER_LINKING),
CLIENT_ASSERTION,
EXCHANGE_EXTERNAL_TOKEN(USER_LINKING),
JWT_AUTHORIZATION_GRANT(USER_LINKING);
private final Set<IdentityProviderCapability> capabilities;
IdentityProviderType(IdentityProviderCapability... capabilities) {
if (capabilities == null || capabilities.length == 0) {
this.capabilities = Collections.emptySet();
} else {
this.capabilities = Arrays.stream(capabilities).collect(Collectors.toSet());
}
}
public Set<IdentityProviderCapability> getCapabilities() {
return capabilities;
}
}

View File

@ -472,7 +472,7 @@ public interface RealmModel extends RoleContainerModel {
* Returns identity providers as a stream.
*
* @return Stream of {@link IdentityProviderModel}. Never returns {@code null}.
* @deprecated Use {@link IdentityProviderStorageProvider#getAllStream()} instead.
* @deprecated Use {@link IdentityProviderStorageProvider#getAllStream(IdentityProviderQuery)} instead.
*/
@Deprecated
Stream<IdentityProviderModel> getIdentityProvidersStream();

View File

@ -1,7 +1,6 @@
package org.keycloak.broker.spiffe;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderShowInAccountConsole;
import org.keycloak.models.RealmModel;
import java.util.regex.Pattern;
@ -15,18 +14,12 @@ public class SpiffeIdentityProviderConfig extends IdentityProviderModel {
private static final Pattern TRUST_DOMAIN_PATTERN = Pattern.compile("spiffe://[a-z0-9.\\-_]*");
public SpiffeIdentityProviderConfig() {
getConfig().put(IdentityProviderModel.SHOW_IN_ACCOUNT_CONSOLE, IdentityProviderShowInAccountConsole.NEVER.name());
}
public SpiffeIdentityProviderConfig(IdentityProviderModel model) {
super(model);
}
@Override
public boolean isHideOnLogin() {
return true;
}
public int getAllowedClockSkew() {
String allowedClockSkew = getConfig().get(ALLOWED_CLOCK_SKEW);
if (allowedClockSkew == null || allowedClockSkew.isEmpty()) {

View File

@ -1,6 +1,7 @@
package org.keycloak.cache;
import com.github.benmanes.caffeine.cache.Cache;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
@ -29,7 +30,7 @@ public class DefaultAlternativeLookupProvider implements AlternativeLookupProvid
}
}
IdentityProviderModel idp = session.identityProviders().getAllStream()
IdentityProviderModel idp = session.identityProviders().getAllStream(IdentityProviderQuery.any())
.filter(i -> issuerUrl.equals(i.getConfig().get(IdentityProviderModel.ISSUER)))
.findFirst().orElse(null);
if (idp != null && idp.getAlias() != null) {

View File

@ -20,6 +20,7 @@ package org.keycloak.protocol.oidc;
import java.util.Collections;
import java.util.HashMap;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.SecretGenerator;
@ -1534,8 +1535,8 @@ public class TokenManager {
private Stream<OIDCIdentityProvider> getOIDCIdentityProviders(LogoutToken logoutToken, KeycloakSession session) {
try {
return session.identityProviders()
.getAllStream(Map.of(
OIDCIdentityProviderConfig.ISSUER, logoutToken.getIssuer()
.getAllStream(IdentityProviderQuery.userAuthentication()
.with(OIDCIdentityProviderConfig.ISSUER, logoutToken.getIssuer()
), -1, -1)
.map(model -> {
var idp = IdentityBrokerService.getIdentityProvider(session, model.getAlias());

View File

@ -19,6 +19,7 @@
package org.keycloak.protocol.oidc.tokenexchange;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.broker.provider.BrokeredIdentityContext;
@ -64,6 +65,7 @@ import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
import static org.keycloak.models.IdentityProviderType.EXCHANGE_EXTERNAL_TOKEN;
import java.util.ArrayList;
import java.util.List;
@ -449,7 +451,7 @@ public abstract class AbstractTokenExchangeProvider implements TokenExchangeProv
} catch (IdentityBrokerException ignore) {
}
return session.identityProviders().getAllStream().map(idpModel -> {
return session.identityProviders().getAllStream(IdentityProviderQuery.type(EXCHANGE_EXTERNAL_TOKEN)).map(idpModel -> {
IdentityProvider<?> idp = IdentityBrokerService.getIdentityProvider(session, idpModel.getAlias());
if (idp instanceof ExchangeExternalToken external && external.isIssuer(alias, formParams)) {

View File

@ -6,6 +6,7 @@ import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.authentication.requiredactions.DeleteAccount;
import org.keycloak.authentication.requiredactions.UpdateEmail;
import org.keycloak.common.Profile;
@ -310,9 +311,10 @@ public class AccountConsole implements AccountResourceProvider {
}
IdentityProviderStorageProvider identityProviders = session.identityProviders();
Stream<IdentityProviderModel> realmBrokers = identityProviders.getAllStream(Map.of(
IdentityProviderModel.ENABLED, "true",
IdentityProviderModel.ORGANIZATION_ID, ""), 0, 1);
Stream<IdentityProviderModel> realmBrokers = identityProviders.getAllStream(IdentityProviderQuery.userAuthentication()
.with(IdentityProviderModel.ENABLED, "true")
.with(IdentityProviderModel.ORGANIZATION_ID, ""),
0, 1);
Stream<IdentityProviderModel> linkedBrokers = session.users().getFederatedIdentitiesStream(realm, user)
.map(FederatedIdentityModel::getIdentityProvider)
.map(identityProviders::getByAlias);

View File

@ -35,6 +35,7 @@ import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.http.HttpRequest;
@ -151,7 +152,7 @@ public class LinkedAccountsResource {
IdentityProviderModel.ALIAS_NOT_IN, fedAliasesToExclude,
IdentityProviderModel.SHOW_IN_ACCOUNT_CONSOLE, IdentityProviderShowInAccountConsole.ALWAYS.name());
linkedAccounts = session.identityProviders().getAllStream(searchOptions, firstResult, maxResults)
linkedAccounts = session.identityProviders().getAllStream(IdentityProviderQuery.userAuthentication().with(searchOptions), firstResult, maxResults)
.map(idp -> this.toLinkedAccount(idp, null, null))
.toList();
}
@ -195,7 +196,7 @@ public class LinkedAccountsResource {
@Deprecated
public List<LinkedAccountRepresentation> getLinkedAccounts(KeycloakSession session, RealmModel realm, UserModel user) {
return session.identityProviders().getAllStream(Map.of(IdentityProviderModel.ENABLED, "true"), null, null)
return session.identityProviders().getAllStream(IdentityProviderQuery.userAuthentication().with(IdentityProviderModel.ENABLED, "true"), null, null)
.map(provider -> toLinkedAccountRepresentation(provider, session.users().getFederatedIdentitiesStream(realm, user)))
.filter(Objects::nonNull)
.sorted().toList();

View File

@ -24,6 +24,9 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.models.IdentityProviderCapability;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.models.IdentityProviderType;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProvider;
@ -60,7 +63,6 @@ import jakarta.ws.rs.core.Response;
import org.keycloak.utils.StringUtil;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@ -182,6 +184,8 @@ public class IdentityProvidersResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.IDENTITY_PROVIDERS)
@Operation(summary = "List identity providers")
public Stream<IdentityProviderRepresentation> getIdentityProviders(
@Parameter(description = "Filter by identity providers type") @QueryParam("type") String type,
@Parameter(description = "Filter by identity providers capability") @QueryParam("capability") String capability,
@Parameter(description = "Filter specific providers by name. Search can be prefix (name*), contains (*name*) or exact (\"name\"). Default prefixed.") @QueryParam("search") String search,
@Parameter(description = "Boolean which defines whether brief representations are returned (default: false)") @QueryParam("briefRepresentation") Boolean briefRepresentation,
@Parameter(description = "Pagination offset") @QueryParam("first") Integer firstResult,
@ -199,14 +203,23 @@ public class IdentityProvidersResource {
boolean searchRealmOnlyIDPs = Optional.ofNullable(realmOnly).orElse(false);
Map<String, String> searchOptions = new HashMap<>();
IdentityProviderQuery query;
if (type != null) {
query = IdentityProviderQuery.type(IdentityProviderType.valueOf(type));
} else if (capability != null) {
query = IdentityProviderQuery.capability(IdentityProviderCapability.valueOf(capability));
} else {
query = IdentityProviderQuery.any();
}
if (StringUtil.isNotBlank(search)) {
searchOptions.put(IdentityProviderModel.SEARCH, search);
query.with(IdentityProviderModel.SEARCH, search);
}
if (searchRealmOnlyIDPs) {
searchOptions.put(IdentityProviderModel.ORGANIZATION_ID, null);
query.with(IdentityProviderModel.ORGANIZATION_ID, null);
}
return session.identityProviders().getAllStream(searchOptions, firstResult, maxResults).map(toRepresentation);
return session.identityProviders().getAllStream(query, firstResult, maxResults).map(toRepresentation);
}
/**

View File

@ -0,0 +1,84 @@
package org.keycloak.tests.admin.identityprovider;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.keycloak.models.IdentityProviderCapability;
import org.keycloak.models.IdentityProviderType;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.broker.spiffe.SpiffeIdentityProviderConfig;
import org.keycloak.broker.spiffe.SpiffeIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.RealmConfig;
import org.keycloak.testframework.realm.RealmConfigBuilder;
import org.keycloak.testframework.remote.runonserver.InjectRunOnServer;
import org.keycloak.testframework.remote.runonserver.RunOnServerClient;
import org.keycloak.tests.client.authentication.external.SpiffeClientAuthTest;
import org.keycloak.testsuite.util.IdentityProviderBuilder;
import java.util.List;
@KeycloakIntegrationTest(config = SpiffeClientAuthTest.SpiffeServerConfig.class)
public class IdentityProviderTypeTest {
@InjectRealm(config = MyRealm.class)
ManagedRealm realm;
@InjectRunOnServer
RunOnServerClient runOnServer;
@Test
public void testFilterByType() {
MatcherAssert.assertThat(getIdps(null, null), Matchers.containsInAnyOrder("myoidc", "myspiffe", "mysaml"));
MatcherAssert.assertThat(getIdps(IdentityProviderType.ANY, null), Matchers.containsInAnyOrder("myoidc", "myspiffe", "mysaml"));
MatcherAssert.assertThat(getIdps(IdentityProviderType.USER_AUTHENTICATION, null), Matchers.containsInAnyOrder("myoidc", "mysaml"));
MatcherAssert.assertThat(getIdps(IdentityProviderType.CLIENT_ASSERTION, null), Matchers.containsInAnyOrder("myoidc", "myspiffe"));
}
@Test
public void testFilterByCapability() {
MatcherAssert.assertThat(getIdps(null, IdentityProviderCapability.USER_LINKING), Matchers.containsInAnyOrder("myoidc", "mysaml"));
}
@Test
public void testDefaultsToUserAuthenticationProviders() {
runOnServer.run(s -> {
List<String> idps = s.identityProviders().getAllStream().map(IdentityProviderModel::getAlias).toList();
MatcherAssert.assertThat(idps, Matchers.containsInAnyOrder("myoidc", "mysaml"));
});
}
private List<String> getIdps(IdentityProviderType type, IdentityProviderCapability capability) {
return realm.admin().identityProviders()
.find(type != null ? type.name() : null, capability != null ? capability.name() : null, null, null, 0, 100)
.stream().map(IdentityProviderRepresentation::getAlias).toList();
}
public static class MyRealm implements RealmConfig {
@Override
public RealmConfigBuilder configure(RealmConfigBuilder realm) {
return realm
.identityProvider(IdentityProviderBuilder.create()
.providerId(SpiffeIdentityProviderFactory.PROVIDER_ID)
.alias("myspiffe")
.setAttribute(IdentityProviderModel.ISSUER, "spiffe://mytrust")
.setAttribute(SpiffeIdentityProviderConfig.BUNDLE_ENDPOINT_KEY, "https://myendpoint")
.build())
.identityProvider(IdentityProviderBuilder.create()
.providerId(OIDCIdentityProviderFactory.PROVIDER_ID)
.alias("myoidc")
.build())
.identityProvider(IdentityProviderBuilder.create()
.providerId(SAMLIdentityProviderFactory.PROVIDER_ID)
.alias("mysaml")
.build());
}
}
}

View File

@ -11,8 +11,6 @@ import org.keycloak.broker.spiffe.SpiffeIdentityProviderConfig;
import org.keycloak.broker.spiffe.SpiffeIdentityProviderFactory;
import org.keycloak.http.simple.SimpleHttp;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderShowInAccountConsole;
import org.keycloak.models.IdentityProviderStorageProvider;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.InjectSimpleHttp;
@ -62,16 +60,6 @@ public class SpiffeConfigTest {
checkNotDisplayOnLoginPages("testConfig");
checkNoIdpsInAccountConsole();
rep = realm.admin().identityProviders().get("testConfig").toRepresentation();
Assertions.assertTrue(rep.isHideOnLogin());
Assertions.assertEquals(IdentityProviderShowInAccountConsole.NEVER.name(), rep.getConfig().get(IdentityProviderModel.SHOW_IN_ACCOUNT_CONSOLE));
runOnServer.run(s -> {
IdentityProviderModel idp = s.getProvider(IdentityProviderStorageProvider.class, "jpa").getByAlias("testConfig");
Assertions.assertTrue(idp.isHideOnLogin());
Assertions.assertEquals(IdentityProviderShowInAccountConsole.NEVER.name(), idp.getConfig().get(IdentityProviderModel.SHOW_IN_ACCOUNT_CONSOLE));
});
}
@Test

View File

@ -8,6 +8,7 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.models.IdentityProviderQuery;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.authorization.model.Policy;
@ -239,7 +240,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(server, clientPolicyRep);
management.users().adminImpersonatingPermission().addAssociatedPolicy(clientPolicy);
management.users().adminImpersonatingPermission().setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
session.identityProviders().getAllStream().forEach(idp -> {
session.identityProviders().getAllStream(IdentityProviderQuery.userAuthentication()).forEach(idp -> {
management.idps().setPermissionsEnabled(idp, true);
management.idps().exchangeToPermission(idp).addAssociatedPolicy(clientPolicy);
});