Refactor security trace

This commit is contained in:
Ramon Ramirez 2026-01-19 14:42:28 -04:00
parent 221720ecc6
commit 5c91ee50b6
11 changed files with 279 additions and 45 deletions

View File

@ -40,6 +40,14 @@ public class MessageHelper {
this.errorMappings = initializeErrorMappings(); this.errorMappings = initializeErrorMappings();
} }
public Response handleSuccess(Object data, String statusCode) {
log.info(
"Respuesta exitosa controlada: {}",
statusCode
);
return buildResponse(data, statusCode);
}
public Response handleException(HttpStatusCodeException exception) { public Response handleException(HttpStatusCodeException exception) {
log.error( log.error(
"Error interno controlado: {} -> {}", "Error interno controlado: {} -> {}",
@ -110,6 +118,25 @@ public class MessageHelper {
.build(); .build();
} }
private Response buildResponse(Object data, String statusCode) {
ErrorMapping mapping = errorMappings.getOrDefault(
statusCode, errorMappings.getOrDefault(
statusCode, createDefaultMapping()
)
);
StatusResponse status = createError(mapping, null);
log.error(
"[Success] Message {} -> {}",
statusCode,
status.getMessage()
);
return Response.status(mapping.getHttpCode())
.entity(new ApiResponse<>(data, status))
.build();
}
private Map<String, ErrorMapping> initializeErrorMappings() { private Map<String, ErrorMapping> initializeErrorMappings() {
try { try {
String json; String json;

View File

@ -3,12 +3,14 @@ package com.banesco.common.application.helper;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.util.internal.StringUtil;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
@Slf4j @Slf4j
@ -36,6 +38,14 @@ public class SerializationHelper {
} }
} }
public String encodeStringToBase64(String text) {
if (StringUtil.isNullOrEmpty(text)) {
return null;
}
return Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
}
public <T> T decodeBase64(String base64String, Class<T> clazz) { public <T> T decodeBase64(String base64String, Class<T> clazz) {
try { try {
byte[] decodedBytes = Base64.getDecoder().decode(base64String); byte[] decodedBytes = Base64.getDecoder().decode(base64String);
@ -93,4 +103,57 @@ public class SerializationHelper {
return new HashMap<>(); return new HashMap<>();
} }
} }
public <T> Map<String, Object> toTreeMap(T element) {
return toTreeMap(element, null);
}
public <T> Map<String, Object> toTreeMap(
T element,
List<String> excludedFields
) {
if (element == null) {
return new TreeMap<>();
}
try {
Map<String, Object> tempMap = objectMapper.convertValue(
element, new TypeReference<>() {}
);
Map<String, Object> treeMap = new TreeMap<>(tempMap);
if (excludedFields != null && !excludedFields.isEmpty()) {
Set<String> excludedSet = new HashSet<>(excludedFields);
excludedSet.forEach(treeMap::remove);
}
return treeMap;
} catch (Exception e) {
log.error("Error al convertir objeto a TreeMap: {}", e.getMessage());
return new TreeMap<>();
}
}
public String generateSignature(
Object operation,
String channelOrigin
) {
Map<String, Object> operationSortedMap = toTreeMap(operation);
StringBuilder concatenatedValues = new StringBuilder();
String channelBase64 = encodeStringToBase64(channelOrigin);
for (Object value : operationSortedMap.values()) {
if(!Objects.isNull(value)) {
concatenatedValues.append(value);
}
}
String finalString = concatenatedValues + channelBase64;
log.info("1. Operation concatenando valores: {}", concatenatedValues);
log.info("2. Channel Origin codificado: {}", channelBase64);
return encodeSha256(finalString);
}
} }

View File

@ -0,0 +1,17 @@
package com.banesco.common.domain.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.*;
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public class Device {
private String deviceType;
private String deviceDescription;
private String deviceIp;
private String deviceSessionReference;
}

View File

@ -6,7 +6,9 @@ public class RequestContext {
private RequestContext() {} private RequestContext() {}
private static final String REQUEST_ID = "requestId"; public static final String REQUEST_ID = "requestId";
public static final String DEVICE = "device";
public static final String DEVICE_SESSION_REFERENCE = "deviceSessionReference";
public static String getRequestId() { public static String getRequestId() {
return MDC.get(REQUEST_ID); return MDC.get(REQUEST_ID);

View File

@ -6,15 +6,88 @@ import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
@Slf4j
@Provider @Provider
public class RequestIdFilter implements ContainerRequestFilter, ContainerResponseFilter { public class RequestIdFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override @Override
public void filter(ContainerRequestContext requestContext) { public void filter(ContainerRequestContext requestContext) {
RequestContext.setRequestId(UUID.randomUUID().toString().substring(0, 13)); String requestId = requestContext.getHeaderString(RequestContext.DEVICE_SESSION_REFERENCE);
if (isEmpty(requestId)) {
requestId = requestContext.getUriInfo()
.getQueryParameters()
.getFirst(RequestContext.DEVICE_SESSION_REFERENCE);
}
if (isEmpty(requestId) && hasJsonBody(requestContext)) {
requestId = extractRequestIdFromBody(requestContext);
}
if (isEmpty(requestId)) {
requestId = UUID.randomUUID().toString().substring(0, 13);
}
RequestContext.setRequestId(requestId);
}
private boolean isEmpty(String value) {
return value == null || value.trim().isEmpty();
}
private boolean hasJsonBody(ContainerRequestContext context) {
try {
String method = context.getMethod();
String contentType = context.getHeaderString("Content-Type");
return ("POST".equals(method) || "PUT".equals(method))
&& contentType != null
&& contentType.contains("application/json");
} catch (Exception e) {
log.warn("La peticion no es un POST o PUT: {}", e.getMessage());
return false;
}
}
private String extractRequestIdFromBody(ContainerRequestContext context) {
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
context.getEntityStream().transferTo(buffer);
byte[] bodyBytes = buffer.toByteArray();
context.setEntityStream(new ByteArrayInputStream(bodyBytes));
String bodyString = new String(bodyBytes, StandardCharsets.UTF_8);
io.vertx.core.json.JsonObject jsonObject = new io.vertx.core.json.JsonObject(bodyString);
if (jsonObject.containsKey(RequestContext.DEVICE)) {
io.vertx.core.json.JsonObject device = jsonObject.getJsonObject(RequestContext.DEVICE);
if (device.containsKey(RequestContext.DEVICE_SESSION_REFERENCE)) {
return device.getString(RequestContext.DEVICE_SESSION_REFERENCE);
}
}
if (jsonObject.containsKey(RequestContext.REQUEST_ID)) {
return jsonObject.getString(RequestContext.REQUEST_ID);
}
if (jsonObject.containsKey(RequestContext.DEVICE_SESSION_REFERENCE)) {
return jsonObject.getString(RequestContext.DEVICE_SESSION_REFERENCE);
}
return null;
} catch (Exception e) {
log.error("Error extrayendo el requestId del cuerpo de la peticion: {}", e.getMessage());
return null;
}
} }
@Override @Override
@ -22,6 +95,10 @@ public class RequestIdFilter implements ContainerRequestFilter, ContainerRespons
ContainerRequestContext requestContext, ContainerRequestContext requestContext,
ContainerResponseContext responseContext ContainerResponseContext responseContext
) { ) {
RequestContext.clear(); try {
RequestContext.clear();
} catch (Exception e) {
log.error("Error limpiando el filtro: {}", e.getMessage());
}
} }
} }

View File

@ -110,20 +110,21 @@ public class PaymentOrderClient implements PaymentOrderUseCase {
private String getSignatureIdentifier(PaymentOrderRequest params) { private String getSignatureIdentifier(PaymentOrderRequest params) {
Map<String, Object> paymentOrderRequest = serializationHelper.toMap(params); Map<String, Object> paymentOrderRequest = serializationHelper.toMap(params);
String signature = serializationHelper.encodeSha256( Map<String, Object> operation = serializationHelper.toMap(params.getOperation());
serializationHelper.toJsonString(paymentOrderRequest.get("operation")) + String signature = serializationHelper.generateSignature(
serializationHelper.encodeBase64(params.getChannelId()) params.getOperation(), params.getChannelId()
); ) + "asgasg";
paymentOrderRequest.put("signature", signature); paymentOrderRequest.put("signature", signature);
paymentOrderRequest.put("operation", operation);
paymentOrderRequest.remove("fintechId"); paymentOrderRequest.remove("fintechId");
String signatureIdentifier = serializationHelper.encodeBase64(paymentOrderRequest); String signatureIdentifier = serializationHelper.encodeBase64(paymentOrderRequest);
log.info("1. Firma generada: {}", signature); log.info("3. Firma generada: {}", signature);
log.info("2. Parametros de la solicitud: {}", paymentOrderRequest); log.info("4. Parametros de la solicitud: {}", paymentOrderRequest);
log.info("3. Solicitud codificada: {}", signatureIdentifier); log.info("5. Solicitud final: {} -> {}", paymentOrderRequest, signatureIdentifier);
return signatureIdentifier; return signatureIdentifier;
} }

View File

@ -9,6 +9,7 @@ import com.banesco.module.service_order_payment_search.domain.dto.request.Servic
import com.banesco.module.service_order_payment_search.domain.dto.response.ServiceOrderPaymentSearchResponse; import com.banesco.module.service_order_payment_search.domain.dto.response.ServiceOrderPaymentSearchResponse;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ -28,13 +29,20 @@ public class ServiceOrderPaymentSearchService implements ServiceOrderPaymentSear
} }
@Override @Override
public ApiResponse<ServiceOrderPaymentSearchResponse> execute( public Response execute(
ServiceOrderPaymentSearchRequest request ServiceOrderPaymentSearchRequest request
) { ) {
log.info("Iniciando ejecucion para el id: {}", request.getPartyId()); log.info("Iniciando ejecucion para el id: {}", request.getPartyId());
Response response;
try { try {
return apiPrivate(request); ApiResponse<ServiceOrderPaymentSearchResponse> apiResponse = apiPrivate(request);
response = messageHelper.handleSuccess(
apiResponse.getData(),
apiResponse.getStatusResponse().getStatusCode()
);
} catch (ApiPrivateException e) { } catch (ApiPrivateException e) {
log.warn( log.warn(
"Excepcion de la api privada: {} -> {}", "Excepcion de la api privada: {} -> {}",
@ -44,11 +52,13 @@ public class ServiceOrderPaymentSearchService implements ServiceOrderPaymentSear
throw HttpStatusCodeException.badRequest("400"); throw HttpStatusCodeException.badRequest("400");
} catch (HttpStatusCodeException e) { } catch (HttpStatusCodeException e) {
log.error("Excepcion HTTP del api privada: {} - {}", e.getStatusCode(), e.getErrorCode()); log.error("Excepcion HTTP del api privada: {} - {}", e.getStatusCode(), e.getErrorCode());
throw e; response = messageHelper.handleException(e);
} catch (Exception e) { } catch (Exception e) {
log.error("Excepcion generica del api privada: {}", e.getMessage()); log.error("Excepcion generica del api privada: {}", e.getMessage());
throw e; response = messageHelper.handleGenericException(e);
} }
return response;
} }
private ApiResponse<ServiceOrderPaymentSearchResponse> apiPrivate( private ApiResponse<ServiceOrderPaymentSearchResponse> apiPrivate(

View File

@ -1,11 +1,10 @@
package com.banesco.module.service_order_payment_search.application.usecase; package com.banesco.module.service_order_payment_search.application.usecase;
import com.banesco.common.domain.model.ApiResponse;
import com.banesco.module.service_order_payment_search.domain.dto.request.ServiceOrderPaymentSearchRequest; import com.banesco.module.service_order_payment_search.domain.dto.request.ServiceOrderPaymentSearchRequest;
import com.banesco.module.service_order_payment_search.domain.dto.response.ServiceOrderPaymentSearchResponse; import jakarta.ws.rs.core.Response;
public interface ServiceOrderPaymentSearchUseCase { public interface ServiceOrderPaymentSearchUseCase {
ApiResponse<ServiceOrderPaymentSearchResponse> execute( Response execute(
ServiceOrderPaymentSearchRequest request ServiceOrderPaymentSearchRequest request
); );
} }

View File

@ -1,7 +1,10 @@
package com.banesco.module.service_order_payment_search.domain.dto.request; package com.banesco.module.service_order_payment_search.domain.dto.request;
import com.banesco.common.domain.model.Device;
import com.banesco.common.infrastructure.context.RequestContext;
import com.banesco.module.instruction.domain.model.Instruction; import com.banesco.module.instruction.domain.model.Instruction;
import com.banesco.module.instruction.domain.model.InstructionPurposeType; import com.banesco.module.instruction.domain.model.InstructionPurposeType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.*; import lombok.*;
@ -18,12 +21,17 @@ import static java.util.Map.entry;
@RegisterForReflection @RegisterForReflection
public class ServiceOrderPaymentSearchRequest { public class ServiceOrderPaymentSearchRequest {
@NonNull @NonNull
@JsonIgnore
private String customerReferenceFintechId; private String customerReferenceFintechId;
@NonNull @NonNull
@JsonIgnore
private String appId; private String appId;
@NonNull @NonNull
private Instruction procedureRequest; private Instruction procedureRequest;
@NonNull
private Device device;
@JsonIgnore
public String getPartyId() { public String getPartyId() {
return getProcedureRequest() return getProcedureRequest()
.getInstructionInvolvement() .getInstructionInvolvement()
@ -34,6 +42,7 @@ public class ServiceOrderPaymentSearchRequest {
.getIdentifierValue(); .getIdentifierValue();
} }
@JsonIgnore
public String getInitiatedDate() { public String getInitiatedDate() {
return getProcedureRequest() return getProcedureRequest()
.getInstructionDate() .getInstructionDate()
@ -41,6 +50,7 @@ public class ServiceOrderPaymentSearchRequest {
.getDate(); .getDate();
} }
@JsonIgnore
public String getInstructionRequestId() { public String getInstructionRequestId() {
return getProcedureRequest() return getProcedureRequest()
.getInstructionIdentifier() .getInstructionIdentifier()
@ -48,14 +58,20 @@ public class ServiceOrderPaymentSearchRequest {
.getIdentifierValue(); .getIdentifierValue();
} }
@JsonIgnore
public String getChannelCode() { public String getChannelCode() {
return getProcedureRequest().getInstructionPurposeType().name(); return getProcedureRequest()
.getInstructionPurposeType()
.name();
} }
@JsonIgnore
public boolean isBole() { public boolean isBole() {
return getProcedureRequest().getInstructionPurposeType() == InstructionPurposeType.BOLE; return getProcedureRequest()
.getInstructionPurposeType() == InstructionPurposeType.BOLE;
} }
@JsonIgnore
public Map<String, String> toParams() { public Map<String, String> toParams() {
return Map.ofEntries( return Map.ofEntries(
entry("partyReferenceId", Objects.toString(getPartyId(), "")), entry("partyReferenceId", Objects.toString(getPartyId(), "")),
@ -63,12 +79,20 @@ public class ServiceOrderPaymentSearchRequest {
); );
} }
@JsonIgnore
public Map<String, String> toQueryString() { public Map<String, String> toQueryString() {
return Map.ofEntries( return Map.ofEntries(
entry("appId", Objects.toString(getAppId(), "")), entry("appId", Objects.toString(getAppId(), "")),
entry("customerReferenceFintechId", Objects.toString(getCustomerReferenceFintechId(), "")), entry("customerReferenceFintechId", Objects.toString(getCustomerReferenceFintechId(), "")),
entry("initiatedDate", Objects.toString(getInitiatedDate(), "")), entry("initiatedDate", Objects.toString(getInitiatedDate(), "")),
entry("instructionRequestId", Objects.toString(getInstructionRequestId(), "")) entry("instructionRequestId", Objects.toString(getInstructionRequestId(), "")),
entry("deviceType", Objects.toString(getDevice().getDeviceType(), "")),
entry("deviceDescription", Objects.toString(getDevice().getDeviceDescription(), "")),
entry("deviceIp", Objects.toString(getDevice().getDeviceIp(), "")),
entry("deviceSessionReference", Objects.toString(
getDevice().getDeviceSessionReference(),
RequestContext.getRequestId()
))
); );
} }
} }

View File

@ -1,8 +1,7 @@
package com.banesco.module.service_order_payment_search.infrastructure.resource; package com.banesco.module.service_order_payment_search.infrastructure.resource;
import com.banesco.common.application.helper.MessageHelper;
import com.banesco.common.domain.exception.HttpStatusCodeException;
import com.banesco.common.domain.model.ApiResponse; import com.banesco.common.domain.model.ApiResponse;
import com.banesco.common.domain.model.Device;
import com.banesco.common.domain.model.StatusResponse; import com.banesco.common.domain.model.StatusResponse;
import com.banesco.module.instruction.domain.model.Instruction; import com.banesco.module.instruction.domain.model.Instruction;
import com.banesco.module.service_order_payment_search.application.usecase.ServiceOrderPaymentSearchUseCase; import com.banesco.module.service_order_payment_search.application.usecase.ServiceOrderPaymentSearchUseCase;
@ -31,14 +30,11 @@ import java.util.Objects;
public class ServiceOrderPaymentSearchResource { public class ServiceOrderPaymentSearchResource {
private final ServiceOrderPaymentSearchUseCase useCase; private final ServiceOrderPaymentSearchUseCase useCase;
private final MessageHelper messageHelper;
@Inject @Inject
public ServiceOrderPaymentSearchResource( public ServiceOrderPaymentSearchResource(
MessageHelper messageHelper,
ServiceOrderPaymentSearchUseCase useCase ServiceOrderPaymentSearchUseCase useCase
) { ) {
this.messageHelper = messageHelper;
this.useCase = useCase; this.useCase = useCase;
} }
@ -447,27 +443,45 @@ public class ServiceOrderPaymentSearchResource {
@QueryParam("instructionRequestId") @QueryParam("instructionRequestId")
@Parameter(description = "ID de la peticion de pago", example = "1") @Parameter(description = "ID de la peticion de pago", example = "1")
String instructionRequestId String instructionRequestId,
@QueryParam("deviceType")
@Parameter(description = "Tipo de dispositivo", example = "Mobile")
String deviceType,
@QueryParam("deviceDescription")
@Parameter(description = "Descripcion del dispositivo", example = "Xiaomi Note 11 PRO")
String deviceDescription,
@QueryParam("deviceIp")
@Parameter(description = "Direccion IP del dispositivo", example = "127.0.0.1")
String deviceIp,
@QueryParam("deviceSessionReference")
@Parameter(description = "Referencia de la peticion del dispositivo", example = "12345678901304")
String deviceSessionReference
) { ) {
log.info("Iniciando consulta para instruccion de archivo id: {}", partyReferenceId); log.info("Iniciando consulta para instruccion de archivo id: {}", partyReferenceId);
try { return useCase.execute(
return Response.ok(useCase.execute( ServiceOrderPaymentSearchRequest.builder()
ServiceOrderPaymentSearchRequest.builder() .customerReferenceFintechId(Objects.toString(customerReferenceFintechId, ""))
.customerReferenceFintechId(Objects.toString(customerReferenceFintechId, "")) .appId(Objects.toString(appId, ""))
.appId(Objects.toString(appId, "")) .procedureRequest(Instruction.fromResource(
.procedureRequest(Instruction.fromResource( partyReferenceId,
partyReferenceId, initiatedDate,
initiatedDate, instructionRequestId,
instructionRequestId, channelCode
channelCode ))
)) .device(
.build() Device.builder()
)).build(); .deviceType(deviceType)
} catch (HttpStatusCodeException e) { .deviceDescription(deviceDescription)
return messageHelper.handleException(e); .deviceIp(deviceIp)
} catch (Exception e) { .deviceSessionReference(deviceSessionReference)
return messageHelper.handleGenericException(e); .build()
} )
.build()
);
} }
} }