AbstractApiValidator.java
- /*
- * GovWay - A customizable API Gateway
- * https://govway.org
- *
- * Copyright (c) 2005-2025 Link.it srl (https://link.it).
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3, as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- package org.openspcoop2.utils.rest;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.List;
- import org.openspcoop2.utils.UtilsMultiException;
- import org.openspcoop2.utils.rest.api.Api;
- import org.openspcoop2.utils.rest.api.ApiBodyParameter;
- import org.openspcoop2.utils.rest.api.ApiCookieParameter;
- import org.openspcoop2.utils.rest.api.ApiHeaderParameter;
- import org.openspcoop2.utils.rest.api.ApiOperation;
- import org.openspcoop2.utils.rest.api.ApiParameterSchema;
- import org.openspcoop2.utils.rest.api.ApiParameterSchemaComplexType;
- import org.openspcoop2.utils.rest.api.ApiParameterTypeSchema;
- import org.openspcoop2.utils.rest.api.ApiRequestDynamicPathParameter;
- import org.openspcoop2.utils.rest.api.ApiRequestFormParameter;
- import org.openspcoop2.utils.rest.api.ApiRequestQueryParameter;
- import org.openspcoop2.utils.rest.api.ApiResponse;
- import org.openspcoop2.utils.rest.api.ApiSchemaTypeRestriction;
- import org.openspcoop2.utils.rest.api.ApiUtilities;
- import org.openspcoop2.utils.rest.entity.Cookie;
- import org.openspcoop2.utils.rest.entity.HttpBaseEntity;
- import org.openspcoop2.utils.rest.entity.HttpBaseRequestEntity;
- import org.openspcoop2.utils.rest.entity.HttpBaseResponseEntity;
- import org.openspcoop2.utils.transport.TransportUtils;
- import org.openspcoop2.utils.transport.http.ContentTypeUtilities;
- import org.springframework.web.util.UriUtils;
- /**
- * ApiValidatorConfig
- *
- *
- * @author Poli Andrea (apoli@link.it)
- * @author $Author$
- * @version $Rev$, $Date$
- */
- public abstract class AbstractApiValidator {
- public abstract void validatePreConformanceCheck(HttpBaseEntity<?> httpEntity,ApiOperation operation,Object ... args) throws ProcessingException,ValidatorException;
-
- public abstract void validatePostConformanceCheck(HttpBaseEntity<?> httpEntity,ApiOperation operation,Object ... args) throws ProcessingException,ValidatorException;
-
- public abstract void validateValueAsType(ApiParameterType parameterType, String value, String type, ApiSchemaTypeRestriction typeRestriction) throws ProcessingException,ValidatorException;
-
- public void validate(Api api, HttpBaseEntity<?> httpEntity, Object ... args) throws ProcessingException,ValidatorException{
-
- ApiOperation operation = api.findOperation(httpEntity.getMethod(), httpEntity.getUrl());
- if(operation==null){
- throw new ProcessingException("Resource "+httpEntity.getMethod()+" '"+httpEntity.getUrl()+"' not found");
- }
- // es. xsd
- validatePreConformanceCheck(httpEntity, operation, args);
-
- validateConformanceCheck(httpEntity, operation, api.getBaseURL());
-
- // es. elementi specifici come nomi nel xsd etc..
- validatePostConformanceCheck(httpEntity, operation, args);
-
- }
-
- private void validateConformanceCheck(HttpBaseEntity<?> httpEntity,ApiOperation operation, URL baseUrl) throws ProcessingException,ValidatorException{
- try{
-
-
-
- if(httpEntity.getContentType() != null) {
-
- String baseTypeHttp = ContentTypeUtilities.readBaseTypeFromContentType(httpEntity.getContentType());
-
- boolean contentTypeSupported = false;
- List<ApiBodyParameter> requestBodyParametersList = null;
- List<ApiResponse> responses = null;
- if(operation.getRequest()!=null && operation.getRequest().sizeBodyParameters()>0){
- requestBodyParametersList = operation.getRequest().getBodyParameters();
- }
- if(operation.sizeResponses()>0){
- responses = operation.getResponses();
- }
- int status = -1;
-
- if(httpEntity instanceof HttpBaseRequestEntity<?>) {
-
- if(requestBodyParametersList != null) {
- for(ApiBodyParameter input: requestBodyParametersList) {
- if(input.isAllMediaType() || ContentTypeUtilities.isMatch(null, baseTypeHttp, input.getMediaType())) {
- contentTypeSupported = true;
- break;
- }
- }
- }
- } else if(httpEntity instanceof HttpBaseResponseEntity<?>) {
- status = ((HttpBaseResponseEntity<?>)httpEntity).getStatus();
-
- if(responses != null) {
-
- // Fix: se si traccia del codice http esatto non devo andare a verificare il codice di default
-
- // prima faccio la verifica con codice esatto
- ApiResponse outputDefault = null;
- boolean findExactResponseCode = false;
- for(ApiResponse output: responses) {
- if(status==output.getHttpReturnCode()) {
- findExactResponseCode = true;
- if(output.sizeBodyParameters()>0) {
- for(ApiBodyParameter outputBodyParameter: output.getBodyParameters()) {
- if(outputBodyParameter.isAllMediaType() || ContentTypeUtilities.isMatch(null, baseTypeHttp, outputBodyParameter.getMediaType()) ) {
- contentTypeSupported = true;
- break;
- }
- }
- }
- }
- else if(output.isDefaultHttpReturnCode()) {
- outputDefault = output;
- }
- }
-
- // poi con l'eventuale default
- if(!contentTypeSupported && !findExactResponseCode && outputDefault!=null) {
- if(outputDefault.sizeBodyParameters()>0) {
- for(ApiBodyParameter outputBodyParameter: outputDefault.getBodyParameters()) {
- if(outputBodyParameter.isAllMediaType() || ContentTypeUtilities.isMatch(null, baseTypeHttp, outputBodyParameter.getMediaType()) ) {
- contentTypeSupported = true;
- break;
- }
- }
- }
- }
- }
-
- }
-
- if(!contentTypeSupported) {
- if(status>0)
- throw new ValidatorException("Content-Type '"+baseTypeHttp+"' (http response status '"+status+"') unsupported");
- else
- throw new ValidatorException("Content-Type '"+baseTypeHttp+"' unsupported");
- }
- }
- else {
-
- // senza content-type
-
- if(httpEntity instanceof HttpBaseRequestEntity<?>) {
-
- if(operation.getRequest()!=null && operation.getRequest().sizeBodyParameters()>0){
-
- boolean required = false;
- for(ApiBodyParameter input: operation.getRequest().getBodyParameters()) {
- if(input.isRequired()) {
- required = true;
- }
- }
- if(required) {
- throw new ValidatorException("Request without payload (Content-Type 'null') unsupported");
- }
-
- }
-
- }
-
-
- }
-
- if(httpEntity instanceof HttpBaseRequestEntity<?>) {
-
- HttpBaseRequestEntity<?> request = (HttpBaseRequestEntity<?>) httpEntity;
-
- if(operation.getRequest()!=null && operation.getRequest().sizeHeaderParameters()>0){
- for (ApiHeaderParameter paramHeader : operation.getRequest().getHeaderParameters()) {
- String name = paramHeader.getName();
- List<String> values = TransportUtils.getRawObject(request.getHeaders(), name);
- if(values==null || values.isEmpty()){
- if(paramHeader.isRequired()){
- throw new ValidatorException("Required http header '"+name+"' not found");
- }
- }
- else {
- for (String value : values) {
- if(value!=null){
- validate(paramHeader.getApiParameterSchema(), ApiParameterType.header, name, value, "http header");
- }
- }
- }
- }
- }
-
- if(operation.getRequest()!=null && operation.getRequest().sizeCookieParameters()>0){
- for (ApiCookieParameter paramCookie : operation.getRequest().getCookieParameters()) {
- String name = paramCookie.getName();
- String value = null;
- if(request.getCookies()!=null){
- for (Cookie cookie : request.getCookies()) {
- if(name.equalsIgnoreCase(cookie.getName())){
- value = cookie.getValue();
- }
- }
- }
- if(value==null){
- if(paramCookie.isRequired()){
- throw new ValidatorException("Required Cookie '"+name+"' not found");
- }
- }
- if(value!=null){
- validate(paramCookie.getApiParameterSchema(), ApiParameterType.cookie, name, value, "cookie");
- }
- }
- }
-
- if(operation.getRequest()!=null && operation.getRequest().sizeQueryParameters()>0){
- for (ApiRequestQueryParameter paramQuery : operation.getRequest().getQueryParameters()) {
-
- // i parametri sono esplosi. La validazione viene fatta con json o openapi
- boolean parametriEsplosi = false;
- if(paramQuery.getApiParameterSchema()!=null && paramQuery.getApiParameterSchema().getSchemas()!=null && !paramQuery.getApiParameterSchema().getSchemas().isEmpty()) {
- for (ApiParameterTypeSchema schema : paramQuery.getApiParameterSchema().getSchemas()) {
- // Il controllo basta farlo sul primo.
- if(schema.getSchema()!=null && schema.getSchema().isTypeObject()) {
- if (
- (schema.getSchema().isStyleQueryForm() && schema.getSchema().isExplodeEnabled())
- ||
- (schema.getSchema().isStyleQueryDeepObject())
- ) {
- parametriEsplosi = true;
- break;
- }
- }
- }
- }
- if(parametriEsplosi) {
- continue;
- }
-
- String name = paramQuery.getName();
- List<String> values = TransportUtils.getRawObject(request.getParameters(), name);
- if(values==null || values.isEmpty()){
- if(paramQuery.isRequired()){
- throw new ValidatorException("Required query parameter '"+name+"' not found");
- }
- }
- else {
- for (String value : values) {
- if(value!=null){
- validate(paramQuery.getApiParameterSchema(), ApiParameterType.query, name, value, "query parameter");
- }
- }
- }
- }
- }
-
- if(operation.getRequest()!=null && operation.getRequest().sizeDynamicPathParameters()>0){
- for (ApiRequestDynamicPathParameter paramDynamicPath : operation.getRequest().getDynamicPathParameters()) {
- boolean find = false;
- String valueFound = null;
- if(operation.isDynamicPath()){
- for (int i = 0; i < operation.sizePath(); i++) {
- if(operation.isDynamicPath()){
- String idDinamic = operation.getDynamicPathId(i);
- if(paramDynamicPath.getName().equals(idDinamic)){
- String [] urlList = ApiUtilities.extractUrlList(baseUrl, request.getUrl());
- if(i>=urlList.length){
- throw new ValidatorException("Dynamic path '"+paramDynamicPath.getName()+"' not found (position:"+i+", urlLenght:"+urlList.length+")");
- }
- find = true;
- valueFound = urlList[i];
- break;
- }
- }
- }
- }
- if(!find && paramDynamicPath.isRequired()){
- throw new ValidatorException("Required dynamic path '"+paramDynamicPath.getName()+"' not found");
- }
- if(find){
- String valueUrlDecoded = valueFound;
- // il valore può essere url encoded
- try {
- // Note that Java’s URLEncoder class encodes space character(" ") into a + sign.
- // This is contrary to other languages like Javascript that encode space character into %20.
- /*
- * URLEncoder is not for encoding URLs, but for encoding parameter names and values for use in GET-style URLs or POST forms.
- * That is, for transforming plain text into the application/x-www-form-urlencoded MIME format as described in the HTML specification.
- **/
- //valueUrlDecoded = java.net.URLDecoder.decode(valueFound, org.openspcoop2.utils.resources.Charset.UTF_8.getValue());
-
- valueUrlDecoded = UriUtils.decode(valueFound, org.openspcoop2.utils.resources.Charset.UTF_8.getValue());
-
- //System.out.println("DOPO '"+valueUrlDecoded+"' PRIMA '"+valueFound+"'");
-
- }catch(Throwable e) {
- //System.out.println("ERRORE");
- //e.printStackTrace(System.out);
- // utilizzo valore originale
- //throw new RuntimeException(e.getMessage(),e);
- }
- validate(paramDynamicPath.getApiParameterSchema(), ApiParameterType.path, paramDynamicPath.getName(), valueUrlDecoded, "dynamic path",
- (valueUrlDecoded!=null && !valueUrlDecoded.equals(valueFound)) ? valueFound : null);
- }
- }
- }
-
- if(operation.getRequest()!=null && operation.getRequest().sizeFormParameters()>0){
- for (ApiRequestFormParameter paramForm : operation.getRequest().getFormParameters()) {
- String name = paramForm.getName();
- List<String> values = TransportUtils.getRawObject(request.getParameters(), name);
- if(values==null || values.isEmpty()){
- if(paramForm.isRequired()){
- throw new ValidatorException("Required form parameter '"+name+"' not found");
- }
- }
- else {
- for (String value : values) {
- if(value!=null){
- validate(paramForm.getApiParameterSchema(), ApiParameterType.form, name, value, "form parameter");
- }
- }
- }
- }
- }
-
- }
-
- if(httpEntity instanceof HttpBaseResponseEntity<?>) {
-
- HttpBaseResponseEntity<?> response = (HttpBaseResponseEntity<?>) httpEntity;
- ApiResponse apiResponseFound = null;
- ApiResponse apiResponseDefault = null;
-
- for (ApiResponse apiResponse : operation.getResponses()) {
- if(apiResponse.isDefaultHttpReturnCode()) {
- apiResponseDefault = apiResponse;
- }
- if(response.getStatus() == apiResponse.getHttpReturnCode()){
- apiResponseFound = apiResponse;
- break;
- }
- }
-
- if(apiResponseFound==null && apiResponseDefault!=null) {
- apiResponseFound = apiResponseDefault;
- }
- if(apiResponseFound==null){
- throw new ValidatorException("Http status code '"+response.getStatus()+"' unsupported");
- }
-
- if(apiResponseFound.sizeHeaderParameters()>0){
- for (ApiHeaderParameter paramHeader : apiResponseFound.getHeaderParameters()) {
- String name = paramHeader.getName();
- List<String> values = TransportUtils.getRawObject(response.getHeaders(), name);
- if(values==null || values.isEmpty()){
- if(paramHeader.isRequired()){
- throw new ValidatorException("Required http header '"+name+"' not found");
- }
- }
- else {
- for (String value : values) {
- if(value!=null){
- validate(paramHeader.getApiParameterSchema(), ApiParameterType.header, name, value, "http header");
- }
- }
- }
- }
- }
-
- if(apiResponseFound.sizeCookieParameters()>0){
- for (ApiCookieParameter paramCookie : apiResponseFound.getCookieParameters()) {
- String name = paramCookie.getName();
- String value = null;
- if(response.getCookies()!=null){
- for (Cookie cookie : response.getCookies()) {
- if(name.equalsIgnoreCase(cookie.getName())){
- value = cookie.getValue();
- }
- }
- }
- if(value==null){
- if(paramCookie.isRequired()){
- throw new ValidatorException("Required cookie '"+name+"' not found");
- }
- }
- if(value!=null){
- validate(paramCookie.getApiParameterSchema(), ApiParameterType.cookie, name, value, "cookie");
- }
- }
- }
-
- }
- }catch(ProcessingException e){
- throw e;
- }catch(ValidatorException e){
- throw e;
- }catch(Exception e){
- throw new ProcessingException(e.getMessage(),e);
- }
-
- }
-
-
- private void validate(ApiParameterSchema apiParameterSchema, ApiParameterType type, String name, String value, String position) throws ValidatorException, ProcessingException {
- this.validate(apiParameterSchema, type, name, value, position, null);
- }
- private void validate(ApiParameterSchema apiParameterSchema, ApiParameterType type, String name, String value, String position, String valueUrlEncoded) throws ValidatorException, ProcessingException {
- if(apiParameterSchema!=null && apiParameterSchema.getSchemas()!=null && !apiParameterSchema.getSchemas().isEmpty()) {
- ApiParameterSchemaComplexType ct = apiParameterSchema.getComplexType();
- if(ct==null) {
- ct = ApiParameterSchemaComplexType.simple;
- }
-
- String prefixError = "Invalid value '"+value+"' in "+position+" '"+name+"'";
- if(valueUrlEncoded!=null) {
- prefixError = "Invalid value '"+value+"' (urlEncoded: '"+valueUrlEncoded+"') in "+position+" '"+name+"'";
- }
-
- switch (ct) {
- case simple: // caso speciale con 1 solo
- case allOf:
- StringBuilder sbException = new StringBuilder();
- List<ValidatorException> listValidatorException = new ArrayList<ValidatorException>();
- for (ApiParameterTypeSchema schema : apiParameterSchema.getSchemas()) {
- try{
- validateValueAsType(type, value, schema.getType(),schema.getSchema());
- }catch(ValidatorException val){
- String msgError = prefixError+" (expected type '"+schema.getType()+"'): "+val.getMessage();
- if(sbException.length()>0) {
- sbException.append("\n");
- }
- sbException.append(msgError);
- listValidatorException.add( new ValidatorException(msgError,val) );
- }
- }
- if(!listValidatorException.isEmpty()) {
- UtilsMultiException multi = new UtilsMultiException(listValidatorException.toArray(new ValidatorException[1]));
- throw new ValidatorException(sbException.toString(), multi);
- }
- return;
-
- case anyOf:
- sbException = new StringBuilder();
- listValidatorException = new ArrayList<ValidatorException>();
- for (ApiParameterTypeSchema schema : apiParameterSchema.getSchemas()) {
- try{
- validateValueAsType(type, value, schema.getType(),schema.getSchema());
- return; // ne basta una che valida correttamente
- }catch(ValidatorException val){
- String msgError = prefixError+" (expected type '"+schema.getType()+"'): "+val.getMessage();
- if(sbException.length()>0) {
- sbException.append("\n");
- }
- sbException.append(msgError);
- listValidatorException.add( new ValidatorException(msgError,val) );
- }
- }
- if(!listValidatorException.isEmpty()) {
- UtilsMultiException multi = new UtilsMultiException(listValidatorException.toArray(new ValidatorException[1]));
- throw new ValidatorException(sbException.toString(), multi);
- }
- return; //caso che non può accadere
-
- case oneOf:
- sbException = new StringBuilder();
- listValidatorException = new ArrayList<ValidatorException>();
- int schemiValidi = 0;
- for (ApiParameterTypeSchema schema : apiParameterSchema.getSchemas()) {
- try{
- validateValueAsType(type, value, schema.getType(),schema.getSchema());
- schemiValidi++;
- }catch(ValidatorException val){
- String msgError = prefixError+" (expected type '"+schema.getType()+"'): "+val.getMessage();
- if(sbException.length()>0) {
- sbException.append("\n");
- }
- sbException.append(msgError);
- listValidatorException.add( new ValidatorException(msgError,val) );
- }
- }
- if(schemiValidi==0) {
- if(!listValidatorException.isEmpty()) {
- UtilsMultiException multi = new UtilsMultiException(listValidatorException.toArray(new ValidatorException[1]));
- throw new ValidatorException(sbException.toString(), multi);
- }
- else {
- throw new ValidatorException(prefixError);
- }
- }
- else if(schemiValidi>1) {
- throw new ValidatorException(prefixError+": expected validates the value against exactly one of the subschemas; founded valid in "+schemiValidi+" schemas");
- }
- return; // deve essere validato rispetto esattamente ad uno schema
- }
- }
-
- }
- }