Ensure identity providers returned to the org IDP selection are IDPs not associated with any orgs.

Closes #32238

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen 2024-08-20 10:47:53 -03:00 committed by Pedro Igor
parent be766c751a
commit 585d179fe0
8 changed files with 38 additions and 44 deletions

View file

@ -47,7 +47,15 @@ public interface IdentityProvidersResource {
@GET @GET
@Path("instances") @Path("instances")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
List<IdentityProviderRepresentation> find(@QueryParam("search") String search, @QueryParam("briefRepresentation") Boolean briefRepresentation, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults); List<IdentityProviderRepresentation> 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<IdentityProviderRepresentation> find(@QueryParam("search") String search, @QueryParam("briefRepresentation") Boolean briefRepresentation,
@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults,
@QueryParam("realmOnly") Boolean realmOnly);
@POST @POST
@Path("instances") @Path("instances")

View file

@ -63,13 +63,14 @@ export const IdentityProviderSelect = ({
async () => { async () => {
const params: IdentityProvidersQuery = { const params: IdentityProvidersQuery = {
max: 20, max: 20,
realmOnly: true,
}; };
if (search) { if (search) {
params.search = search; params.search = search;
} }
const idps = await adminClient.identityProviders.find(params); const idps = await adminClient.identityProviders.find(params);
return idps.filter((i) => !i.config?.["kc.org"]); return idps;
}, },
setIdps, setIdps,
[search], [search],

View file

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

View file

@ -137,11 +137,6 @@ public class InfinispanIDPProvider implements IDPProvider {
return idpDelegate.getByFlow(flowId, search, first, max); return idpDelegate.getByFlow(flowId, search, first, max);
} }
@Override
public Stream<IdentityProviderModel> getAllStream(String search, Integer first, Integer max) {
return idpDelegate.getAllStream(search, first, max);
}
@Override @Override
public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) { public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) {
return idpDelegate.getAllStream(attrs, first, max); return idpDelegate.getAllStream(attrs, first, max);

View file

@ -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.LINK_ONLY;
import static org.keycloak.models.IdentityProviderModel.ORGANIZATION_ID; 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.POST_BROKER_LOGIN_FLOW_ID;
import static org.keycloak.models.IdentityProviderModel.SEARCH;
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery; import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
import static org.keycloak.utils.StreamsUtil.closing; import static org.keycloak.utils.StreamsUtil.closing;
@ -205,24 +206,6 @@ public class JpaIDPProvider implements IDPProvider {
return toModel(getEntityByAlias(alias)); return toModel(getEntityByAlias(alias));
} }
@Override
public Stream<IdentityProviderModel> getAllStream(String search, Integer first, Integer max) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<IdentityProviderEntity> query = builder.createQuery(IdentityProviderEntity.class);
Root<IdentityProviderEntity> idp = query.from(IdentityProviderEntity.class);
List<Predicate> 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<IdentityProviderEntity> typedQuery = em.createQuery(query.select(idp).where(predicates.toArray(Predicate[]::new)));
return closing(paginateQuery(typedQuery, first, max).getResultStream()).map(this::toModel);
}
@Override @Override
public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) { public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) {
CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaBuilder builder = em.getCriteriaBuilder();
@ -259,6 +242,12 @@ public class JpaIDPProvider implements IDPProvider {
predicates.add(builder.equal(idp.get(key), value)); predicates.add(builder.equal(idp.get(key), value));
} }
break; break;
}
case SEARCH: {
if (StringUtil.isNotBlank(value)) {
predicates.add(this.getAliasSearchPredicate(value, builder, idp));
}
break;
} default: { } default: {
String dbProductName = em.unwrap(Session.class).doReturningWork(connection -> connection.getMetaData().getDatabaseProductName()); String dbProductName = em.unwrap(Session.class).doReturningWork(connection -> connection.getMetaData().getDatabaseProductName());
MapJoin<IdentityProviderEntity, String, String> configJoin = idp.joinMap("config"); MapJoin<IdentityProviderEntity, String, String> configJoin = idp.joinMap("config");

View file

@ -16,7 +16,6 @@
*/ */
package org.keycloak.models; package org.keycloak.models;
import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -99,22 +98,9 @@ public interface IDPProvider extends Provider {
* @return a non-null stream of {@link IdentityProviderModel}s. * @return a non-null stream of {@link IdentityProviderModel}s.
*/ */
default Stream<IdentityProviderModel> getAllStream() { default Stream<IdentityProviderModel> 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<IdentityProviderModel> getAllStream(String search, Integer first, Integer max);
/** /**
* Returns all identity providers in the realm filtered according to the specified parameters. * 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. * @return a non-null stream of {@link IdentityProviderModel}s that match the search criteria.
*/ */
default Stream<IdentityProviderModel> getByOrganization(String orgId, Integer first, Integer max) { default Stream<IdentityProviderModel> 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);
} }
/** /**

View file

@ -50,6 +50,7 @@ public class IdentityProviderModel implements Serializable {
public static final String ORGANIZATION_ID = "organizationId"; public static final String ORGANIZATION_ID = "organizationId";
public static final String PASS_MAX_AGE = "passMaxAge"; public static final String PASS_MAX_AGE = "passMaxAge";
public static final String POST_BROKER_LOGIN_FLOW_ID = "postBrokerLoginFlowId"; public static final String POST_BROKER_LOGIN_FLOW_ID = "postBrokerLoginFlowId";
public static final String SEARCH = "search";
public static final String SYNC_MODE = "syncMode"; public static final String SYNC_MODE = "syncMode";
private String internalId; private String internalId;

View file

@ -55,7 +55,10 @@ import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.keycloak.utils.StringUtil;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; 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 = "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 = "Boolean which defines whether brief representations are returned (default: false)") @QueryParam("briefRepresentation") Boolean briefRepresentation,
@Parameter(description = "Pagination offset") @QueryParam("first") Integer firstResult, @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(); this.auth.realm().requireViewIdentityProviders();
if (maxResults == null) { if (maxResults == null) {
maxResults = 100; // always set a maximum of 100 by default maxResults = 100; // always set a maximum of 100 by default
} }
Function<IdentityProviderModel, IdentityProviderRepresentation> toRepresentation = Optional.<Boolean>ofNullable(briefRepresentation).orElse(false) Function<IdentityProviderModel, IdentityProviderRepresentation> toRepresentation = Optional.ofNullable(briefRepresentation).orElse(false)
? m -> ModelToRepresentation.toBriefRepresentation(realm, m) ? m -> ModelToRepresentation.toBriefRepresentation(realm, m)
: m -> StripSecretsUtils.stripSecrets(session, ModelToRepresentation.toRepresentation(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<String, String> 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);
} }
/** /**