diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java index 4edf835a23..0cee01e81d 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java @@ -47,7 +47,15 @@ public interface IdentityProvidersResource { @GET @Path("instances") @Produces(MediaType.APPLICATION_JSON) - List find(@QueryParam("search") String search, @QueryParam("briefRepresentation") Boolean briefRepresentation, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults); + List find(@QueryParam("search") String search, @QueryParam("briefRepresentation") Boolean briefRepresentation, + @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults); + + @GET + @Path("instances") + @Produces(MediaType.APPLICATION_JSON) + List find(@QueryParam("search") String search, @QueryParam("briefRepresentation") Boolean briefRepresentation, + @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults, + @QueryParam("realmOnly") Boolean realmOnly); @POST @Path("instances") diff --git a/js/apps/admin-ui/src/organizations/IdentityProviderSelect.tsx b/js/apps/admin-ui/src/organizations/IdentityProviderSelect.tsx index cebd116a7e..00766cdebc 100644 --- a/js/apps/admin-ui/src/organizations/IdentityProviderSelect.tsx +++ b/js/apps/admin-ui/src/organizations/IdentityProviderSelect.tsx @@ -63,13 +63,14 @@ export const IdentityProviderSelect = ({ async () => { const params: IdentityProvidersQuery = { max: 20, + realmOnly: true, }; if (search) { params.search = search; } const idps = await adminClient.identityProviders.find(params); - return idps.filter((i) => !i.config?.["kc.org"]); + return idps; }, setIdps, [search], diff --git a/js/libs/keycloak-admin-client/src/resources/identityProviders.ts b/js/libs/keycloak-admin-client/src/resources/identityProviders.ts index 160632c83a..7ce214d3d8 100644 --- a/js/libs/keycloak-admin-client/src/resources/identityProviders.ts +++ b/js/libs/keycloak-admin-client/src/resources/identityProviders.ts @@ -12,6 +12,7 @@ export interface PaginatedQuery { export interface IdentityProvidersQuery extends PaginatedQuery { search?: string; + realmOnly?: boolean; } export class IdentityProviders extends Resource<{ realm?: string }> { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIDPProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIDPProvider.java index c976ed5cba..06cbe65fad 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIDPProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIDPProvider.java @@ -137,11 +137,6 @@ public class InfinispanIDPProvider implements IDPProvider { return idpDelegate.getByFlow(flowId, search, first, max); } - @Override - public Stream getAllStream(String search, Integer first, Integer max) { - return idpDelegate.getAllStream(search, first, max); - } - @Override public Stream getAllStream(Map attrs, Integer first, Integer max) { return idpDelegate.getAllStream(attrs, first, max); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaIDPProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaIDPProvider.java index 75df2f7eb7..bb08dd9469 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaIDPProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaIDPProvider.java @@ -54,6 +54,7 @@ import static org.keycloak.models.IdentityProviderModel.HIDE_ON_LOGIN; import static org.keycloak.models.IdentityProviderModel.LINK_ONLY; import static org.keycloak.models.IdentityProviderModel.ORGANIZATION_ID; import static org.keycloak.models.IdentityProviderModel.POST_BROKER_LOGIN_FLOW_ID; +import static org.keycloak.models.IdentityProviderModel.SEARCH; import static org.keycloak.models.jpa.PaginationUtils.paginateQuery; import static org.keycloak.utils.StreamsUtil.closing; @@ -205,24 +206,6 @@ public class JpaIDPProvider implements IDPProvider { return toModel(getEntityByAlias(alias)); } - @Override - public Stream getAllStream(String search, Integer first, Integer max) { - CriteriaBuilder builder = em.getCriteriaBuilder(); - CriteriaQuery query = builder.createQuery(IdentityProviderEntity.class); - Root idp = query.from(IdentityProviderEntity.class); - - List predicates = new ArrayList<>(); - predicates.add(builder.equal(idp.get("realmId"), getRealm().getId())); - - if (StringUtil.isNotBlank(search)) { - predicates.add(this.getAliasSearchPredicate(search, builder, idp)); - } - - query.orderBy(builder.asc(idp.get(ALIAS))); - TypedQuery typedQuery = em.createQuery(query.select(idp).where(predicates.toArray(Predicate[]::new))); - return closing(paginateQuery(typedQuery, first, max).getResultStream()).map(this::toModel); - } - @Override public Stream getAllStream(Map attrs, Integer first, Integer max) { CriteriaBuilder builder = em.getCriteriaBuilder(); @@ -259,6 +242,12 @@ public class JpaIDPProvider implements IDPProvider { predicates.add(builder.equal(idp.get(key), value)); } break; + } + case SEARCH: { + if (StringUtil.isNotBlank(value)) { + predicates.add(this.getAliasSearchPredicate(value, builder, idp)); + } + break; } default: { String dbProductName = em.unwrap(Session.class).doReturningWork(connection -> connection.getMetaData().getDatabaseProductName()); MapJoin configJoin = idp.joinMap("config"); diff --git a/server-spi/src/main/java/org/keycloak/models/IDPProvider.java b/server-spi/src/main/java/org/keycloak/models/IDPProvider.java index 8894356ca8..8a772e13ac 100644 --- a/server-spi/src/main/java/org/keycloak/models/IDPProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/IDPProvider.java @@ -16,7 +16,6 @@ */ package org.keycloak.models; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; @@ -99,22 +98,9 @@ public interface IDPProvider extends Provider { * @return a non-null stream of {@link IdentityProviderModel}s. */ default Stream getAllStream() { - return getAllStream("", null, null); + return getAllStream(Map.of(), null, null); } - /** - * Returns all identity providers in the realm filtered according to the specified parameters. - * - * @param search a {@link String} representing an identity provider alias (partial or exact). If the value is enclosed - * in double quotes, the method treats it as an exact search (e.g. {@code "name"}). If the value is enclosed in - * wildcards, the method treats it as an infix search (e.g. {@code *name*}). Otherwise, the method treats it as a - * prefix search (i.e. {@code name*} and {@code name} return the same results). - * @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 getAllStream(String search, Integer first, Integer max); - /** * Returns all identity providers in the realm filtered according to the specified parameters. * @@ -134,7 +120,7 @@ public interface IDPProvider extends Provider { * @return a non-null stream of {@link IdentityProviderModel}s that match the search criteria. */ default Stream getByOrganization(String orgId, Integer first, Integer max) { - return getAllStream(Map.of(IdentityProviderModel.ORGANIZATION_ID, orgId), first, max); + return getAllStream(Map.of(IdentityProviderModel.ORGANIZATION_ID, orgId != null ? orgId : ""), first, max); } /** diff --git a/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java b/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java index 53360fcbe4..df006c94e8 100755 --- a/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java +++ b/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java @@ -50,6 +50,7 @@ public class IdentityProviderModel implements Serializable { public static final String ORGANIZATION_ID = "organizationId"; public static final String PASS_MAX_AGE = "passMaxAge"; public static final String POST_BROKER_LOGIN_FLOW_ID = "postBrokerLoginFlowId"; + public static final String SEARCH = "search"; public static final String SYNC_MODE = "syncMode"; private String internalId; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java index 2fb91d5645..943db7d238 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java @@ -55,7 +55,10 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; 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; @@ -180,18 +183,28 @@ public class IdentityProvidersResource { @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, - @Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults) { + @Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults, + @Parameter(description = "Boolean which defines if only realm-level IDPs (not associated with orgs) should be returned (default: false)") @QueryParam("realmOnly") Boolean realmOnly) { this.auth.realm().requireViewIdentityProviders(); if (maxResults == null) { maxResults = 100; // always set a maximum of 100 by default } - Function toRepresentation = Optional.ofNullable(briefRepresentation).orElse(false) + Function toRepresentation = Optional.ofNullable(briefRepresentation).orElse(false) ? m -> ModelToRepresentation.toBriefRepresentation(realm, m) : m -> StripSecretsUtils.stripSecrets(session, ModelToRepresentation.toRepresentation(realm, m)); - return session.identityProviders().getAllStream(search, firstResult, maxResults).map(toRepresentation); + boolean searchRealmOnlyIDPs = Optional.ofNullable(realmOnly).orElse(false); + + Map searchOptions = new HashMap<>(); + if (StringUtil.isNotBlank(search)) { + searchOptions.put(IdentityProviderModel.SEARCH, search); + } + if (searchRealmOnlyIDPs) { + searchOptions.put(IdentityProviderModel.ORGANIZATION_ID, null); + } + return session.identityProviders().getAllStream(searchOptions, firstResult, maxResults).map(toRepresentation); } /**