Refactor security trace

This commit is contained in:
Ramon Ramirez 2026-01-19 14:47:29 -04:00
parent e898aa712d
commit e8c6eb1ccd
10 changed files with 212 additions and 55 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: {} -> {}",
@ -106,8 +114,27 @@ public class MessageHelper {
); );
return Response.status(mapping.getHttpCode()) return Response.status(mapping.getHttpCode())
.entity(new ApiResponse<>(status)) .entity(new ApiResponse<>(status))
.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() {

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

@ -10,6 +10,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;
import java.util.Objects; import java.util.Objects;
@ -34,33 +35,31 @@ public class ServiceOrderPaymentSearchService implements ServiceOrderPaymentSear
} }
@Override @Override
public ApiResponse<ServiceOrderPaymentSearchResponse> execute( public Response execute(
ServiceOrderPaymentSearchRequest request ServiceOrderPaymentSearchRequest request
) { ) {
log.info("Iniciando ejecucion para el transaccion: {}", request.getPartyId()); log.info("Iniciando ejecucion para la transaccion: {}", request.getPartyId());
Response response;
validate(request); validate(request);
try { try {
ApiResponse<ServiceOrderPaymentSearchResponse> response = business(request); ApiResponse<ServiceOrderPaymentSearchResponse> apiResponse = business(request);
if ( response = messageHelper.handleSuccess(
!Objects.isNull(response.getData()) && apiResponse.getData(),
messageHelper.isSuccessStatusCode(response.getStatusResponse()) apiResponse.getStatusResponse().getStatusCode()
) { );
return new ApiResponse<>(response.getData(), messageHelper.createStatusResponse(
response.getStatusResponse().getStatusCode()
));
}
throw HttpStatusCodeException.serviceUnavailable("503");
} catch (HttpStatusCodeException e) { } catch (HttpStatusCodeException e) {
log.error("Excepcion HTTP del api de negocio: {} - {}", e.getStatusCode(), e.getErrorCode()); log.error("Excepcion HTTP del api de dominio: {} - {}", e.getStatusCode(), e.getErrorCode());
throw e; response = messageHelper.handleException(e);
} catch (Exception e) { } catch (Exception e) {
log.error("Excepcion generica del api de negocio: {}", e.getMessage()); log.error("Excepcion generica del api de dominio: {}", e.getMessage());
throw e; response = messageHelper.handleGenericException(e);
} }
return response;
} }
private void validate( private void validate(

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

@ -47,8 +47,6 @@ public class BusServiceOrderPaymentSearchClient implements BusinessUseCase {
businessConfig.getTimeout().getResponse() businessConfig.getTimeout().getResponse()
); );
log.debug("Request configurado: {}", request);
try { try {
ApiResponse<T> response = httpClientUseCase.executeApiResponse(request); ApiResponse<T> response = httpClientUseCase.executeApiResponse(request);

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()
);
} }
} }