Fix artifact filename validation (#770)

* use validated ArtifactUpload object when creating a new artifact

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* rename method

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* add regular expression classes

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* add filename validation to UI upload button

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* move filename validation to uploadStarted

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* clean up code for UI error handling during artifact upload, assert
filename validation

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* update visibilities

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* clean up code

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* clean up code

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* change RegexChar class to enum and use i18n

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* typo, use StringBuilder

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* typo

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* use dedicated class for collections of regular expression characters

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* remove Optional, remove stringBuilder

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* PR findings

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* make regex validation method static

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* use WhiteListType.NONE for filename validation via mgmt-api

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>
This commit is contained in:
Stefan Klotz
2018-12-17 10:17:46 +01:00
committed by Dominic Schabel
parent a2c1e5f132
commit 20d84a10eb
22 changed files with 536 additions and 225 deletions

View File

@@ -32,6 +32,7 @@ import org.apache.commons.lang3.RandomUtils;
import org.eclipse.hawkbit.ddi.rest.resource.DdiArtifactDownloadTest.DownloadTestConfiguration;
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
@@ -84,8 +85,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// create artifact
final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
final Artifact artifact = artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize));
// no artifact available
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/123455",
@@ -171,8 +172,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// create artifact
final int artifactSize = 5 * 1024 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
final Artifact artifact = artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize));
// download fails as artifact is not yet assigned
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
@@ -211,8 +212,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// create artifact
final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "file1",
false, artifactSize);
final Artifact artifact = artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "file1", false, artifactSize));
// download
final MvcResult result = mvc.perform(get(
@@ -241,8 +242,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// create artifact
final byte random[] = RandomUtils.nextBytes(resultLength);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "file1",
false, resultLength);
final Artifact artifact = artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "file1", false, resultLength));
assertThat(random.length).isEqualTo(resultLength);

View File

@@ -40,6 +40,7 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
import org.eclipse.hawkbit.repository.model.Target;
@@ -112,10 +113,10 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1",
false, artifactSize);
final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds),
"test1.signature", false, artifactSize);
final Artifact artifact = artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "test1", false, artifactSize));
final Artifact artifactSignature = artifactManagement.create(new ArtifactUpload(
new ByteArrayInputStream(random), getOsModule(ds), "test1.signature", false, artifactSize));
final Target savedTarget = testdataFactory.createTarget("4712");
@@ -270,10 +271,10 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1",
false, artifactSize);
final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds),
"test1.signature", false, artifactSize);
final Artifact artifact = artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "test1", false, artifactSize));
final Artifact artifactSignature = artifactManagement.create(new ArtifactUpload(
new ByteArrayInputStream(random), getOsModule(ds), "test1.signature", false, artifactSize));
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(getOsModule(ds))
.key(visibleMetadataOsKey).value(visibleMetadataOsValue).targetVisible(true));
@@ -394,10 +395,10 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1",
false, artifactSize);
final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds),
"test1.signature", false, artifactSize);
final Artifact artifact = artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "test1", false, artifactSize));
final Artifact artifactSignature = artifactManagement.create(new ArtifactUpload(
new ByteArrayInputStream(random), getOsModule(ds), "test1.signature", false, artifactSize));
final Target savedTarget = testdataFactory.createTarget("4712");

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.slf4j.Logger;
@@ -81,9 +82,9 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
}
try (InputStream in = file.getInputStream()) {
final Artifact result = artifactManagement.create(in, softwareModuleId, fileName,
final Artifact result = artifactManagement.create(new ArtifactUpload(in, softwareModuleId, fileName,
md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(), false,
file.getContentType(), file.getSize());
file.getContentType(), file.getSize()));
final MgmtArtifact reponse = MgmtSoftwareModuleMapper.toResponse(result);
MgmtSoftwareModuleMapper.addLinks(result, reponse);

View File

@@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@@ -38,6 +39,7 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
@@ -204,6 +206,21 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
}
@Test
@Description("Verifies that artifact with invalid filename cannot be uploaded to prevent cross site scripting.")
public void uploadArtifactFailsIfFilenameInvalide() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModule("quota", "quota");
final String illegalFilename = "<img src=ernw onerror=alert(1)>.xml";
final byte[] randomBytes = randomBytes(5 * 1024);
final MockMultipartFile file = new MockMultipartFile("file", illegalFilename, null, randomBytes);
mvc.perform(fileUpload("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).file(file)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message", containsString("The filename might contain unsafe HTML code.")));
}
private void assertArtifact(final SoftwareModule sm, final byte[] random) throws IOException {
// check result in db...
@@ -426,10 +443,10 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final int artifactSize = 5 * 1024;
final byte random[] = randomBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false, artifactSize);
final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2",
false, artifactSize);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
final Artifact artifact2 = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file2", false, artifactSize));
downloadAndVerify(sm, random, artifact);
downloadAndVerify(sm, random, artifact2);
@@ -458,8 +475,8 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final int artifactSize = 5 * 1024;
final byte random[] = randomBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false, artifactSize);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
// perform test
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}", sm.getId(), artifact.getId())
@@ -485,10 +502,10 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final int artifactSize = 5 * 1024;
final byte random[] = randomBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false, artifactSize);
final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2",
false, artifactSize);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
final Artifact artifact2 = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file2", false, artifactSize));
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -527,7 +544,8 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// SM does not exist
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize);
artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
mvc.perform(get("/rest/v1/softwaremodules/1234567890/artifacts")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
@@ -838,7 +856,8 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize);
artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
assertThat(softwareModuleManagement.findAll(PAGE)).as("Softwaremoudle size is wrong").hasSize(1);
assertThat(artifactManagement.count()).isEqualTo(1);
@@ -861,7 +880,8 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final Long appTypeSmId = ds1.findFirstModuleByType(appType).get().getId();
artifactManagement.create(new ByteArrayInputStream(random), appTypeSmId, "file1", false, artifactSize);
artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), appTypeSmId, "file1", false, artifactSize));
assertThat(softwareModuleManagement.count()).isEqualTo(3);
assertThat(artifactManagement.count()).isEqualTo(1);
@@ -889,9 +909,10 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
// Create 2 artifacts
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false, artifactSize);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2", false, artifactSize);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file2", false, artifactSize));
// check repo before delete
assertThat(softwareModuleManagement.findAll(PAGE)).hasSize(1);

