SwaggerResponseValidator.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.openspcoop2.utils.openapi.validator.OpenapiLibraryValidatorConfig;

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

  37. import io.swagger.v3.oas.models.OpenAPI;
  38. import io.swagger.v3.oas.models.media.Content;
  39. import io.swagger.v3.oas.models.media.MediaType;
  40. import io.swagger.v3.oas.models.responses.ApiResponse;

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

  92.    
  93.     public ValidationReport validateResponse(Response response, ApiOperation apiOperation) {
  94.        
  95.         final ApiResponse responseSchema = getApiResponse(response, apiOperation);
  96.         if (responseSchema.getContent() == null) {
  97.             return this.normalValidator.validateResponse(response, apiOperation);
  98.         }
  99.        
  100.         //  VALIDAZIONE CUSTOM 1:
  101.         //  Controllo che il content-type nullo non sia ammesso quando è richiesto un content
  102.         //  Le risposte vuote sono quelle senza content, se il content c'è, ci deve essere
  103.         //  anche il mediaType
  104.        
  105.         Content contentSchema = responseSchema.getContent();
  106.         if (contentSchema != null && !contentSchema.isEmpty()) {
  107.             if (response.getContentType().isEmpty()) {
  108.                 return ValidationReport.singleton(
  109.                         this.normalValidatorMessages.create(
  110.                                 "validation.response.contentType.notAllowed",
  111.                                 "[RESPONSE] Required Content-Type is missing"
  112.                         ));
  113.             }
  114.         }
  115.        
  116.         if (response.getResponseBody().isPresent() && response.getContentType().isEmpty()) {
  117.             return ValidationReport.singleton(
  118.                     this.normalValidatorMessages.create(
  119.                             "validation.response.contentType.notAllowed",
  120.                             "[RESPONSE] Empty Content-Type not allowed if body is present"
  121.                     ));        
  122.         }
  123.        
  124.         // VALIDAZIONE CUSTOM 2
  125.         // Se lo schema del response body è: type: string, format: binary, ovvero un BinarySchema,
  126.         // allora al più valida che il body sia un json e valida tutto il resto della richiesta
  127.         // Se invece il format è base64 controlla che sia in base64
  128.        
  129.         final Optional<String> mostSpecificMatch = findMostSpecificMatch(response, responseSchema.getContent().keySet());      
  130.         if (!mostSpecificMatch.isPresent()) {
  131.             // Se non matcho il content-type, lascio fare al normal validator,
  132.             return this.normalValidator.validateResponse(response, apiOperation);
  133.         }
  134.        
  135.         final MediaType mediaType = responseSchema.getContent().get(mostSpecificMatch.get());
  136.         com.google.common.net.MediaType responseMediaType = com.google.common.net.MediaType.parse(mostSpecificMatch.get());
  137.         final Body responseBody = response.getResponseBody().orElse(null);
  138.         ValidationReport report = ValidationReport.empty();
  139.        
  140.         // Validazione schema string format binary  
  141.         if (SwaggerValidatorUtils.isBinarySchemaFile(mediaType.getSchema()) && responseBody != null) {
  142.             if (ContentTypeUtils.isJsonContentType(response)) {
  143.                 report = report.merge(SwaggerValidatorUtils.validateJsonFormat(responseBody,this.normalValidatorMessages,false))
  144.                             .merge(this.fileValidator.validateResponse(response, apiOperation));
  145.             }      
  146.            
  147.         }
  148.        
  149.         // validazione schema string format base64  
  150.         else if (SwaggerValidatorUtils.isBase64SchemaFile(mediaType.getSchema()) && responseBody != null) {
  151.             report = report.merge(SwaggerValidatorUtils.validateBase64Body(responseBody,this.normalValidatorMessages,false))
  152.                     .merge(this.fileValidator.validateResponse(response, apiOperation));
  153.            
  154.         }
  155.        
  156.         // validazione schema subtype *
  157.         else if (this.validateWildcardSubtypeAsJson && responseMediaType.subtype().equals("*")) {
  158.             report = this.normalSchemaValidator
  159.                     .validate( () -> response.getResponseBody().get().toJsonNode(), mediaType.getSchema(), "response.body")
  160.                     .merge(this.normalValidator.validateResponse(response, apiOperation));
  161.            
  162.         }
  163.        
  164.         else {
  165.             report = this.normalValidator.validateResponse(response, apiOperation);
  166.         }
  167.        
  168.         return report;
  169.    
  170.     }
  171.    
  172.     private static LevelResolver.Builder getLevelResolverBuilder(OpenapiLibraryValidatorConfig config) {
  173.         var errorLevelResolver = LevelResolver.create();
  174.        
  175.         // Il LevelResolver serve a gestire il livello di serietà dei messaggi                      
  176.         // Di default il LevelResolver porta segnala ogni errore di validazione come
  177.         // un ERROR, quindi dobbiamo disattivarli selettivamente.
  178.         // Le chiavi da usare per il LevelResolver sono nel progetto swagger-validator
  179.         // sotto src/main/resources/messages.properties
  180.        
  181.         // Config Response
  182.         if(!config.isValidateResponseHeaders()) {
  183.             errorLevelResolver.withLevel("validation.response.parameter.header", Level.IGNORE);
  184.         }
  185.         if(!config.isValidateResponseBody()) {
  186.             errorLevelResolver.withLevel("validation.response.body", Level.IGNORE);
  187.         }
  188.        
  189.         return errorLevelResolver;
  190.        
  191.     }

  192.     private ApiResponse getApiResponse(final Response response, final ApiOperation apiOperation) {
  193.         final ApiResponse apiResponse = apiOperation.getOperation().getResponses()
  194.                 .get(Integer.toString(response.getStatus()));
  195.         if (apiResponse == null) {
  196.             return apiOperation.getOperation().getResponses().get("default"); // try the default response
  197.         }
  198.         return apiResponse;
  199.     }

  200. }