SUAPResponseGenerator.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.pdd.core.handlers.suap;

import java.util.HashMap;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.core.constants.Costanti;
import org.openspcoop2.message.ForcedResponseMessage;
import org.openspcoop2.message.OpenSPCoop2Message;
import org.openspcoop2.message.OpenSPCoop2RestJsonMessage;
import org.openspcoop2.message.OpenSPCoop2RestMessage;
import org.openspcoop2.message.constants.MessageRole;
import org.openspcoop2.message.constants.MessageType;
import org.openspcoop2.message.exception.MessageException;
import org.openspcoop2.pdd.core.PdDContext;
import org.openspcoop2.pdd.core.handlers.HandlerException;
import org.openspcoop2.pdd.core.handlers.OutResponseContext;
import org.openspcoop2.pdd.core.handlers.OutResponseHandler;
import org.openspcoop2.pdd.core.transazioni.Transaction;
import org.openspcoop2.pdd.core.transazioni.TransactionContext;
import org.openspcoop2.pdd.core.transazioni.TransactionNotExistsException;
import org.openspcoop2.pdd.logger.LogLevels;
import org.openspcoop2.protocol.sdk.Eccezione;
import org.openspcoop2.protocol.sdk.constants.CodiceErroreCooperazione;
import org.openspcoop2.protocol.sdk.diagnostica.MsgDiagnostico;
import org.openspcoop2.utils.Map;
import org.openspcoop2.utils.MapKey;
import org.openspcoop2.utils.regexp.RegularExpressionEngine;
import org.openspcoop2.utils.transport.TransportResponseContext;
import org.openspcoop2.utils.transport.TransportUtils;
import org.openspcoop2.utils.transport.http.ContentTypeUtilities;
import org.openspcoop2.utils.transport.http.HttpConstants;