View File

@@ -29,6 +29,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
@@ -129,8 +130,10 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
set.getModules().forEach(module -> {
final byte random[] = RandomStringUtils.random(5).getBytes();
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "binary.tgz", false, 0);
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "file.signature", false, 0);
artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "binary.tgz", false, 0));
artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "file.signature", false, 0));
});
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
@@ -250,8 +253,10 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
set.getModules().forEach(module -> {
final byte random[] = RandomStringUtils.random(5).getBytes();
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "binary.tgz", false, 0);
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "file.signature", false, 0);
artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "binary.tgz", false, 0));
artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "file.signature", false, 0));
});
softwareModuleManagement.createMetaData(
@@ -426,7 +431,8 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final SoftwareModule module = (SoftwareModule) set.getModules().toArray()[0];
final byte random[] = RandomStringUtils.random(5).getBytes();
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "binaryFile", false, 0);
artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "binaryFile", false, 0));
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
deploymentManagement.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType()));

View File

@@ -26,6 +26,7 @@ import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration;
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -178,7 +179,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
distributionSet.getModules().forEach(module -> {
final byte[] random = RandomStringUtils.random(5).getBytes();
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "file1", false, 0);
artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "file1", false, 0));
softwareModuleManagement.update(entityFactory.softwareModule().update(module.getId())
.description("Description of the software module"));
});

View File

@@ -31,6 +31,7 @@ import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.rest.documentation.AbstractApiRestDocumentation;
import org.eclipse.hawkbit.rest.documentation.ApiModelPropertiesGeneric;
@@ -244,7 +245,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
final byte random[] = RandomStringUtils.random(5).getBytes();
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0);
artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
mockMvc.perform(
get(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts", sm.getId()))
@@ -333,8 +334,8 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
final byte random[] = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false, 0);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
mockMvc.perform(delete(
MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts/{artifactId}",
@@ -352,8 +353,8 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
final byte random[] = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false, 0);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
mockMvc.perform(
get(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts/{artifactId}",
@@ -387,8 +388,8 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
final byte random[] = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false, 0);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
mockMvc.perform(get(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING
+ "/{softwareModuleId}/artifacts/{artifactId}/download", sm.getId(), artifact.getId())