Remove JSON Patch support from the Client API v2 MVP (#44120)

Closes: #43572

Signed-off-by: Peter Zaoral <pzaoral@redhat.com>
This commit is contained in:
Peter Zaoral 2025-11-18 10:42:10 +01:00 committed by GitHub
parent a4c583246d
commit b9d94d325b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 16 additions and 40 deletions

View File

@ -436,10 +436,6 @@
</dependency>
<!-- Keycloak Dependencies-->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>zjsonpatch</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>

View File

@ -40,9 +40,5 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>zjsonpatch</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -27,7 +27,7 @@ public interface ClientApi {
ClientRepresentation createOrUpdateClient(@Valid ClientRepresentation client);
@PATCH
@Consumes({MediaType.APPLICATION_JSON_PATCH_JSON, CONTENT_TYPE_MERGE_PATCH})
@Consumes(CONTENT_TYPE_MERGE_PATCH)
@Produces(MediaType.APPLICATION_JSON)
ClientRepresentation patchClient(JsonNode patch);

View File

@ -20,13 +20,12 @@ import org.keycloak.services.client.ClientService;
import org.keycloak.services.client.DefaultClientService;
import org.keycloak.services.resources.admin.ClientResource;
import org.keycloak.services.resources.admin.ClientsResource;
import org.keycloak.services.util.ObjectMapperResolver;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import io.fabric8.zjsonpatch.JsonPatch;
import io.fabric8.zjsonpatch.JsonPatchException;
public class DefaultClientApi implements ClientApi {
@ -39,6 +38,9 @@ public class DefaultClientApi implements ClientApi {
private final ClientResource clientResource;
private final ClientsResource clientsResource;
private final String clientId;
private final ObjectMapper objectMapper;
private static final ObjectMapper MAPPER = new ObjectMapperResolver().getContext(null);
public DefaultClientApi(KeycloakSession session, ClientsResource clientsResource, ClientResource clientResource, String clientId) {
this.session = session;
@ -49,6 +51,7 @@ public class DefaultClientApi implements ClientApi {
this.clientsResource = clientsResource;
this.clientResource = clientResource;
this.clientId = clientId;
this.objectMapper = MAPPER;
}
@Override
@ -76,28 +79,22 @@ public class DefaultClientApi implements ClientApi {
@Override
public ClientRepresentation patchClient(JsonNode patch) {
// patches don't yet allow for creating
ClientRepresentation client = getClient();
try {
String contentType = session.getContext().getHttpRequest().getHttpHeaders().getHeaderString(HttpHeaders.CONTENT_TYPE);
ClientRepresentation updated = null;
// TODO: there should be a more centralized objectmapper
ObjectMapper objectMapper = new ObjectMapper();
if (MediaType.valueOf(contentType).getSubtype().equals(MediaType.APPLICATION_JSON_PATCH_JSON_TYPE.getSubtype())) {
JsonNode patchedNode = JsonPatch.apply(patch, objectMapper.convertValue(client, JsonNode.class));
updated = objectMapper.convertValue(patchedNode, ClientRepresentation.class);
} else { // must be merge patch
final ObjectReader objectReader = objectMapper.readerForUpdating(client);
updated = objectReader.readValue(patch);
MediaType mediaType = contentType == null ? null : MediaType.valueOf(contentType);
MediaType mergePatch = MediaType.valueOf(ClientApi.CONTENT_TYPE_MERGE_PATCH);
if (mediaType == null || !mediaType.isCompatible(mergePatch)) {
throw new WebApplicationException("Unsupported media type", Response.Status.UNSUPPORTED_MEDIA_TYPE);
}
final ObjectReader objectReader = objectMapper.readerForUpdating(client);
ClientRepresentation updated = objectReader.readValue(patch);
validateUnknownFields(updated, response);
return clientService.createOrUpdate(clientsResource, clientResource, realm, updated, true).representation();
} catch (JsonPatchException e) {
// TODO: kubernetes uses 422 instead
throw new WebApplicationException(e.getMessage(), Response.Status.BAD_REQUEST);
} catch (IllegalArgumentException e) {
throw new WebApplicationException("Unsupported media type", Response.Status.UNSUPPORTED_MEDIA_TYPE);
} catch (JsonProcessingException e) {
throw new WebApplicationException(e.getMessage(), Response.Status.BAD_REQUEST);
} catch (IOException e) {

View File

@ -90,23 +90,10 @@ public class ClientApiV2Test {
public void jsonPatchClient() throws Exception {
HttpPatch request = new HttpPatch(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/account");
setAuthHeader(request);
request.setEntity(new StringEntity("not json"));
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_PATCH_JSON);
try (var response = client.execute(request)) {
EntityUtils.consumeQuietly(response.getEntity());
assertEquals(400, response.getStatusLine().getStatusCode());
}
request.setEntity(new StringEntity(
"""
[{"op": "add", "path": "/description", "value": "I'm a description"}]
"""));
try (var response = client.execute(request)) {
assertEquals(200, response.getStatusLine().getStatusCode());
ClientRepresentation client = mapper.createParser(response.getEntity().getContent()).readValueAs(ClientRepresentation.class);
assertEquals("I'm a description", client.getDescription());
assertEquals(415, response.getStatusLine().getStatusCode());
}
}