Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

@JsonFilter("projection")
@JsonInclude(JsonInclude.Include.NON_ABSENT)
public class BaseRepresentation {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.keycloak.quarkus.runtime.integration.jaxrs;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.ext.Provider;
import org.keycloak.services.util.Projections;

@Provider
@PreMatching
public class ProjectionsPreFilter implements ContainerRequestFilter {

@Override
public void filter(ContainerRequestContext requestContext) {
String projectionsParam = requestContext.getUriInfo().getQueryParameters().getFirst(Projections.PARAM);
if (projectionsParam != null) {
Projections.setCurrent(requestContext, projectionsParam);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.stream.Stream;

import jakarta.validation.Valid;
import jakarta.validation.groups.ConvertGroup;
import jakarta.ws.rs.QueryParam;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
Expand All @@ -19,8 +18,8 @@
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.representations.admin.v2.validation.CreateClient;
import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.util.Projections;

@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS_V2)
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
Expand All @@ -40,6 +39,6 @@ ClientRepresentation createClient(@Valid ClientRepresentation client,
@QueryParam("fieldValidation") FieldValidation fieldValidation);

@Path("{id}")
ClientApi client(@PathParam("id") String id);
ClientApi client(@PathParam("id") String id, @QueryParam(Projections.PARAM) Projections projections);
}

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import org.keycloak.validation.jakarta.JakartaValidatorProvider;
import org.keycloak.services.util.Projections;

public class DefaultClientsApi implements ClientsApi {
private final KeycloakSession session;
Expand Down Expand Up @@ -53,7 +54,7 @@ public ClientRepresentation createClient(@Valid ClientRepresentation client, Fie
}

@Override
public ClientApi client(@PathParam("id") String clientId) {
public ClientApi client(@PathParam("id") String clientId, @QueryParam("fields") Projections projections) {
var client = Optional.ofNullable(session.clients().getClientByClientId(realm, clientId)).orElseThrow(() -> new NotFoundException("Client cannot be found"));
session.getContext().setClient(client);
return session.getProvider(ClientApi.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jdk8.StreamSerializer;

import jakarta.ws.rs.ext.ContextResolver;
import jakarta.ws.rs.ext.Provider;

import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -62,6 +67,12 @@ public static ObjectMapper createStreamSerializer() {
mapper.findAndRegisterModules();
}

if (Boolean.parseBoolean(System.getProperty("keycloak.jsonEnableProjections", "true"))) {
// used via @JsonFilter in BaseRepresentation
FilterProvider filters = new SimpleFilterProvider().addFilter("projection", new FieldProjectionFilter());
mapper.setFilterProvider(filters);
}

return mapper;
}

Expand All @@ -74,4 +85,17 @@ private static class ObjectMapperInitializer {

private static final ObjectMapper OBJECT_MAPPER = createStreamSerializer();
}

private static class FieldProjectionFilter extends SimpleBeanPropertyFilter {

protected boolean include(PropertyWriter writer) {

Projections projections = Projections.getCurrent();
if (projections == null) {
return super.include(writer);
}

return projections.contains(writer.getName());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.keycloak.services.util;

import jakarta.ws.rs.container.ContainerRequestContext;
import org.jboss.resteasy.reactive.server.core.CurrentRequestManager;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

public class Projections {

public static final String PARAM = "fields";

private static final String REQUEST_PROPERTY = "projections";

private static final Pattern COMMA = Pattern.compile(",");

private static final Set<String> INCLUDE_ALL = Collections.unmodifiableSet(new HashSet<>());

private final Set<String> fields;

public Projections(String raw) {
this.fields = raw == null ? INCLUDE_ALL : Set.of(COMMA.split(raw));
}

public Set<String> getFields() {
return fields;
}

@Override
public String toString() {
return fields.toString();
}

public boolean contains(String attr) {
if (fields == INCLUDE_ALL) {
return true;
}
return fields.contains(attr);
}

public static Projections getCurrent() {
ResteasyReactiveRequestContext context = CurrentRequestManager.get();
if (context == null) {
return null;
}
return (Projections) context.getProperty(Projections.REQUEST_PROPERTY);
}

public static void setCurrent(ContainerRequestContext requestContext, String projectionsParam) {
// TODO avoid duplicate parsing here and on resource method
requestContext.setProperty(Projections.REQUEST_PROPERTY, new Projections(projectionsParam));
}
}
Loading