Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SELC-4840] feat: added Ivass interceptor and utils to handle zip #218

Merged
merged 8 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ public InternalException(){
super();
}

public InternalException(String message){
super(message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,20 @@ void testConstructor() {
assertNull(cause.getMessage());
assertSame(suppressed, cause.getSuppressed());
}

@Test
void constructorWithMessage() {
String message = "Error message";
InternalException exception = new InternalException(message);
assertEquals(message, exception.getMessage());
assertNull(exception.getCause());
}

@Test
void constructorWithMessage_nullMessage() {
InternalException exception = new InternalException((String) null);
assertNull(exception.getMessage());
assertNull(exception.getCause());
}
}

9 changes: 9 additions & 0 deletions connector/rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,14 @@
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package it.pagopa.selfcare.party.registry_proxy.connector.rest.decoder;

import it.pagopa.selfcare.party.registry_proxy.connector.exception.InternalException;
import it.pagopa.selfcare.party.registry_proxy.connector.exception.InvalidRequestException;
import it.pagopa.selfcare.party.registry_proxy.connector.exception.ResourceNotFoundException;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.ResponseErrorHandler;

import java.io.IOException;

@Configuration
public class RestTemplateErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().isError();
}

@Override
public void handleError(ClientHttpResponse response) throws IOException {
switch (response.getStatusCode()) {
case BAD_REQUEST:
throw new InvalidRequestException(response.getStatusText());
case NOT_FOUND:
throw new ResourceNotFoundException(response.getStatusText());
default:
throw new InternalException(response.getStatusText());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package it.pagopa.selfcare.party.registry_proxy.connector.rest.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.io.IOException;


@Slf4j
@Component
public class IvassInterceptor implements ClientHttpRequestInterceptor {
@Override
@NonNull
public ClientHttpResponse intercept(@NonNull HttpRequest request, @NonNull byte[] body, @NonNull ClientHttpRequestExecution execution) throws IOException {
log.info("Request: " + request.getURI());
log.info("Headers: " + request.getHeaders());
log.info("Method: " + request.getMethod());
ClientHttpResponse response = execution.execute(request, body);
log.info("Response Status: " + response.getStatusCode());
log.info("Response Headers: " + response.getHeaders());
return response;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package it.pagopa.selfcare.party.registry_proxy.connector.rest.model;

import com.opencsv.bean.CsvBindByName;
import it.pagopa.selfcare.party.registry_proxy.connector.model.InsuranceCompany;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

@Data
@NoArgsConstructor
@EqualsAndHashCode(of = "originId")
public class IvassDataTemplate implements InsuranceCompany {

@CsvBindByName(column = "CODICE_IVASS")
private String originId;
@CsvBindByName(column = "CODICE_FISCALE")
private String taxCode;
@CsvBindByName(column = "DENOMINAZIONE_IMPRESA")
private String description;
@CsvBindByName(column = "PEC")
private String digitalAddress;
@CsvBindByName(column = "TIPO_LAVORO")
private String workType;
@CsvBindByName(column = "TIPO_ALBO")
private String registerType;
@CsvBindByName(column = "INDIRIZZO_SEDE_LEGALE_RAPPRESENTANZA_IN_ITALIA")
private String address;

public String getTaxCode() {
if(!StringUtils.isBlank(this.taxCode) && this.taxCode.length() < 11) {
return StringUtils.leftPad(this.taxCode, 11, "0");
}
return this.taxCode.trim();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package it.pagopa.selfcare.party.registry_proxy.connector.rest.utils;

import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import it.pagopa.selfcare.party.registry_proxy.connector.model.InsuranceCompany;
import it.pagopa.selfcare.party.registry_proxy.connector.rest.model.IvassDataTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Slf4j
@Service
public class IvassUtils {

public byte[] extractFirstEntryByteArrayFromZip(byte[] zipBytes) {
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(zipBytes);
ZipInputStream zipInputStream = new ZipInputStream(byteArrayInputStream)) {
ZipEntry entry = zipInputStream.getNextEntry();

if (entry != null) {
int totalSizeEntry = 0;
int thresholdSize = 100000000; // 100 MB
double thresholdRatio = 10; // 10 times the compressed size

try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = zipInputStream.read(buffer)) != -1) {
totalSizeEntry += length;

// Check the compression ratio of the extracted file (security reasons)
double compressionRatio = (double) totalSizeEntry / entry.getCompressedSize();
if(compressionRatio > thresholdRatio) {
log.error("Compression ratio exceeds the maximum allowed limit of " + thresholdRatio);
return new byte[0];
}

// Check if the extracted file size exceeds the maximum allowed limit (security reasons)
if(totalSizeEntry > thresholdSize) {
log.error("Extracted file size exceeds the maximum allowed limit of " + thresholdSize + " bytes");
return new byte[0];
}

byteArrayOutputStream.write(buffer, 0, length);
}
return byteArrayOutputStream.toByteArray();
}
} else {
throw new IOException("No entries found in the zip file");
}
} catch (IOException e) {
log.debug("Error extracting file from zip", e);
return new byte[0];
}
}

public List<InsuranceCompany> readCsv(byte[] csv) {
List<IvassDataTemplate> companies = new ArrayList<>();
try (Reader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(csv)))) {
CsvToBean<IvassDataTemplate> csvToBean = new CsvToBeanBuilder<IvassDataTemplate>(reader)
.withType(IvassDataTemplate.class)
.withSeparator(';')
.build();
companies = csvToBean.parse();
} catch (Exception e) {
log.error("Impossible to acquire data for IVASS. Error: {}", e.getMessage(), e);
}
return new ArrayList<>(companies);
}

public byte[] manageUTF8BOM(byte[] csv) {
// Check if the csv has the UTF-8 BOM and remove it
if (csv.length > 3 && csv[0] == (byte) 0xEF && csv[1] == (byte) 0xBB && csv[2] == (byte) 0xBF){
csv = Arrays.copyOfRange(csv, 3, csv.length);
}
return csv;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package it.pagopa.selfcare.party.registry_proxy.connector.rest.decoder;

import it.pagopa.selfcare.party.registry_proxy.connector.exception.InternalException;
import it.pagopa.selfcare.party.registry_proxy.connector.exception.InvalidRequestException;
import it.pagopa.selfcare.party.registry_proxy.connector.exception.ResourceNotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

class RestTemplateErrorHandlerTest {
@Mock
private ClientHttpResponse response;

@InjectMocks
private RestTemplateErrorHandler errorHandler;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}

@Test
void hasError_shouldReturnTrue_whenResponseIsError() throws IOException {
when(response.getStatusCode()).thenReturn(HttpStatus.BAD_REQUEST);
assertTrue(errorHandler.hasError(response));
}

@Test
void hasError_shouldReturnFalse_whenResponseIsNotError() throws IOException {
when(response.getStatusCode()).thenReturn(HttpStatus.OK);
assertFalse(errorHandler.hasError(response));
}

@Test
void handleError_shouldThrowInvalidRequestException_whenStatusIsBadRequest() throws IOException {
when(response.getStatusCode()).thenReturn(HttpStatus.BAD_REQUEST);
when(response.getStatusText()).thenReturn("Bad Request");
assertThrows(InvalidRequestException.class, () -> errorHandler.handleError(response));
}

@Test
void handleError_shouldThrowResourceNotFoundException_whenStatusIsNotFound() throws IOException {
when(response.getStatusCode()).thenReturn(HttpStatus.NOT_FOUND);
when(response.getStatusText()).thenReturn("Not Found");
assertThrows(ResourceNotFoundException.class, () -> errorHandler.handleError(response));
}

@Test
void handleError_shouldThrowInternalException_whenStatusIsOtherError() throws IOException {
when(response.getStatusCode()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR);
when(response.getStatusText()).thenReturn("Internal Server Error");
assertThrows(InternalException.class, () -> errorHandler.handleError(response));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package it.pagopa.selfcare.party.registry_proxy.connector.rest.interceptor;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;

class IvassInterceptorTest {
@Mock
private HttpRequest request;

@Mock
private ClientHttpRequestExecution execution;

@Mock
private ClientHttpResponse response;

@InjectMocks
private IvassInterceptor ivassInterceptor;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}

@Test
void intercept_shouldLogRequestAndResponseDetails() throws IOException {
byte[] body = new byte[]{};
when(execution.execute(request, body)).thenReturn(response);
when(response.getStatusCode()).thenReturn(HttpStatus.OK);

ClientHttpResponse result = ivassInterceptor.intercept(request, body, execution);

assertEquals(response, result);
verify(request, times(1)).getURI();
verify(request, times(1)).getHeaders();
verify(request, times(1)).getMethod();
verify(response, times(1)).getStatusCode();
verify(response, times(1)).getHeaders();
}

@Test
void intercept_shouldHandleIOException() throws IOException {
byte[] body = new byte[]{};
when(execution.execute(request, body)).thenThrow(new IOException("Test exception"));

assertThrows(IOException.class, () -> ivassInterceptor.intercept(request, body, execution));
}
}
Loading
Loading