Skip to content
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
6 changes: 5 additions & 1 deletion multiapps-controller-core/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>multiapps-controller-core</artifactId>
Expand All @@ -13,6 +13,10 @@
</parent>

<dependencies>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
Expand Down
1 change: 1 addition & 0 deletions multiapps-controller-core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
requires org.apache.commons.lang3;
requires org.apache.httpcomponents.httpclient;
requires org.apache.httpcomponents.httpcore;
requires org.apache.tika.core;
requires org.cloudfoundry.multiapps.common;
requires org.cloudfoundry.multiapps.controller.api;
requires org.slf4j;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public final class Messages {
public static final String OBJECT_STORE_FILE_STORAGE_HEALTH_DATABASE_HEALTH = "Object store file storage health: \"{0}\", Database health: \"{1}\"";
public static final String ERROR_OCCURRED_DURING_OBJECT_STORE_HEALTH_CHECKING_FOR_INSTANCE = "Error occurred during object store health checking for instance: \"{0}\"";
public static final String ERROR_OCCURRED_WHILE_CHECKING_DATABASE_INSTANCE_0 = "Error occurred while checking database instance: \"{0}\"";
public static final String THE_PROVIDED_MULTIPART_FILE_CANNOT_BE_EMPTY = "The provided multipart file cannot be empty";
public static final String THE_PROVIDED_0_FILE_IS_INVALID = "The provided {0} file is invalid! The file format must be either yaml or mtaext";
public static final String UNSUPPORTED_FILE_FORMAT = "Unsupported file format! \"{0}\" detected";

// Warning messages
public static final String ENVIRONMENT_VARIABLE_IS_NOT_SET_USING_DEFAULT = "Environment variable \"{0}\" is not set. Using default \"{1}\"...";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.cloudfoundry.multiapps.controller.core.validators.parameters;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;

import jakarta.inject.Named;
import org.apache.commons.io.FilenameUtils;
import org.apache.tika.Tika;
import org.cloudfoundry.multiapps.common.SLException;
import org.cloudfoundry.multiapps.controller.core.Messages;
import org.springframework.web.multipart.MultipartFile;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.YAMLException;

@Named
public class FileMimeTypeValidator {

private static final String APPLICATION_ZIP_MIME_TYPE = "application/zip";
private static final String APPLICATION_OCTET_STREAM_MIME_TYPE = "application/octet-stream";
private static final String TEXT_PLAIN_MIME_TYPE = "text/plain";
private static final String YAML_FILE_EXTENSION = "yaml";
private static final String EXTENSION_DESCRIPTOR_FILE_EXTENSION = "mtaext";
private static final Tika tika = new Tika();

public void validateMultipartFileMimeType(MultipartFile multipartFile) {
if (multipartFile == null || multipartFile.isEmpty()) {
throw new IllegalArgumentException(Messages.THE_PROVIDED_MULTIPART_FILE_CANNOT_BE_EMPTY);
}

try {
validateInputStreamMimeType(multipartFile.getInputStream(), multipartFile.getOriginalFilename());
} catch (IOException e) {
throw new SLException(e);
}
}

public void validateInputStreamMimeType(InputStream uploadedFileInputStream, String filename) throws IOException {
String detectedType = getFileMimeType(uploadedFileInputStream);
switch (detectedType) {
case TEXT_PLAIN_MIME_TYPE -> validateYamlFile(uploadedFileInputStream, filename);
case APPLICATION_ZIP_MIME_TYPE, APPLICATION_OCTET_STREAM_MIME_TYPE -> {
}
default -> throw new IllegalArgumentException(MessageFormat.format(Messages.UNSUPPORTED_FILE_FORMAT, detectedType));
}
}

private String getFileMimeType(InputStream uploadedFileInputStream) throws IOException {
return tika.detect(uploadedFileInputStream);
}

private void validateYamlFile(InputStream uploadedFileInputStream, String filename) {
validateTextFileExtension(filename);
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
try {
yaml.load(uploadedFileInputStream);
} catch (YAMLException e) {
throw new IllegalArgumentException(MessageFormat.format(Messages.THE_PROVIDED_0_FILE_IS_INVALID, filename), e);
}
}

private void validateTextFileExtension(String filename) {
String fileExtension = FilenameUtils.getExtension(filename);

if (!(YAML_FILE_EXTENSION.equals(fileExtension) || EXTENSION_DESCRIPTOR_FILE_EXTENSION.equals(fileExtension))) {
throw new IllegalArgumentException(MessageFormat.format(Messages.THE_PROVIDED_0_FILE_IS_INVALID, filename));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.cloudfoundry.multiapps.controller.core.validators.parameters;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import org.cloudfoundry.multiapps.controller.core.Messages;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.web.multipart.MultipartFile;

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

class FileMimeTypeValidatorTest {

private MultipartFile multipartFile;
private InputStream inputStream;
private final FileMimeTypeValidator fileMimeTypeValidator = new FileMimeTypeValidator();

@BeforeEach
void setUp() {
multipartFile = Mockito.mock(MultipartFile.class);
}

@Test
void testValidateMultipartFileMimeTypeWithNullFile() {
assertThrows(IllegalArgumentException.class, () -> {
fileMimeTypeValidator.validateMultipartFileMimeType(null);
}, Messages.THE_PROVIDED_MULTIPART_FILE_CANNOT_BE_EMPTY);
}

@Test
void testValidateMultipartFileMimeTypeWithEmptyFile() {
when(multipartFile.isEmpty()).thenReturn(true);

assertThrows(IllegalArgumentException.class, () -> {
fileMimeTypeValidator.validateMultipartFileMimeType(multipartFile);
}, Messages.THE_PROVIDED_MULTIPART_FILE_CANNOT_BE_EMPTY);
}

@Test
void testValidateMultipartFileMimeType_ValidYamlFile() throws Exception {
inputStream = new ByteArrayInputStream("test: test".getBytes());

when(multipartFile.getInputStream()).thenReturn(inputStream);
when(multipartFile.getOriginalFilename()).thenReturn("valid.yaml");

fileMimeTypeValidator.validateMultipartFileMimeType(multipartFile);
}

@Test
void testValidateMultipartFileOctetStreamMimeType() throws Exception {
when(multipartFile.getInputStream()).thenReturn(inputStream);
when(multipartFile.getOriginalFilename()).thenReturn("valid.zip");

fileMimeTypeValidator.validateMultipartFileMimeType(multipartFile);
}

@Test
void testValidateMultipartFileOApplicationZipMimeType() throws Exception {
when(multipartFile.getInputStream()).thenReturn(inputStream);
when(multipartFile.getOriginalFilename()).thenReturn("valid.zip");

fileMimeTypeValidator.validateMultipartFileMimeType(multipartFile);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.cloudfoundry.multiapps.controller.process.steps;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.text.MessageFormat;
import java.util.ArrayList;
Expand All @@ -8,10 +10,13 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;

import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.apache.commons.io.IOUtils;
import org.cloudfoundry.multiapps.common.ContentException;
import org.cloudfoundry.multiapps.common.SLException;
import org.cloudfoundry.multiapps.controller.client.util.ResilientOperationExecutor;
import org.cloudfoundry.multiapps.controller.core.validators.parameters.FileMimeTypeValidator;
import org.cloudfoundry.multiapps.controller.persistence.model.FileEntry;
import org.cloudfoundry.multiapps.controller.persistence.model.ImmutableFileEntry;
import org.cloudfoundry.multiapps.controller.persistence.services.FileStorageException;
Expand All @@ -25,19 +30,18 @@
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;

import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named("validateDeployParametersStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ValidateDeployParametersStep extends SyncFlowableStep {

private final ResilientOperationExecutor resilientOperationExecutor = new ResilientOperationExecutor();
private final ExecutorService fileStorageThreadPool;
private final FileMimeTypeValidator fileMimeTypeValidator;

@Inject
public ValidateDeployParametersStep(ExecutorService fileStorageThreadPool) {
public ValidateDeployParametersStep(ExecutorService fileStorageThreadPool, FileMimeTypeValidator fileMimeTypeValidator) {
this.fileStorageThreadPool = fileStorageThreadPool;
this.fileMimeTypeValidator = fileMimeTypeValidator;
}

@Override
Expand Down Expand Up @@ -181,6 +185,7 @@ private void mergeArchive(ProcessContext context, List<FileEntry> archivePartEnt
archivePartEntries.size(), archiveSize);
FileEntry uploadedArchive = persistArchive(archiveStreamWithName, context, archiveSize);
context.setVariable(Variables.APP_ARCHIVE_ID, uploadedArchive.getId());
validateMergedArchive(uploadedArchive);
getStepLogger().infoWithoutProgressMessage(MessageFormat.format(Messages.ARCHIVE_WITH_ID_0_AND_NAME_1_WAS_STORED,
uploadedArchive.getId(),
archiveStreamWithName.getArchiveName()));
Expand All @@ -189,13 +194,22 @@ private void mergeArchive(ProcessContext context, List<FileEntry> archivePartEnt
}
}

private void validateMergedArchive(FileEntry fileEntry) {
try (InputStream fileInputStream = fileService.openInputStream(fileEntry.getSpace(), fileEntry.getId())) {
fileMimeTypeValidator.validateInputStreamMimeType(fileInputStream,
fileEntry.getName());
} catch (FileStorageException | IOException e) {
throw new SLException(e);
}
}

private String[] getArchivePartIds(ProcessContext context) {
String archiveId = context.getRequiredVariable(Variables.APP_ARCHIVE_ID);
return archiveId.split(",");
}

private List<FileEntry> getArchivePartEntries(ProcessContext context, String[] appArchivePartsId) {
return Arrays.stream(appArchivePartsId)
return Arrays.stream(appArchivePartsId)
.map(appArchivePartId -> findFile(context, appArchivePartId))
.toList();
}
Expand Down
Loading