Skip to content

Commit

Permalink
[P4ADEV-2047] Authorization on download API
Browse files Browse the repository at this point in the history
  • Loading branch information
pelliccm committed Feb 21, 2025
1 parent b54d342 commit 0ce0729
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 33 deletions.
1 change: 1 addition & 0 deletions openapi/p4pa-fileshare.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ components:
- NOT_FOUND
- CONFLICT
- BAD_REQUEST
- UNAUTHORIZED
- GENERIC_ERROR
message:
type: string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package it.gov.pagopa.pu.fileshare.connector.processexecutions;

import it.gov.pagopa.pu.p4paauth.dto.generated.UserInfo;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ExportFile;

public interface ExportFileService {
ExportFile getExportFile(Long exportFileId, Long organizationId, UserInfo loggedUser, String accessToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package it.gov.pagopa.pu.fileshare.connector.processexecutions;

import it.gov.pagopa.pu.fileshare.connector.processexecutions.client.ExportFileClient;
import it.gov.pagopa.pu.fileshare.exception.custom.UnauthorizedFileDownloadException;
import it.gov.pagopa.pu.fileshare.service.AuthorizationService;
import it.gov.pagopa.pu.p4paauth.dto.generated.UserInfo;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ExportFile;
import org.springframework.stereotype.Service;

@Service
public class ExportFileServiceImpl implements ExportFileService {

private final ExportFileClient client;

public ExportFileServiceImpl(ExportFileClient client) {
this.client = client;
}

@Override
public ExportFile getExportFile(Long exportFileId, Long organizationId, UserInfo loggedUser, String accessToken) {
boolean isLoggedUserAdmin = AuthorizationService.isAdminRole(
organizationId, loggedUser);
String operatorExternalUserId = null;

if(!isLoggedUserAdmin){
operatorExternalUserId = loggedUser.getMappedExternalUserId();
}

ExportFile exportFile = client.getExportFile(exportFileId, accessToken);

if (!isLoggedUserAdmin && !operatorExternalUserId.equals(exportFile.getOperatorExternalId())) {
throw new UnauthorizedFileDownloadException("User is not authorized to download export file with ID " + exportFileId);
}

return exportFile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import it.gov.pagopa.pu.fileshare.connector.processexecutions.config.ProcessExecutionsApisHolder;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ExportFile;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.IngestionFlowFile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;

Expand All @@ -23,7 +21,7 @@ public ExportFile getExportFile(Long exportFileId, String accessToken) {
log.debug("Fetching export file with ID [{}]", exportFileId);
return processExecutionsApisHolder.getExportFileEntityControllerApi(accessToken).crudGetExportfile(String.valueOf(exportFileId));
} catch (HttpClientErrorException.NotFound e) {
log.info("Cannot find ExportFile with ID [{}]", exportFileId, e);
log.info("Cannot find ExportFile with ID [{}]", exportFileId);
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import it.gov.pagopa.pu.fileshare.dto.FileResourceDTO;
import it.gov.pagopa.pu.fileshare.security.SecurityUtils;
import it.gov.pagopa.pu.fileshare.service.export.ExportFileFacadeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
Expand All @@ -13,6 +14,7 @@
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class ExportFilesController implements ExportFileApi {

private final ExportFileFacadeService exportFileFacadeService;
Expand All @@ -25,6 +27,8 @@ public ExportFilesController(

@Override
public ResponseEntity<Resource> downloadExportFile(Long organizationId, Long exportFileId) {
log.debug("Requesting to download export file [exportFileId: {}] of organization [organizationId: {}]", exportFileId, organizationId);

FileResourceDTO fileResourceDTO = exportFileFacadeService.downloadExportFile(organizationId, exportFileId, SecurityUtils.getLoggedUser(), SecurityUtils.getAccessToken());

Resource fileResource = new InputStreamResource(fileResourceDTO.getResourceStream());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import it.gov.pagopa.pu.fileshare.exception.custom.FileAlreadyExistsException;
import it.gov.pagopa.pu.fileshare.exception.custom.FileUploadException;
import it.gov.pagopa.pu.fileshare.exception.custom.InvalidFileException;
import it.gov.pagopa.pu.fileshare.exception.custom.UnauthorizedFileDownloadException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ValidationException;
Expand Down Expand Up @@ -41,6 +42,11 @@ public ResponseEntity<FileshareErrorDTO> handleInvalidFileError(RuntimeException
return handleException(ex, request, HttpStatus.BAD_REQUEST, CodeEnum.INVALID_FILE);
}

@ExceptionHandler({UnauthorizedFileDownloadException.class})
public ResponseEntity<FileshareErrorDTO> handleUnauthorizedFileDownloadError(RuntimeException ex, HttpServletRequest request){
return handleException(ex, request, HttpStatus.UNAUTHORIZED, CodeEnum.UNAUTHORIZED);
}

@ExceptionHandler({FileUploadException.class})
public ResponseEntity<FileshareErrorDTO> handleFileStorageError(RuntimeException ex, HttpServletRequest request){
return handleException(ex, request, HttpStatus.INTERNAL_SERVER_ERROR, CodeEnum.FILE_UPLOAD_ERROR);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package it.gov.pagopa.pu.fileshare.exception.custom;

public class UnauthorizedFileDownloadException extends RuntimeException {
public UnauthorizedFileDownloadException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import it.gov.pagopa.pu.fileshare.connector.auth.client.AuthnClient;
import it.gov.pagopa.pu.p4paauth.dto.generated.UserInfo;
import it.gov.pagopa.pu.p4paauth.dto.generated.UserOrganizationRoles;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

@Service
@Slf4j
public class AuthorizationService {
public static final String ROLE_ADMIN = "ROLE_ADMIN";

private final AuthnClient authnClient;

Expand All @@ -19,4 +23,17 @@ public UserInfo validateToken(String accessToken){
log.info("Requesting validate token");
return authnClient.getUserInfo(accessToken);
}

public static boolean isAdminRole(Long organizationId, UserInfo loggedUser) {
return getUserOrganizationRoles(organizationId, loggedUser)
.filter(o -> !CollectionUtils.isEmpty(o.getRoles()) && o.getRoles()
.contains(ROLE_ADMIN))
.isPresent();
}

private static Optional<UserOrganizationRoles> getUserOrganizationRoles(Long organizationId, UserInfo loggedUser) {
return loggedUser.getOrganizations().stream()
.filter(o -> organizationId.equals(o.getOrganizationId()) && !CollectionUtils.isEmpty(o.getRoles()))
.findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,28 @@
package it.gov.pagopa.pu.fileshare.service.export;


import static it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ExportFile.StatusEnum.COMPLETED;
import static it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ExportFile.StatusEnum.ERROR;

import it.gov.pagopa.pu.fileshare.connector.processexecutions.client.ExportFileClient;
import it.gov.pagopa.pu.fileshare.connector.processexecutions.ExportFileService;
import it.gov.pagopa.pu.fileshare.dto.FileResourceDTO;
import it.gov.pagopa.pu.fileshare.service.FileStorerService;
import it.gov.pagopa.pu.fileshare.service.UserAuthorizationService;
import it.gov.pagopa.pu.p4paauth.dto.generated.UserInfo;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ExportFile;
import java.io.InputStream;
import java.nio.file.Path;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;

public class ExportFileFacadeServiceImpl implements ExportFileFacadeService {
private final UserAuthorizationService userAuthorizationService;
private final FileStorerService fileStorerService;
private final ExportFileClient exportFileClient;
private final String archivedSubFolder;
private final ExportFileService exportFileService;

public ExportFileFacadeServiceImpl(
UserAuthorizationService userAuthorizationService,
FileStorerService fileStorerService,
ExportFileClient exportFileClient,
@Value("${folders.process-target-sub-folders.archive}") String archivedSubFolder) {
ExportFileService exportFileService) {
this.userAuthorizationService = userAuthorizationService;
this.fileStorerService = fileStorerService;
this.exportFileClient = exportFileClient;
this.archivedSubFolder = archivedSubFolder;
this.exportFileService = exportFileService;
}

@Override
Expand All @@ -38,7 +31,7 @@ public FileResourceDTO downloadExportFile(Long organizationId,
String accessToken) {
userAuthorizationService.checkUserAuthorization(organizationId, user, accessToken);

ExportFile exportFile = exportFileClient.getExportFile(exportFileId, accessToken);
ExportFile exportFile = exportFileService.getExportFile(exportFileId, organizationId, user, accessToken);

Path filePath = getFilePath(exportFile);

Expand All @@ -52,10 +45,7 @@ private Path getFilePath(ExportFile exportFile) {

Path filePath = organizationBasePath
.resolve(exportFile.getFilePathName());
if (exportFile.getStatus() == COMPLETED || exportFile.getStatus() == ERROR) {
filePath = filePath
.resolve(archivedSubFolder);
}

return filePath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

import it.gov.pagopa.pu.fileshare.connector.processexecutions.config.ProcessExecutionsApisHolder;
import it.gov.pagopa.pu.p4paprocessexecutions.controller.generated.ExportFileEntityControllerApi;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ClassificationsExportFile;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.ExportFile;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.IngestionFlowFile;
import it.gov.pagopa.pu.p4paprocessexecutions.dto.generated.IngestionFlowFileRequestDTO;
import java.net.URI;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -16,7 +12,6 @@
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;

@ExtendWith(MockitoExtension.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package it.gov.pagopa.pu.fileshare.service.export;

import it.gov.pagopa.pu.fileshare.connector.processexecutions.client.ExportFileClient;
import it.gov.pagopa.pu.fileshare.connector.processexecutions.ExportFileService;
import it.gov.pagopa.pu.fileshare.dto.FileResourceDTO;
import it.gov.pagopa.pu.fileshare.service.FileStorerService;
import it.gov.pagopa.pu.fileshare.service.UserAuthorizationService;
Expand All @@ -27,7 +27,7 @@ class ExportFileFacadeServiceImplTest {
@Mock
private FileStorerService fileStorerServiceMock;
@Mock
private ExportFileClient exportFileClientMock;
private ExportFileService exportFileServiceMock;
@Mock
private ExportFileFacadeServiceImpl exportFileService;
private static final String ARCHIVED_SUB_FOLDER = "Archived";
Expand All @@ -37,16 +37,15 @@ void setUp() {
exportFileService = new ExportFileFacadeServiceImpl(
userAuthorizationServiceMock,
fileStorerServiceMock,
exportFileClientMock,
ARCHIVED_SUB_FOLDER);
exportFileServiceMock);
}

@AfterEach
void verifyNoMoreInteractions(){
Mockito.verifyNoMoreInteractions(
userAuthorizationServiceMock,
fileStorerServiceMock,
exportFileClientMock);
exportFileServiceMock);
}

@Test
Expand All @@ -57,7 +56,7 @@ void givenAuthorizedUserWhenDownloadExportFileThenReturnFileResource() {
Path organizationBasePath = Path.of("/organizationFolder");
String filePathName = "examplePath";
String fileName = "testFile.zip";
Path fullFilePath = organizationBasePath.resolve(filePathName).resolve(ARCHIVED_SUB_FOLDER);
Path fullFilePath = organizationBasePath.resolve(filePathName);

UserInfo user = TestUtils.getSampleUser();

Expand All @@ -72,7 +71,7 @@ void givenAuthorizedUserWhenDownloadExportFileThenReturnFileResource() {
Mockito.when(fileStorerServiceMock.buildOrganizationBasePath(organizationId))
.thenReturn(organizationBasePath);

Mockito.when(exportFileClientMock.getExportFile(exportFileId, accessToken)).thenReturn(exportFile);
Mockito.when(exportFileServiceMock.getExportFile(exportFileId, organizationId, user, accessToken)).thenReturn(exportFile);

Mockito.when(fileStorerServiceMock.decryptFile(fullFilePath, fileName)).thenReturn(decryptedInputStream);

Expand All @@ -82,7 +81,7 @@ void givenAuthorizedUserWhenDownloadExportFileThenReturnFileResource() {
Assertions.assertEquals(fileName, result.getFileName());

Mockito.verify(userAuthorizationServiceMock).checkUserAuthorization(organizationId, user, accessToken);
Mockito.verify(exportFileClientMock).getExportFile(exportFileId, accessToken);
Mockito.verify(exportFileServiceMock).getExportFile(exportFileId, organizationId, user, accessToken);
Mockito.verify(fileStorerServiceMock).decryptFile(fullFilePath, fileName);
}

Expand All @@ -107,7 +106,7 @@ void givenExportFileInProgressWhenDownloadExportFileThenReturnFilePath() {
InputStream decryptedInputStream = Mockito.mock(ByteArrayInputStream.class);

Mockito.when(fileStorerServiceMock.buildOrganizationBasePath(organizationId)).thenReturn(organizationBasePath);
Mockito.when(exportFileClientMock.getExportFile(exportFileId, accessToken)).thenReturn(exportFile);
Mockito.when(exportFileServiceMock.getExportFile(exportFileId, organizationId, user, accessToken)).thenReturn(exportFile);
Mockito.when(fileStorerServiceMock.decryptFile(fullFilePath, fileName)).thenReturn(decryptedInputStream);

FileResourceDTO result = exportFileService.downloadExportFile(organizationId, exportFileId, user, accessToken);
Expand Down

0 comments on commit 0ce0729

Please sign in to comment.