From 6e81306e6f5ecc4c7896331eda496493a98f9235 Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:58:58 +0200 Subject: [PATCH] fix: prevent leaking container validation annotations --- .../extractor/DelegatingMethodParameter.java | 4 +- .../core/service/AbstractRequestService.java | 7 +- .../core/service/GenericParameterService.java | 8 +- .../api/v30/app267/HelloController.java | 18 ++- .../api/v31/app267/HelloController.java | 18 ++- .../test/resources/results/3.0.1/app267.json | 147 +++++++++++------- .../test/resources/results/3.1.0/app267.json | 43 ++++- 7 files changed, 171 insertions(+), 74 deletions(-) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java index 20a8cac17..0cad65630 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/DelegatingMethodParameter.java @@ -309,10 +309,10 @@ public boolean isParameterObject() { } /** - * Gets field. If Is parameter object. then The Field should be not null + * Gets field. If it is a parameter object, then The {@code Field} should be not null. + * see {@link DelegatingMethodParameter#isParameterObject()} * * @return the field - * @see #isParameterObject #isParameterObject#isParameterObject */ @Nullable public Field getField() { diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java index 03afc6d41..a8d9ac278 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java @@ -710,17 +710,16 @@ public void applyBeanValidatorAnnotations(final MethodParameter methodParameter, parameter.setSchema(schema); } SchemaUtils.applyValidationsToSchema(schema, annotations, openapiVersion); - if (schema instanceof ArraySchema && methodParameter instanceof DelegatingMethodParameter mp) { + if (schema instanceof ArraySchema && methodParameter instanceof DelegatingMethodParameter delegatingMethodParameter) { java.lang.reflect.AnnotatedType annotatedType = null; if (isParameterObject) { - Field field = mp.getField(); + Field field = delegatingMethodParameter.getField(); if (field != null) { annotatedType = field.getAnnotatedType(); } } else { - java.lang.reflect.Parameter param = mp.getParameter(); - annotatedType = param.getAnnotatedType(); + annotatedType = delegatingMethodParameter.getParameter().getAnnotatedType(); } if (annotatedType instanceof AnnotatedParameterizedType paramType) { java.lang.reflect.AnnotatedType[] typeArgs = paramType.getAnnotatedActualTypeArguments(); diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java index 7caf020bb..9347b6ef1 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java @@ -431,7 +431,7 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque */ private TypeAndTypeAnnotations resolveTypeAndTypeAnnotationsForParameter(MethodParameter methodParameter) { if (methodParameter instanceof DelegatingMethodParameter delegatingMethodParameter - && delegatingMethodParameter.getField() != null) { + && delegatingMethodParameter.getField() != null) { AnnotatedType annotated = delegatingMethodParameter.getField().getAnnotatedType(); Type type = GenericTypeResolver.resolveType(annotated.getType(), methodParameter.getContainingClass()); return new TypeAndTypeAnnotations(type, Arrays.asList(annotationsFromAnnotatedTypeArguments(annotated))); @@ -448,7 +448,11 @@ private TypeAndTypeAnnotations resolveTypeAndTypeAnnotationsForParameter(MethodP : new TypeAndTypeAnnotations(type, new ArrayList<>()); } - return new TypeAndTypeAnnotations(type, Arrays.asList(methodParameter.getParameterType().getAnnotations())); + AnnotatedType annotated = methodParameter.getParameter().getAnnotatedType(); + List parameterAnnotations = Stream.concat( + Arrays.stream(annotationsFromAnnotatedTypeArguments(annotated)), + Arrays.stream(methodParameter.getParameterType().getAnnotations())).toList(); + return new TypeAndTypeAnnotations(type, parameterAnnotations); } /** diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app267/HelloController.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app267/HelloController.java index 304a5016b..16d2eecdd 100644 --- a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app267/HelloController.java +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app267/HelloController.java @@ -16,16 +16,26 @@ package test.org.springdoc.api.v30.app267; +import jakarta.validation.constraints.Pattern; import org.springdoc.core.annotations.ParameterObject; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController public class HelloController { - @GetMapping("/items") - public String list(@ParameterObject PersonQueryFilter criteria) { - return "ok"; - } + @GetMapping("/items") + public String list(@ParameterObject PersonQueryFilter criteria) { + return "ok"; + } + + @GetMapping("/persons") + public String persons( + List<@Pattern(regexp = "^[a-zA-Z]$") String> middleNames, + List<@Pattern(regexp = "^\\d+$") String> phoneNumbers) { + return "ok"; + } } diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app267/HelloController.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app267/HelloController.java index 180e6a12a..168134033 100644 --- a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app267/HelloController.java +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app267/HelloController.java @@ -16,16 +16,26 @@ package test.org.springdoc.api.v31.app267; +import jakarta.validation.constraints.Pattern; import org.springdoc.core.annotations.ParameterObject; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController public class HelloController { - @GetMapping("/items") - public String list(@ParameterObject PersonQueryFilter criteria) { - return "ok"; - } + @GetMapping("/items") + public String list(@ParameterObject PersonQueryFilter criteria) { + return "ok"; + } + + @GetMapping("/persons") + public String persons( + List<@Pattern(regexp = "^[a-zA-Z]$") String> middleNames, + List<@Pattern(regexp = "^\\d+$") String> phoneNumbers) { + return "ok"; + } } diff --git a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app267.json b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app267.json index 0c87205e7..dbbc22aa8 100644 --- a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app267.json +++ b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app267.json @@ -1,65 +1,98 @@ { - "openapi": "3.0.1", - "info": { - "title": "OpenAPI definition", - "version": "v0" + "openapi" : "3.0.1", + "info" : { + "title" : "OpenAPI definition", + "version" : "v0" }, - "servers": [ - { - "url": "http://localhost", - "description": "Generated server url" - } - ], - "paths": { - "/items": { - "get": { - "tags": [ - "hello-controller" - ], - "operationId": "list", - "parameters": [ - { - "name": "firstNames", - "in": "query", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } + "servers" : [ { + "url" : "http://localhost", + "description" : "Generated server url" + } ], + "paths" : { + "/persons" : { + "get" : { + "tags" : [ "hello-controller" ], + "operationId" : "persons", + "parameters" : [ { + "name" : "middleNames", + "in" : "query", + "required" : true, + "schema" : { + "type" : "array", + "items" : { + "pattern" : "^[a-zA-Z]$", + "type" : "string" } - }, - { - "name": "middleNames", - "in": "query", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } + } + }, { + "name" : "phoneNumbers", + "in" : "query", + "required" : true, + "schema" : { + "type" : "array", + "items" : { + "pattern" : "^\\d+$", + "type" : "string" } - }, - { - "name": "phoneNumbers", - "in": "query", - "required": false, - "schema": { - "type": "array", - "items": { - "pattern": "^\\d+$", - "type": "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } } } } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "string" + } + } + }, + "/items" : { + "get" : { + "tags" : [ "hello-controller" ], + "operationId" : "list", + "parameters" : [ { + "name" : "firstNames", + "in" : "query", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "name" : "middleNames", + "in" : "query", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "name" : "phoneNumbers", + "in" : "query", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "pattern" : "^\\d+$", + "type" : "string" + } + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" } } } @@ -68,5 +101,5 @@ } } }, - "components": {} -} + "components" : { } +} \ No newline at end of file diff --git a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.1.0/app267.json b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.1.0/app267.json index 8fb710062..d2e71cd66 100644 --- a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.1.0/app267.json +++ b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.1.0/app267.json @@ -9,6 +9,47 @@ "description" : "Generated server url" } ], "paths" : { + "/persons" : { + "get" : { + "tags" : [ "hello-controller" ], + "operationId" : "persons", + "parameters" : [ { + "name" : "middleNames", + "in" : "query", + "required" : true, + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "pattern" : "^[a-zA-Z]$" + } + } + }, { + "name" : "phoneNumbers", + "in" : "query", + "required" : true, + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "pattern" : "^\\d+$" + } + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, "/items" : { "get" : { "tags" : [ "hello-controller" ], @@ -61,4 +102,4 @@ } }, "components" : { } -} +} \ No newline at end of file