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
This commit is contained in:
Stéphane Nicoll
2024-11-18 17:49:50 +01:00
parent 9f6b25e9a6
commit 145ed26e6f
2 changed files with 29 additions and 3 deletions

View File

@@ -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<String, Object> extractBodyArguments(ContainerRequestContext data) {
Map<String, Object> entity = ((ContainerRequest) data).readEntity(Map.class);
private Map<String, String> extractBodyArguments(ContainerRequestContext data) {
Map<String, String> entity = ((ContainerRequest) data).readEntity(Map.class,
new ParameterizedTypeReference<Map<String, String>>() {
}.getType());
return (entity != null) ? entity : Collections.emptyMap();
}

View File

@@ -314,6 +314,24 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
});
}
@Test
void writeOperationWithListOfValuesIsRejected() {
load(TestEndpointConfiguration.class, (client) -> {
Map<String, Object> 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<String, Object> 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<T extends Configurable
this.endpointDelegate.write(foo, bar);
}
@WriteOperation
void writeGeneric(@Selector String part, Object generic) {
this.endpointDelegate.write(generic.toString(), generic.toString());
}
@DeleteOperation
Map<String, Object> deletePart(@Selector String part) {
return Collections.singletonMap("part", part);