/**
 * SUAPResponseGenerator
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class SUAPResponseGenerator implements OutResponseHandler {

	private static final String ERROR_400_001_CODE = "ERROR_400_001";
	private static final String ERROR_400_001_MESSAGE = "incorrect request input";
	
	private static final String ERROR_401_001_CODE = "ERROR_401_001";
	private static final String ERROR_401_001_MESSAGE = "PDND token not found";
	
	private static final String ERROR_401_002_CODE = "ERROR_401_002";
	private static final String ERROR_401_002_MESSAGE = "Invalid PDND token";
	
	private static final String ERROR_401_003 = "ERROR_401_003";
	private static final String ERROR_401_003_MESSAGE = "AgID-JWT-Signature token not found";
	
	private static final String ERROR_401_004_CODE = "ERROR_401_004";
	private static final String ERROR_401_004_MESSAGE = "invalid AgID-JWT-Signature token";
	
	private static final String ERROR_404_001_CODE = "ERROR_404_001";
	private static final String ERROR_404_001_MESSAGE = "resource not found";
	
	private static final String ERROR_428_001_CODE = "ERROR_428_001";
	private static final String ERROR_428_001_MESSAGE = "hash not found";
	private static final String ERROR_428_001_MESSAGE_PATTERN_MATCH = ".*Parameter 'If-Match' is required.*";
	
	private static final String ERROR_500_007 = "ERROR_500_007";
	private static final String ERROR_500_007_MESSAGE = "response processing error";
	
	@Override
	public void invoke(OutResponseContext context) throws HandlerException {
		if(context!=null && context.getMessaggio()!=null 
				&& MessageType.JSON.equals(context.getMessaggio().getMessageType()) 
				&& MessageRole.FAULT.equals(context.getMessaggio().getMessageRole())) {
			try {
				
				String idTransazione = null;
				if(context.getPddContext()!=null){
					PdDContext pddContext = context.getPddContext();
					if (pddContext.getObject(Costanti.ID_TRANSAZIONE)==null)
						throw new HandlerException("Identificativo della transazione assente");
					idTransazione = (String) pddContext.getObject(Costanti.ID_TRANSAZIONE);
					if (idTransazione==null)
						throw new HandlerException("Identificativo della transazione assente");
				}
				
				if(isErroreValidazioneRichiesta(context)) {
					if(isErroreValidazioneRichiestaHashNotFound(idTransazione)) {
						modifyErrorMessage(428, ERROR_428_001_CODE, ERROR_428_001_MESSAGE, context.getMessaggio());
					}
					else {
						modifyErrorMessage(400, ERROR_400_001_CODE, ERROR_400_001_MESSAGE, context.getMessaggio());
					}
				}
				else if(isRequestReadTimeout(context) ||
						isContenutiRichiestaNonRiconosciuto(context) ||
						isErroreCorrelazioneApplicativaRisposta(context) ||
						isErroreTrasformazioneRichiesta(context)) {
					modifyErrorMessage(400, ERROR_400_001_CODE, ERROR_400_001_MESSAGE, context.getMessaggio());
				}
				else if(isErroreTokenNonPresente(context)) {
					if(isAuthorizationWithoutBearer(context)) {
						modifyErrorMessage(401, ERROR_401_002_CODE, ERROR_401_002_MESSAGE, context.getMessaggio());
					}
					else {
						modifyErrorMessage(401, ERROR_401_001_CODE, ERROR_401_001_MESSAGE, context.getMessaggio());
					}
				}
				else if(isErroreToken(context)) {
					modifyErrorMessage(401, ERROR_401_002_CODE, ERROR_401_002_MESSAGE, context.getMessaggio());
				}
				else if(isOperazioneNonIndividuata(context)) {
					modifyErrorMessage(404, ERROR_404_001_CODE, ERROR_404_001_MESSAGE, context.getMessaggio());
				}
				else if(isConnectorError(context) || 
						isConnectionTimeout(context) || 
						isReadTimeout(context) || isResponseReadTimeout(context) ||
						isContenutiRispostaNonRiconosciuto(context) || isErroreCorrelazioneApplicativaRisposta(context) ||
						isErroreSicurezzaMessaggioRisposta(context) || isErroreAllegatiRisposta(context) ||
						isErroreValidazioneRisposta(context) || isErroreTrasformazioneRisposta(context) ||
						isRispostaDuplicata(context)) {
					modifyErrorMessage(500, ERROR_500_007, ERROR_500_007_MESSAGE, context.getMessaggio());
				}
				else if(context.getPddContext()!=null){		
					Transaction transaction = getTransaction(idTransazione);
					boolean casoAgitJWTSignatureNonPresente = false;
					boolean casoAgitJWTSignatureNonValido = false;
					boolean casoVoucherPDNDNonValido = false;
					if(transaction!=null && transaction.getTracciaRisposta()!=null && transaction.getTracciaRisposta().getBusta()!=null &&
							transaction.getTracciaRisposta().getBusta().getListaEccezioni()!=null &&
							!transaction.getTracciaRisposta().getBusta().getListaEccezioni().isEmpty()) {
						for (Eccezione eccezione : transaction.getTracciaRisposta().getBusta().getListaEccezioni()) {
							if(eccezione.getCodiceEccezione()!=null) {
								if(eccezione.getCodiceEccezione().equals(CodiceErroreCooperazione.SICUREZZA_TOKEN_NON_PRESENTE.getCodice())) {
									String desc = eccezione.getDescrizione(context.getProtocolFactory());
									if(desc!=null && "Header HTTP 'Agid-JWT-Signature' non presente".equals(desc)) {
										casoAgitJWTSignatureNonPresente = true;
									}
								}
								else if(eccezione.getCodiceEccezione().equals(CodiceErroreCooperazione.SICUREZZA_FIRMA_NON_VALIDA.getCodice()) ||
										( CodiceErroreCooperazione.isEccezioneSicurezza(eccezione.getCodiceEccezione()) && !CodiceErroreCooperazione.isEccezioneSicurezzaToken(eccezione.getCodiceEccezione()) ) ||
										CodiceErroreCooperazione.isEccezioneServizioApplicativoErogatore(eccezione.getCodiceEccezione()) // caso audience non valido
										) {
									String desc = eccezione.getDescrizione(context.getProtocolFactory());
									if(desc!=null && desc.contains("Header 'Authorization'")) {
										casoVoucherPDNDNonValido = true;
									}
									else {
										casoAgitJWTSignatureNonValido = true;
									}
								}
								else if(CodiceErroreCooperazione.isEccezioneSicurezzaToken(eccezione.getCodiceEccezione())){ // caso audience non valido per voucher PDND
									casoVoucherPDNDNonValido = true;
								}
							}
						}
					}
					if(casoAgitJWTSignatureNonPresente) {
						modifyErrorMessage(401, ERROR_401_003, ERROR_401_003_MESSAGE, context.getMessaggio());
					}
					else if(casoAgitJWTSignatureNonValido) {
						modifyErrorMessage(401, ERROR_401_004_CODE, ERROR_401_004_MESSAGE, context.getMessaggio());
					}
					else if(casoVoucherPDNDNonValido) {
						modifyErrorMessage(401, ERROR_401_002_CODE, ERROR_401_002_MESSAGE, context.getMessaggio());
					}
				}
				
			}catch(Exception e) {
				context.getLogCore().error("Conversione non riuscita: "+e.getMessage(),e);
			}
		}
		else if(context!=null && context.getMessaggio()!=null) {
			try {					
				if(isRisposta5xxDifferenteJson(context)) {
					if(MessageType.JSON.equals(context.getMessaggio().getMessageType())) { 
						modifyErrorMessage(500, ERROR_500_007, ERROR_500_007_MESSAGE, context.getMessaggio());
					}
					else {
						replaceErrorMessage(500, ERROR_500_007, ERROR_500_007_MESSAGE, context);
					}
				}
			}catch(Exception e) {
				context.getLogCore().error("Conversione (risposta backend) non riuscita: "+e.getMessage(),e);
			}
		}
	}
	
	private static Transaction getTransaction(String idTransazione) {
		Transaction transaction = null;
		try {
			transaction = TransactionContext.getTransaction(idTransazione);
		}catch(TransactionNotExistsException n) {
			// ignore
		}
		return transaction;
	}
	
	static boolean isErroreValidazioneRichiestaHashNotFound(String idTransazione) {
		Transaction transaction = getTransaction(idTransazione);
		if(transaction!=null && transaction.getMsgDiagnostici()!=null && !transaction.getMsgDiagnostici().isEmpty()) {
			for (MsgDiagnostico msg : transaction.getMsgDiagnostici()) {
				if(isMessaggioErroreValidazioneRichiestaHashNotFound(msg)) {
					return true;
				}
			}
		}
		return false;
	}
	static boolean isMessaggioErroreValidazioneRichiestaHashNotFound(MsgDiagnostico msg) {
		if(msg.getSeverita()<=LogLevels.SEVERITA_ERROR_INTEGRATION && 
				msg.getMessaggio()!=null) {
			boolean match = false;
			try {
				match = RegularExpressionEngine.isFind(msg.getMessaggio(), ERROR_428_001_MESSAGE_PATTERN_MATCH);
			}catch(Exception e) {
				// ignore
			}
			if(match) {
				return true;
			}
		}
		return false;
	}
	
	static boolean isErroreValidazioneRichiesta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_VALIDAZIONE_RICHIESTA, context); 
	}
	static boolean isErroreValidazioneRisposta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_VALIDAZIONE_RISPOSTA, context); 
	}
	
	static boolean isErroreTokenNonPresente(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.TOKEN_NON_PRESENTE, context); 
	}
	static boolean isErroreToken(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_TOKEN, context); 
	}
	static boolean isOperazioneNonIndividuata(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.OPERAZIONE_NON_INDIVIDUATA, context); 
	}
	
	static boolean isReadTimeout(OutResponseContext context) {
		return isErroreString(org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_NAME_CONTROLLO_TRAFFICO_VIOLAZIONE, 
				org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_VALUE_READ_TIMEOUT, context); 
	}
	static boolean isRequestReadTimeout(OutResponseContext context) {
		return isErroreString(org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_NAME_CONTROLLO_TRAFFICO_VIOLAZIONE, 
				org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_VALUE_REQUEST_READ_TIMEOUT, context); 
	}
	static boolean isResponseReadTimeout(OutResponseContext context) {
		return isErroreString(org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_NAME_CONTROLLO_TRAFFICO_VIOLAZIONE, 
				org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_VALUE_RESPONSE_READ_TIMEOUT, context); 
	}
	static boolean isConnectionTimeout(OutResponseContext context) {
		return isErroreString(org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_NAME_CONTROLLO_TRAFFICO_VIOLAZIONE, 
				org.openspcoop2.core.controllo_traffico.constants.Costanti.PDD_CONTEXT_VALUE_CONNECTION_TIMEOUT, context); 
	}
	static boolean isConnectorError(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_UTILIZZO_CONNETTORE, context); 
	}
	
	static boolean isContenutiRichiestaNonRiconosciuto(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.CONTENUTO_RICHIESTA_NON_RICONOSCIUTO, context); 
	}
	static boolean isContenutiRispostaNonRiconosciuto(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.CONTENUTO_RISPOSTA_NON_RICONOSCIUTO, context); 
	}
	
	static boolean isErroreSicurezzaMessaggioRisposta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_SICUREZZA_MESSAGGIO_RISPOSTA, context); 
	}
	static boolean isErroreAllegatiRisposta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_ALLEGATI_MESSAGGIO_RISPOSTA, context); 
	}
	
	static boolean isErroreCorrelazioneApplicativaRichiesta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_CORRELAZIONE_APPLICATIVA_RICHIESTA, context); 
	}
	static boolean isErroreCorrelazioneApplicativaRisposta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_CORRELAZIONE_APPLICATIVA_RISPOSTA, context); 
	}
	
	static boolean isErroreTrasformazioneRichiesta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_TRASFORMAZIONE_RICHIESTA, context); 
	}
	static boolean isErroreTrasformazioneRisposta(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_TRASFORMAZIONE_RISPOSTA, context); 
	}
	
	static boolean isRispostaDuplicata(OutResponseContext context) {
		return isErroreBoolean(org.openspcoop2.core.constants.Costanti.RISPOSTA_DUPLICATA, context); 
	}
	
	static boolean isErroreBoolean(String key, OutResponseContext context) {
		MapKey<String> k = Map.newMapKey(key);
		return isErroreBoolean(k, context);
	}
	static boolean isErroreBoolean(MapKey<String> key, OutResponseContext context) {
		if(context.getPddContext()!=null && context.getPddContext().containsKey(key)) {
			Object o = context.getPddContext().get(key);
			if(o instanceof String) {
				String s = (String) o;
				return "true".equals(s);
			}
			else if(o instanceof Boolean) {
				return (Boolean) o;
			}
		}
		return false;
	}
	
	static boolean isErroreString(String key, String value, OutResponseContext context) {
		MapKey<String> k = Map.newMapKey(key);
		return isErroreString(k, value, context);
	}
	static boolean isErroreString(MapKey<String> key, String value, OutResponseContext context) {
		if(context.getPddContext()!=null && context.getPddContext().containsKey(key)) {
			Object o = context.getPddContext().get(key);
			if(o instanceof String) {
				String s = (String) o;
				return value.equals(s);
			}
		}
		return false;
	}

	static boolean isRisposta5xxDifferenteJson(OutResponseContext context) {
		if(context!=null && 
				context.getMessaggio()!=null && context.getMessaggio().getTransportResponseContext()!=null &&
						context.getMessaggio().getTransportResponseContext().getCodiceTrasporto()!=null
				) {
			int codiceHttp = -1;
			try {
				codiceHttp = Integer.valueOf(context.getMessaggio().getTransportResponseContext().getCodiceTrasporto());
			}catch(Exception e) {
				context.getLogCore().error("isInternalRisposta5xxDifferenteJson: "+e.getMessage(),e);
				// ignore
			}
			if(codiceHttp>=500 && codiceHttp<=599) {
				return isInternalRisposta5xxDifferenteJson(context);
			}
		}
		return false;
	}
	private static boolean isInternalRisposta5xxDifferenteJson(OutResponseContext context) {
		try {
			String ct = context.getMessaggio().getContentType();
			if(ct==null) {
				return true; // 5xx senza contentType deve essere mappato
			}
			if(context.getMessaggio() instanceof OpenSPCoop2RestMessage<?>) {
				OpenSPCoop2RestMessage<?> rest = context.getMessaggio().castAsRest();
				if(!rest.hasContent()) {
					return true; // 5xx senza contenuto
				}
			}
			String baseCT = null;
			if(ContentTypeUtilities.isMultipartType(ct)) {
				baseCT = ContentTypeUtilities.getInternalMultipartContentType(ct);
				if(baseCT!=null) {
					baseCT = ContentTypeUtilities.readBaseTypeFromContentType(baseCT);
				}
			}
			else {
				baseCT = ContentTypeUtilities.readBaseTypeFromContentType(ct);
			}
			if(!HttpConstants.CONTENT_TYPE_JSON.equalsIgnoreCase(baseCT)) {
				return true;
			}
		}catch(Exception e) {
			context.getLogCore().error("isInternalRisposta5xxDifferenteJson failed: "+e.getMessage(),e);
			// ignore
		}
		return false;
	}
	
	private static boolean isAuthorizationWithoutBearer(OutResponseContext context) {
		try {
			if(context.getPddContext()!=null && context.getPddContext().containsKey(Costanti.REQUEST_INFO)) {
				org.openspcoop2.protocol.sdk.state.RequestInfo requestInfo = (org.openspcoop2.protocol.sdk.state.RequestInfo) context.getPddContext().getObject(Costanti.REQUEST_INFO);
				if(requestInfo!=null && requestInfo.getProtocolContext()!=null){
					String v = requestInfo.getProtocolContext().getHeaderFirstValue(HttpConstants.AUTHORIZATION);
					if(v!=null && StringUtils.isNotEmpty(v)) {
						return true;
					}
				}
			}
			return false;
		}catch(Exception e) {
			context.getLogCore().error("isAuthorizationWithoutBearer failed: "+e.getMessage(),e);
			// ignore
		}
		return false;
	}
	
	static void modifyErrorMessage(int responseCode, String code, String message, OpenSPCoop2Message msg) throws MessageException {
		String error = "{\"code\": \""+code+"\", \"message\": \""+message+"\"}";
		OpenSPCoop2RestJsonMessage json = msg.castAsRestJson();
		ForcedResponseMessage frs = new ForcedResponseMessage();
		frs.setContent(error.getBytes());
		frs.setContentType(HttpConstants.CONTENT_TYPE_JSON);
		frs.setResponseCode(responseCode+"");
		json.forceResponse(frs);
	}
	static void replaceErrorMessage(int responseCode, String code, String message, OutResponseContext context) throws MessageException {
		try {
			String error = "{\"code\": \""+code+"\", \"message\": \""+message+"\"}";
			TransportResponseContext responseContext = new TransportResponseContext();
			responseContext.setCodiceTrasporto(responseCode+"");
			java.util.Map<String, List<String>> headers = new HashMap<>();
			TransportUtils.addHeader(headers, HttpConstants.CONTENT_TYPE, HttpConstants.CONTENT_TYPE_JSON);
			responseContext.setHeaders(headers);
			OpenSPCoop2Message errorMessage = context.getMessaggio().getFactory().createMessage(MessageType.JSON, responseContext, error.getBytes()).getMessage_throwParseException();
			context.setMessaggio(errorMessage);
		}catch(Exception e) {
			throw new MessageException(e.getMessage(),e);
		}
	}
}