FonteAutenticaV131ResponseGenerator.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2026 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.itwallet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openspcoop2.core.constants.Costanti;
import org.openspcoop2.message.ForcedResponseMessage;
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.ProtocolException;
import org.openspcoop2.protocol.sdk.constants.CodiceErroreCooperazione;
import org.openspcoop2.protocol.sdk.diagnostica.MsgDiagnostico;
import org.openspcoop2.utils.MapKey;
import org.openspcoop2.utils.regexp.RegularExpressionEngine;
import org.openspcoop2.utils.transport.http.ContentTypeUtilities;
import org.openspcoop2.utils.transport.http.HttpConstants;
/**
* FonteAutenticaV131ResponseGenerator
*
* @author Tommaso Burlon (tommaso.burlon@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class FonteAutenticaV131ResponseGenerator implements OutResponseHandler {
private enum FonteAutenticaError {
SERVER_ERROR(500),
INVALID_REQUEST(400),
INVALID_DPOP_PROOF(400),
NOT_FOUND(404),
TEMPORARY_UNAVAILABLE(503),
INVALID_TOKEN(401);
private Integer code;
private FonteAutenticaError(Integer code) {
this.code = code;
}
}
private static final String ERROR_INVALID_TOKEN = "Token not valid";
private static final String ERROR_NOT_FOUND_HASH = "Message hash not found";
private static final String ERROR_NOT_PRESENT_TOKEN = "Token not present";
private static final String ERROR_MALFORMED_TOKEN = "Token malformed";
private static final String ERROR_NOT_FOUND = "Resource not found";
private static final String ERROR_PROCESSING = "Processing error";
private static final String ERROR_BAD_REQUEST = "Invalid input";
private static final String ERROR_NOT_FOUND_AGID_JWT = "AgID-JWT-Signature token not found";
private static final String ERROR_MALFORMED_AGID_JWT = "AgID-JWT-Signature token malformed";
@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 {
parseError(context);
} catch (MessageException | ProtocolException e) {
context.getLogCore().error("Conversione non riuscita: "+e.getMessage(),e);
}
}
}
private String getTransactionId(OutResponseContext context) throws HandlerException {
String id = null;
if(context.getPddContext()!=null){
PdDContext pddContext = context.getPddContext();
if (pddContext.getObject(Costanti.ID_TRANSAZIONE)==null)
throw new HandlerException("Identificativo della transazione assente");
id = (String) pddContext.getObject(Costanti.ID_TRANSAZIONE);
}
if (id == null)
throw new HandlerException("Identificativo della transazione assente");
return id;
}
private Pair<FonteAutenticaError, String> parseValidazioneRichiesta(String idTransazione) {
if(isErroreValidazioneRichiestaHashNotFound(idTransazione)) {
return Pair.of(FonteAutenticaError.INVALID_REQUEST, ERROR_NOT_FOUND_HASH);
} else {
return Pair.of(FonteAutenticaError.INVALID_REQUEST, ERROR_BAD_REQUEST);
}
}
private Optional<Eccezione> getEccezioneWithCodiceErrore(Transaction transaction) {
return transaction.getTracciaRisposta().getBusta().getListaEccezioni().stream()
.filter(e -> e.getCodiceEccezione() != null)
.findAny();
}
private Pair<FonteAutenticaError, String> parsePddContext(OutResponseContext context, String idTransazione) throws ProtocolException {
Transaction transaction = getTransaction(idTransazione);
if(transaction==null
|| transaction.getTracciaRisposta() == null
|| transaction.getTracciaRisposta().getBusta() == null
|| transaction.getTracciaRisposta().getBusta().getListaEccezioni() == null
|| transaction.getTracciaRisposta().getBusta().getListaEccezioni().isEmpty())
return null;
Optional<Eccezione> eccezioneOpt = getEccezioneWithCodiceErrore(transaction);
if (eccezioneOpt.isEmpty())
return null;
Eccezione eccezione = eccezioneOpt.get();
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)) {
return Pair.of(FonteAutenticaError.INVALID_TOKEN, ERROR_NOT_FOUND_AGID_JWT); // casoAgitJWTSignatureNonPresente
}
} else if(eccezione.getCodiceEccezione().equals(CodiceErroreCooperazione.SICUREZZA_FIRMA_NON_VALIDA.getCodice())
|| CodiceErroreCooperazione.isEccezioneServizioApplicativoErogatore(eccezione.getCodiceEccezione()) // caso audience non valido
|| ( CodiceErroreCooperazione.isEccezioneSicurezza(eccezione.getCodiceEccezione())
&& !CodiceErroreCooperazione.isEccezioneSicurezzaToken(eccezione.getCodiceEccezione()))) {
String desc = eccezione.getDescrizione(context.getProtocolFactory());
if(desc!=null && desc.contains("Header 'Authorization'")) {
return Pair.of(FonteAutenticaError.INVALID_TOKEN, ERROR_INVALID_TOKEN); // casoVoucherPDNDNonValido
} else {
return Pair.of(FonteAutenticaError.INVALID_TOKEN, ERROR_MALFORMED_AGID_JWT); // casoAgitJWTSignatureNonValido
}
} else if(CodiceErroreCooperazione.isEccezioneSicurezzaToken(eccezione.getCodiceEccezione())){ // caso audience non valido per voucher PDND
return Pair.of(FonteAutenticaError.INVALID_TOKEN, ERROR_INVALID_TOKEN); // casoVoucherPDNDNonValido
}
return null;
}
private Pair<FonteAutenticaError, String> parseCode(OutResponseContext context) {
if (context.getMessaggio().getTransportResponseContext() != null) {
int code = NumberUtils.toInt(context.getMessaggio().getTransportResponseContext().getCodiceTrasporto(), 500);
if (code == 404) {
return Pair.of(FonteAutenticaError.NOT_FOUND, ERROR_NOT_FOUND);
} else if (code == 401 || code == 403) {
return Pair.of(FonteAutenticaError.INVALID_TOKEN, ERROR_INVALID_TOKEN);
} else if (code >= 400 && code < 500) {
return Pair.of(FonteAutenticaError.INVALID_REQUEST, ERROR_BAD_REQUEST);
}
}
return null;
}
private void parseError(OutResponseContext context) throws MessageException, HandlerException, ProtocolException {
Pair<FonteAutenticaError, String> error = Pair.of(FonteAutenticaError.TEMPORARY_UNAVAILABLE, "The API is temporary unavailable");
String idTransazione = getTransactionId(context);
if(isErroreValidazioneRichiesta(context)) {
error = parseValidazioneRichiesta(idTransazione);
}
else if(isRequestReadTimeout(context) ||
isContenutiRichiestaNonRiconosciuto(context) ||
isErroreCorrelazioneApplicativaRisposta(context) ||
isErroreTrasformazioneRichiesta(context)) {
error = Pair.of(FonteAutenticaError.INVALID_REQUEST, ERROR_BAD_REQUEST);
}
else if(isErroreTokenNonPresente(context)) {
error = Pair.of(FonteAutenticaError.INVALID_TOKEN, ERROR_NOT_PRESENT_TOKEN);
}
else if(isErroreToken(context)) {
error = Pair.of(FonteAutenticaError.INVALID_TOKEN, ERROR_MALFORMED_TOKEN);
}
else if(isOperazioneNonIndividuata(context)) {
error = Pair.of(FonteAutenticaError.NOT_FOUND, ERROR_NOT_FOUND);
}
else if(isConnectorError(context) ||
isConnectionTimeout(context) ||
isReadTimeout(context) || isResponseReadTimeout(context) ||
isContenutiRispostaNonRiconosciuto(context) || isErroreCorrelazioneApplicativaRisposta(context) ||
isErroreSicurezzaMessaggioRisposta(context) || isErroreAllegatiRisposta(context) ||
isErroreValidazioneRisposta(context) || isErroreTrasformazioneRisposta(context) ||
isRispostaDuplicata(context)) {
error = Pair.of(FonteAutenticaError.SERVER_ERROR, ERROR_PROCESSING);
} else if(context.getPddContext()!=null){
error = Objects.requireNonNullElse(parsePddContext(context, idTransazione),
Objects.requireNonNullElse(parseCode(context), error));
} else {
error = Objects.requireNonNullElse(parseCode(context), error);
}
buildError(context, error.getLeft(), error.getRight());
}
private static Transaction getTransaction(String idTransazione) {
Transaction transaction = null;
try {
transaction = TransactionContext.getTransaction(idTransazione);
}catch(TransactionNotExistsException n) {
// ignore
}
return transaction;
}
private static boolean isErroreValidazioneRichiesta(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_VALIDAZIONE_RICHIESTA, context);
}
private static boolean isErroreValidazioneRisposta(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_VALIDAZIONE_RISPOSTA, context);
}
private static boolean isErroreTokenNonPresente(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.TOKEN_NON_PRESENTE, context);
}
private static boolean isErroreToken(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_TOKEN, context);
}
private static boolean isOperazioneNonIndividuata(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.OPERAZIONE_NON_INDIVIDUATA, context);
}
private 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);
}
private 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);
}
private 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);
}
private 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);
}
private static boolean isConnectorError(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_UTILIZZO_CONNETTORE, context);
}
private static boolean isContenutiRichiestaNonRiconosciuto(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.CONTENUTO_RICHIESTA_NON_RICONOSCIUTO, context);
}
private static boolean isContenutiRispostaNonRiconosciuto(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.CONTENUTO_RISPOSTA_NON_RICONOSCIUTO, context);
}
private static boolean isErroreSicurezzaMessaggioRisposta(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_SICUREZZA_MESSAGGIO_RISPOSTA, context);
}
private static boolean isErroreAllegatiRisposta(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_ALLEGATI_MESSAGGIO_RISPOSTA, context);
}
private static boolean isErroreCorrelazioneApplicativaRisposta(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_CORRELAZIONE_APPLICATIVA_RISPOSTA, context);
}
private static boolean isErroreTrasformazioneRichiesta(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_TRASFORMAZIONE_RICHIESTA, context);
}
private static boolean isErroreTrasformazioneRisposta(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.ERRORE_TRASFORMAZIONE_RISPOSTA, context);
}
private static boolean isRispostaDuplicata(OutResponseContext context) {
return isErroreBoolean(org.openspcoop2.core.constants.Costanti.RISPOSTA_DUPLICATA, context);
}
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;
}
private static final String HASH_NOT_FOUND_MATCHING = ".*Parameter 'If-Match' is required.*";
static boolean isMessaggioErroreValidazioneRichiestaHashNotFound(MsgDiagnostico msg) {
if(msg.getSeverita()<=LogLevels.SEVERITA_ERROR_INTEGRATION &&
msg.getMessaggio()!=null) {
boolean match = false;
try {
match = RegularExpressionEngine.isFind(msg.getMessaggio(), HASH_NOT_FOUND_MATCHING);
}catch(Exception e) {
// ignore
}
if(match) {
return true;
}
}
return false;
}
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 = org.openspcoop2.utils.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 void buildError(OutResponseContext context, FonteAutenticaError error, String errorDetails) throws MessageException {
ForcedResponseMessage frs = new ForcedResponseMessage();
OpenSPCoop2RestJsonMessage json = context.getMessaggio().castAsRestJson();
frs.setResponseCode(error.code+"");
if (FonteAutenticaError.INVALID_TOKEN == error) {
String msg = String.format("Bearer error=\"%s\", error_description=\"%s\"",
error.name().toLowerCase(),
errorDetails);
frs.setHeadersValues(Map.of(HttpConstants.AUTHORIZATION_RESPONSE_WWW_AUTHENTICATE,
List.of(msg)));
frs.setContent(new byte[0]);
frs.setContentType(HttpConstants.CONTENT_TYPE_PLAIN);
} else {
String msg = String.format("{\"error\": \"%s\", \"error_description\": \"%s\"}",
error.name().toLowerCase(),
errorDetails);
frs.setContent(msg.getBytes());
frs.setContentType(HttpConstants.CONTENT_TYPE_JSON);
}
json.forceResponse(frs);
}
}