Skip to content

Commit

Permalink
Ensure identity providers returned to the org IDP selection are IDPs …
Browse files Browse the repository at this point in the history
…not associated with any orgs.

Closes keycloak#32238

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
  • Loading branch information
sguilhen authored and pedroigor committed Aug 21, 2024
1 parent be766c7 commit 585d179
Show file tree
Hide file tree
Showing 8 changed files with 38 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ public interface IdentityProvidersResource {
@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);
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
@Path("instances")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface PaginatedQuery {

export interface IdentityProvidersQuery extends PaginatedQuery {
search?: string;
realmOnly?: boolean;
}

export class IdentityProviders extends Resource<{ realm?: string }> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,6 @@ public Stream<String> getByFlow(String flowId, String search, Integer first, Int
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
public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) {
return idpDelegate.getAllStream(attrs, first, max);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
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;

Expand Down Expand Up @@ -205,24 +206,6 @@ public IdentityProviderModel getByAlias(String 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
public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Integer first, Integer max) {
CriteriaBuilder builder = em.getCriteriaBuilder();
Expand Down Expand Up @@ -259,6 +242,12 @@ public Stream<IdentityProviderModel> getAllStream(Map<String, String> attrs, Int
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<IdentityProviderEntity, String, String> configJoin = idp.joinMap("config");
Expand Down
18 changes: 2 additions & 16 deletions server-spi/src/main/java/org/keycloak/models/IDPProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.keycloak.models;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -99,22 +98,9 @@ default IdentityProviderModel getByIdOrAlias(String key) {
* @return a non-null stream of {@link IdentityProviderModel}s.
*/
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.
*
Expand All @@ -134,7 +120,7 @@ default Stream<IdentityProviderModel> getAllStream() {
* @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), first, max);
return getAllStream(Map.of(IdentityProviderModel.ORGANIZATION_ID, orgId != null ? orgId : ""), first, max);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
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;
Expand Down Expand Up @@ -180,18 +183,28 @@ public Stream<IdentityProviderRepresentation> getIdentityProviders(
@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<IdentityProviderModel, IdentityProviderRepresentation> toRepresentation = Optional.<Boolean>ofNullable(briefRepresentation).orElse(false)
Function<IdentityProviderModel, IdentityProviderRepresentation> 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<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);
}

/**
Expand Down

0 comments on commit 585d179

Please sign in to comment.