Add @DefaultValue to max param used in Admin REST APIs (#47561)

This is used to generate Keycloak Admin REST API.
Without this a 'null' value is used, which suggests that returning a paginated list is optional.

Closes #47560

Signed-off-by: Iestyn <33298011+IestynGage@users.noreply.github.com>
This commit is contained in:
Iestyn
2026-05-01 13:49:33 +01:00
committed by GitHub
parent f1273150c8
commit e45bd9d6af
6 changed files with 21 additions and 14 deletions
@@ -130,6 +130,10 @@ public final class Constants {
public static final String GENERATE = "GENERATE";
public static final int DEFAULT_MAX_RESULTS = 100;
/**
* Used by {@code DefaultValue} annotation for when a REST endpoints max size default is set by {@link #DEFAULT_MAX_RESULTS}.
*/
public static final String DEFAULT_MAX_RESULTS_STR = "" + DEFAULT_MAX_RESULTS;
// Delimiter to be used in the configuration of authenticators (and some other components) in case that we need to save
// multiple values into single string
@@ -26,6 +26,7 @@ import java.util.stream.Collectors;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
@@ -216,7 +217,7 @@ public class PolicyService {
@QueryParam("owner") String owner,
@QueryParam("fields") String fields,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
@QueryParam("max") @DefaultValue(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResult) {
if (auth != null) {
this.auth.realm().requireViewAuthorization(resourceServer);
}
@@ -31,6 +31,7 @@ import java.util.stream.Collectors;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
@@ -402,7 +403,7 @@ public class ResourceSetService {
@QueryParam("exactName") Boolean exactName,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
@QueryParam("max") @DefaultValue(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResult) {
return find(id, name, uri, owner, type, scope, matchingUri, exactName, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, ResourceRepresentation>) (resource, deep1) -> toRepresentation(resource, resourceServer, authorization, deep1));
}
@@ -416,7 +417,7 @@ public class ResourceSetService {
@QueryParam("exactName") Boolean exactName,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult,
@QueryParam("max") @DefaultValue(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResult,
BiFunction<Resource, Boolean, ?> toRepresentation) {
requireView();
@@ -24,6 +24,7 @@ import java.util.stream.Stream;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
@@ -569,8 +570,8 @@ public class ClientResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS)
@Operation( summary = "Get user sessions for client Returns a list of user sessions associated with this client\n")
public Stream<UserSessionRepresentation> getUserSessions(@Parameter(description = "Paging offset") @QueryParam("first") Integer firstResult, @Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults) {
@Operation( summary = "Get user sessions for client. Returns a list of user sessions associated with this client.\n")
public Stream<UserSessionRepresentation> getUserSessions(@Parameter(description = "Paging offset") @QueryParam("first") Integer firstResult, @Parameter(description = "Maximum results size.") @QueryParam("max") @DefaultValue(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResults) {
auth.clients().requireView(client);
return session.sessions()
.readOnlyStreamUserSessions(client.getRealm(), client, computeFirstResult(firstResult), computeMaxResults(maxResults))
@@ -616,8 +617,8 @@ public class ClientResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS)
@Operation( summary = "Get offline sessions for client Returns a list of offline user sessions associated with this client")
public Stream<UserSessionRepresentation> getOfflineUserSessions(@Parameter(description = "Paging offset") @QueryParam("first") Integer firstResult, @Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults) {
@Operation( summary = "Get offline sessions for client. Returns a list of offline user sessions associated with this client")
public Stream<UserSessionRepresentation> getOfflineUserSessions(@Parameter(description = "Paging offset") @QueryParam("first") Integer firstResult, @Parameter(description = "Maximum results size.") @QueryParam("max") @DefaultValue(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResults) {
auth.clients().requireView(client);
return session.sessions()
.readOnlyStreamOfflineUserSessions(client.getRealm(), client, computeFirstResult(firstResult), computeMaxResults(maxResults))
@@ -902,7 +902,7 @@ public class RealmAdminResource {
@Parameter(description = "To (inclusive) date (yyyy-MM-dd) or time in Epoch timestamp millis (number of milliseconds since January 1, 1970, 00:00:00 GMT)") @QueryParam("dateTo") String dateTo,
@Parameter(description = "IP Address") @QueryParam("ipAddress") String ipAddress,
@Parameter(description = "Paging offset") @QueryParam("first") Integer firstResult,
@Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults,
@Parameter(description = "Maximum results size") @QueryParam("max") @DefaultValue(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResults,
@Parameter(description = "The direction to sort events by (asc or desc)") @QueryParam("direction") String direction) {
auth.realm().requireViewEvents();
@@ -564,7 +564,7 @@ public class RoleContainerResource extends RoleResource {
*
* @param roleName the role name.
* @param firstResult first result to return. Ignored if negative or {@code null}.
* @param maxResults maximum number of results to return. Ignored if negative or {@code null}.
* @param maxResults maximum number of results to return. Unbounded if negative.
* @param briefRepresentation Boolean which defines whether brief representations are returned (default: false)
* @return a non-empty {@code Stream} of users.
*/
@@ -582,7 +582,7 @@ public class RoleContainerResource extends RoleResource {
public Stream<UserRepresentation> getUsersInRole(final @Parameter(description = "the role name.") @PathParam("role-name") String roleName,
@Parameter(description = "Boolean which defines whether brief representations are returned (default: false)") @QueryParam("briefRepresentation") Boolean briefRepresentation,
@Parameter(description = "first result to return. Ignored if negative or {@code null}.") @QueryParam("first") Integer firstResult,
@Parameter(description = "maximum number of results to return. Ignored if negative or {@code null}.") @QueryParam("max") Integer maxResults) {
@Parameter(description = "Maximum number of results to return. Unbounded if negative.") @QueryParam("max") @DefaultValue(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResults) {
auth.roles().requireView(roleContainer);
auth.users().requireQuery();
@@ -608,7 +608,7 @@ public class RoleContainerResource extends RoleResource {
*
* @param roleName the role name.
* @param firstResult first result to return. Ignored if negative or {@code null}.
* @param maxResults maximum number of results to return. Ignored if negative or {@code null}.
* @param maxResults maximum number of results to return. Unbounded if negative.
* @param briefRepresentation if false, return a full representation of the {@code GroupRepresentation} objects.
* @return a non-empty {@code Stream} of groups.
*/
@@ -624,9 +624,9 @@ public class RoleContainerResource extends RoleResource {
@APIResponse(responseCode = "404", description = "Not Found")
})
public Stream<GroupRepresentation> getGroupsInRole(final @Parameter(description = "the role name.") @PathParam("role-name") String roleName,
@Parameter(description = "first result to return. Ignored if negative or {@code null}.") @QueryParam("first") Integer firstResult,
@Parameter(description = "maximum number of results to return. Ignored if negative or {@code null}.") @QueryParam("max") Integer maxResults,
@Parameter(description = "if false, return a full representation of the {@code GroupRepresentation} objects.") @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) {
@Parameter(description = "First result to return. Ignored if negative or {@code null}.") @QueryParam("first") Integer firstResult,
@Parameter(description = "Maximum number of results to return. Unbounded if negative.") @QueryParam(Constants.DEFAULT_MAX_RESULTS_STR) Integer maxResults,
@Parameter(description = "If false, return a full representation of the {@code GroupRepresentation} objects.") @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) {
auth.roles().requireView(roleContainer);
firstResult = firstResult != null ? firstResult : 0;