From 145ed26e6f754095bfd04cdc542bbb9335fe2928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 18 Nov 2024 17:49:50 +0100 Subject: [PATCH] Reject non-scalar endpoint parameter with Jersey Actuator endpoints should only declare simple type in the signature of an operation. In particular, nested types are not supported. While this is enforced in Spring MVC and Spring Webflux, the Jersey implementation leniently allowed to bind such types prior to this commit. This commit adapts the expectation in the Jersey implementation so that it rejects such request as well. Closes gh-43209 --- .../jersey/JerseyEndpointResourceFactory.java | 9 +++++--- .../AbstractWebEndpointIntegrationTests.java | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java index cf27addf3a1..4f9bb5d2537 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,7 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.util.AntPathMatcher; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -189,8 +190,10 @@ public class JerseyEndpointResourceFactory { } @SuppressWarnings("unchecked") - private Map extractBodyArguments(ContainerRequestContext data) { - Map entity = ((ContainerRequest) data).readEntity(Map.class); + private Map extractBodyArguments(ContainerRequestContext data) { + Map entity = ((ContainerRequest) data).readEntity(Map.class, + new ParameterizedTypeReference>() { + }.getType()); return (entity != null) ? entity : Collections.emptyMap(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java index cc2286270fd..dbdf5569ca4 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java @@ -314,6 +314,24 @@ public abstract class AbstractWebEndpointIntegrationTests { + Map body = new HashMap<>(); + body.put("generic", List.of("one", "two")); + client.post().uri("/test/one").bodyValue(body).exchange().expectStatus().isBadRequest(); + }); + } + + @Test + void writeOperationWithNestedValueIsRejected() { + load(TestEndpointConfiguration.class, (client) -> { + Map body = new HashMap<>(); + body.put("generic", Map.of("nested", "one")); + client.post().uri("/test/one").bodyValue(body).exchange().expectStatus().isBadRequest(); + }); + } + @Test void writeOperationWithVoidResponse() { load(VoidWriteResponseEndpointConfiguration.class, (context, client) -> { @@ -968,6 +986,11 @@ public abstract class AbstractWebEndpointIntegrationTests deletePart(@Selector String part) { return Collections.singletonMap("part", part);