SwaggerRequestValidator.java

  1. /*
  2.  * GovWay - A customizable API Gateway
  3.  * https://govway.org
  4.  *
  5.  * Copyright (c) 2005-2025 Link.it srl (https://link.it).
  6.  *
  7.  * This program is free software: you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License version 3, as published by
  9.  * the Free Software Foundation.
  10.  *
  11.  * This program is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18.  *
  19.  */

  20. package org.openspcoop2.utils.openapi.validator.swagger;

  21. import static com.atlassian.oai.validator.util.ContentTypeUtils.findMostSpecificMatch;

  22. import java.util.Arrays;
  23. import java.util.Optional;
  24. import java.util.stream.Collectors;

  25. import org.apache.commons.lang3.tuple.Pair;
  26. import org.openspcoop2.utils.openapi.validator.OpenapiLibraryValidatorConfig;

  27. import com.atlassian.oai.validator.interaction.request.RequestValidator;
  28. import com.atlassian.oai.validator.model.ApiOperation;
  29. import com.atlassian.oai.validator.model.Body;
  30. import com.atlassian.oai.validator.model.Request;
  31. import com.atlassian.oai.validator.report.LevelResolver;
  32. import com.atlassian.oai.validator.report.MessageResolver;
  33. import com.atlassian.oai.validator.report.ValidationReport;
  34. import com.atlassian.oai.validator.report.ValidationReport.Level;
  35. import com.atlassian.oai.validator.schema.SchemaValidator;
  36. import com.atlassian.oai.validator.schema.transform.AdditionalPropertiesInjectionTransformer;
  37. import com.atlassian.oai.validator.util.ContentTypeUtils;

  38. import io.swagger.v3.oas.models.OpenAPI;
  39. import io.swagger.v3.oas.models.media.Content;
  40. import io.swagger.v3.oas.models.media.MediaType;
  41. import io.swagger.v3.oas.models.parameters.RequestBody;

  42. /**
  43.  * SwaggerRequestValidator
  44.  *
  45.  * @author $Author$
  46.  * @version $Rev$, $Date$
  47.  *
  48.  */
  49. public class SwaggerRequestValidator {

  50.     private final MessageResolver normalValidatorMessages;
  51.     private final SchemaValidator normalSchemaValidator;
  52.     private final RequestValidator normalValidator;
  53.    
  54.     private final MessageResolver fileValidatorMessages;
  55.     private final SchemaValidator fileSchemaValidator;
  56.     private final RequestValidator fileValidator;
  57.    
  58.     private final boolean validateWildcardSubtypeAsJson;
  59.    
  60.     public SwaggerRequestValidator(OpenAPI openApi, OpenapiLibraryValidatorConfig config) {
  61.    
  62.         this.validateWildcardSubtypeAsJson = config.isValidateWildcardSubtypeAsJson();
  63.        
  64.         var errorLevelResolverBuilder = SwaggerRequestValidator.getLevelResolverBuilder(config);
  65.         this.normalValidatorMessages = new MessageResolver(errorLevelResolverBuilder.build());  
  66.         this.normalSchemaValidator = new SchemaValidator(openApi, this.normalValidatorMessages);        
  67.         if (!config.isSwaggerRequestValidator_InjectingAdditionalPropertiesFalse()) {
  68.             var transformers = this.normalSchemaValidator.transformers;
  69.             this.normalSchemaValidator.transformers = transformers.stream()
  70.                     .filter( t -> t != AdditionalPropertiesInjectionTransformer.getInstance())
  71.                     .collect(Collectors.toList());
  72.         }
  73.        
  74.         this.normalValidator = new RequestValidator(this.normalSchemaValidator, this.normalValidatorMessages, openApi, Arrays.asList());
  75.        
  76.         // file validator, non entra nel merito del body.
  77.         errorLevelResolverBuilder.withLevel("validation.request.body", Level.IGNORE);
  78.         errorLevelResolverBuilder.withLevel("validation.request.body.missing", Level.ERROR);
  79.         this.fileValidatorMessages = new MessageResolver(errorLevelResolverBuilder.build());
  80.         this.fileSchemaValidator = new SchemaValidator(openApi, this.fileValidatorMessages);
  81.        
  82.         if (!config.isSwaggerRequestValidator_InjectingAdditionalPropertiesFalse()) {
  83.             var transformers = this.fileSchemaValidator.transformers;
  84.             this.fileSchemaValidator.transformers = transformers.stream()
  85.                     .filter( t -> t != AdditionalPropertiesInjectionTransformer.getInstance())
  86.                     .collect(Collectors.toList());
  87.         }
  88.         this.fileValidator = new RequestValidator(this.fileSchemaValidator, this.fileValidatorMessages, openApi, Arrays.asList());
  89.     }

  90.     public ValidationReport validateRequest(Request request, ApiOperation apiOperation) {

  91.         var requestBodySchema = apiOperation.getOperation().getRequestBody();      
  92.         if (requestBodySchema == null) {
  93.             return this.normalValidator.validateRequest(request, apiOperation);
  94.         }
  95.        
  96.         //  VALIDAZIONE CUSTOM 1:
  97.         //  Controllo che il content-type nullo non sia ammesso quando è richiesto un content
  98.         //  Gli altri casi vengono controllati da atlassian
  99.         //  Questo completa la richiesta che il content-type passato deve stare fra quelli elencati.
  100.        
  101.         Content contentSchema = requestBodySchema.getContent();
  102.         boolean isContentRequired = requestBodySchema.getRequired() == null ? false : requestBodySchema.getRequired();
  103.        
  104.         if (isContentRequired && contentSchema != null && !contentSchema.isEmpty()) {  
  105.             if (request.getContentType().isEmpty()) {
  106.                 return ValidationReport.singleton(
  107.                         this.normalValidatorMessages.create(
  108.                                 "validation.request.contentType.notAllowed",
  109.                                 "[REQUEST] Required Content-Type is missing"
  110.                         ));
  111.             }
  112.         }
  113.        
  114.         if (request.getRequestBody().isPresent() && request.getContentType().isEmpty()) {
  115.             return ValidationReport.singleton(
  116.                     this.normalValidatorMessages.create(
  117.                             "validation.request.contentType.notAllowed",
  118.                             "[REQUEST] Empty Content-Type not allowed if body is present"
  119.                     ));        
  120.         }

  121.         // VALIDAZIONE CUSTOM PER BODY 2
  122.         // Se lo schema del request body è: type: string, format: binary, ovvero un BinarySchema,
  123.         // allora al più valida che il body sia un json e valida tutto il resto della richiesta
  124.         // Se invece il format è base64 controlla che sia in base64
  125.         // Se il subtype è /* valida lo schema della richiesta
  126.        
  127.         var maybeMediaType = findApiMediaTypeForRequest(request, requestBodySchema);
  128.         if (maybeMediaType.isEmpty()) {
  129.             return this.normalValidator.validateRequest(request, apiOperation);
  130.         }

  131.         MediaType type = maybeMediaType.get().getRight();
  132.         com.google.common.net.MediaType requestMediaType = com.google.common.net.MediaType.parse(maybeMediaType.get().getLeft());
  133.         ValidationReport report = ValidationReport.empty();
  134.         Body requestBody = request.getRequestBody().orElse(null);
  135.        
  136.         // Validazione schema string format binary  
  137.         if (SwaggerValidatorUtils.isBinarySchemaFile(type.getSchema()) && requestBody != null) {
  138.             if (ContentTypeUtils.isJsonContentType(request)) {
  139.                 report = report.merge(SwaggerValidatorUtils.validateJsonFormat(requestBody,this.normalValidatorMessages,true))
  140.                             .merge(this.fileValidator.validateRequest(request, apiOperation));
  141.             }
  142.         }
  143.        
  144.         // validazione schema string format base64  
  145.         else if (SwaggerValidatorUtils.isBase64SchemaFile(type.getSchema()) && requestBody != null) {
  146.             report = report.merge(SwaggerValidatorUtils.validateBase64Body(requestBody,this.normalValidatorMessages,true))
  147.                     .merge(this.fileValidator.validateRequest(request, apiOperation));
  148.            
  149.         }
  150.        
  151.         // validazione schema subtype *
  152.         else if (this.validateWildcardSubtypeAsJson && requestMediaType.subtype().equals("*")) {
  153.             report = this.normalSchemaValidator
  154.                     .validate( () -> request.getRequestBody().get().toJsonNode(), type.getSchema(), "request.body")
  155.                     .merge(this.normalValidator.validateRequest(request, apiOperation));
  156.         }
  157.        
  158.         else {
  159.             report = this.normalValidator.validateRequest(request, apiOperation);
  160.         }
  161.        
  162.         return report;
  163.     }
  164.    
  165.     private static LevelResolver.Builder getLevelResolverBuilder(OpenapiLibraryValidatorConfig config) {
  166.         var errorLevelResolver = LevelResolver.create();
  167.        
  168.         // Il LevelResolver serve a gestire il livello di serietà dei messaggi                      
  169.         // Di default il LevelResolver porta segnala ogni errore di validazione come
  170.         // un ERROR, quindi dobbiamo disattivarli selettivamente.
  171.         // Le chiavi da usare per il LevelResolver sono nel progetto swagger-validator
  172.         // sotto src/main/resources/messages.properties
  173.        
  174.         // Config Request
  175.         if (!config.isValidateRequestPath()) {
  176.             errorLevelResolver.withLevel("validation.request.path.missing", Level.IGNORE);                          
  177.         }
  178.         if (!config.isValidateRequestQuery()) {
  179.             errorLevelResolver.withLevel("validation.request.parameter.query", Level.IGNORE);                          
  180.         }
  181.         if (!config.isValidateRequestUnexpectedQueryParam()) {
  182.             errorLevelResolver.withLevel("validation.request.parameter.query.unexpected", Level.IGNORE);
  183.         }
  184.         if (!config.isValidateRequestHeaders()) {
  185.             errorLevelResolver.withLevel("validation.request.parameter.header", Level.IGNORE);
  186.         }
  187.         if (!config.isValidateRequestCookie()) {
  188.             errorLevelResolver.withLevel("validation.request.parameter.cookie", Level.IGNORE);              
  189.         }
  190.         if (!config.isValidateRequestBody()) {
  191.             errorLevelResolver.withLevel("validation.request.body", Level.IGNORE);
  192.         }

  193.         return errorLevelResolver;
  194.        
  195.     }
  196.    
  197.    
  198.     private Optional<Pair<String, MediaType>> findApiMediaTypeForRequest(Request request, RequestBody apiRequestBodyDefinition) {
  199.         return Optional.ofNullable(apiRequestBodyDefinition).map(RequestBody::getContent)
  200.                 .flatMap(content -> findMostSpecificMatch(request, content.keySet())
  201.                         .map(mostSpecificMatch -> Pair.of(mostSpecificMatch, content.get(mostSpecificMatch))));
  202.     }

  203. }