Allow returning attributes when querying organizations

Closes #34590

Signed-off-by: Himanshi Gupta <higupta@redhat.com>
Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
Co-authored-by: Himanshi Gupta <higupta@redhat.com>
This commit is contained in:
Pedro Igor 2024-11-22 07:50:28 -03:00 committed by GitHub
parent 17863d1d4f
commit cc64375c88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 54 additions and 13 deletions

View File

@ -83,6 +83,28 @@ public interface OrganizationsResource {
@QueryParam("max") Integer max
);
/**
* Returns all organizations that match the specified filters.
*
* @param search a {@code String} representing either an organization name or domain.
* @param exact if {@code true}, the organizations will be searched using exact match for the {@code search} param - i.e.
* either the organization name or one of its domains must match exactly the {@code search} param. If false,
* the method returns all organizations whose name or (domains) partially match the {@code search} param.
* @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}.
* @param briefRepresentation if {@code true} the full representation is to be returned. Otherwise, only the basic fields are returned.
* @return a list containing the matched organizations.
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
List<OrganizationRepresentation> search(
@QueryParam("search") String search,
@QueryParam("exact") Boolean exact,
@QueryParam("first") Integer first,
@QueryParam("max") Integer max,
@QueryParam("briefRepresentation") Boolean briefRepresentation
);
/**
* Returns all organizations that contain attributes matching the specified query.
*

View File

@ -1296,7 +1296,7 @@ public class ModelToRepresentation {
}
public static OrganizationRepresentation toRepresentation(OrganizationModel model) {
OrganizationRepresentation rep = toBriefRepresentation(model);
OrganizationRepresentation rep = toBriefRepresentation(model,false);
if (rep == null) {
return null;
}
@ -1304,7 +1304,7 @@ public class ModelToRepresentation {
return rep;
}
public static OrganizationRepresentation toBriefRepresentation(OrganizationModel model) {
public static OrganizationRepresentation toBriefRepresentation(OrganizationModel model, Boolean briefRepresentation) {
if (model == null) {
return null;
}
@ -1312,6 +1312,9 @@ public class ModelToRepresentation {
rep.setId(model.getId());
rep.setName(model.getName());
rep.setAlias(model.getAlias());
if (briefRepresentation) {
rep.setAttributes(model.getAttributes());
}
rep.setEnabled(model.isEnabled());
rep.setRedirectUrl(model.getRedirectUrl());
rep.setDescription(model.getDescription());

View File

@ -127,23 +127,24 @@ public class OrganizationsResource {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
@Operation( summary = "Returns a paginated list of organizations filtered according to the specified parameters")
@Operation(summary = "Returns a paginated list of organizations filtered according to the specified parameters")
public Stream<OrganizationRepresentation> search(
@Parameter(description = "A String representing either an organization name or domain") @QueryParam("search") String search,
@Parameter(description = "A query to search for custom attributes, in the format 'key1:value2 key2:value2'") @QueryParam("q") String searchQuery,
@Parameter(description = "Boolean which defines whether the param 'search' must match exactly or not") @QueryParam("exact") Boolean exact,
@Parameter(description = "The position of the first result to be processed (pagination offset)") @QueryParam("first") @DefaultValue("0") Integer first,
@Parameter(description = "The maximum number of results to be returned - defaults to 10") @QueryParam("max") @DefaultValue("10") Integer max
) {
@Parameter(description = "The maximum number of results to be returned - defaults to 10") @QueryParam("max") @DefaultValue("10") Integer max,
@Parameter(description = "if true, return the full representation. Otherwise, only the basic fields are returned.") @QueryParam("briefRepresentation") @DefaultValue("false") boolean briefRepresentation
) {
auth.realm().requireManageRealm();
Organizations.checkEnabled(provider);
// check if are searching orgs by attribute.
if (StringUtil.isNotBlank(searchQuery)) {
Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery);
return provider.getAllStream(attributes, first, max).map(ModelToRepresentation::toBriefRepresentation);
return provider.getAllStream(attributes, first, max).map(model -> ModelToRepresentation.toBriefRepresentation(model, briefRepresentation));
} else {
return provider.getAllStream(search, exact, first, max).map(ModelToRepresentation::toBriefRepresentation);
return provider.getAllStream(search, exact, first, max).map(model -> ModelToRepresentation.toBriefRepresentation(model, briefRepresentation));
}
}

View File

@ -21,6 +21,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@ -43,6 +44,7 @@ import java.util.stream.Collectors;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.io.IOException;
import java.util.stream.IntStream;
@ -147,6 +149,15 @@ public class OrganizationTest extends AbstractOrganizationTest {
assertThat(orgRep.getDomain("gtbank.net"), not(nullValue()));
assertThat(orgRep.getAttributes(), nullValue());
orgRep.singleAttribute("foo", "bar");
orgRep.singleAttribute("bar", "foo");
testRealm().organizations().get(orgRep.getId()).update(orgRep).close();
existing = testRealm().organizations().search("gtbank.net", true, 0, 10, true);
assertThat(existing, hasSize(1));
orgRep = existing.get(0);
assertThat(orgRep.getAttributes(), notNullValue());
assertThat(2, is(orgRep.getAttributes().size()));
existing = testRealm().organizations().search("nonexistent.org", true, 0, 10);
assertThat(existing, is(empty()));
@ -268,7 +279,8 @@ public class OrganizationTest extends AbstractOrganizationTest {
try {
organization.toRepresentation();
fail("should be deleted");
} catch (NotFoundException ignore) {}
} catch (NotFoundException ignore) {
}
}
@Test
@ -414,15 +426,18 @@ public class OrganizationTest extends AbstractOrganizationTest {
try {
testRealm().organizations().getAll();
fail("Expected NotFoundException");
} catch (NotFoundException expected) {}
} catch (NotFoundException expected) {
}
try {
testRealm().organizations().search("*");
fail("Expected NotFoundException");
} catch (NotFoundException expected) {}
} catch (NotFoundException expected) {
}
try {
testRealm().organizations().get(existing.getId()).toRepresentation();
fail("Expected NotFoundException");
} catch (NotFoundException expected) {}
} catch (NotFoundException expected) {
}
}
}
@ -461,8 +476,8 @@ public class OrganizationTest extends AbstractOrganizationTest {
@Test
public void testCount() {
List<String> orgIds = IntStream.range(0, 10)
.mapToObj(i -> createOrganization("kc.org." + i).getId())
.collect(Collectors.toList());
.mapToObj(i -> createOrganization("kc.org." + i).getId())
.collect(Collectors.toList());
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class);