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

@@ -17,10 +17,13 @@ import java.util.concurrent.ExecutorService;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.RegexCharacterCollection;
import org.eclipse.hawkbit.repository.RegexCharacterCollection.RegexChar;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException;
import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException;
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.ui.artifacts.event.SoftwareModuleEvent;
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent.SoftwareModuleEventType;
@@ -46,8 +49,6 @@ public abstract class AbstractFileTransferHandler implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(AbstractFileTransferHandler.class);
private volatile boolean duplicateFile;
private volatile boolean uploadInterrupted;
private volatile String failureReason;
@@ -62,6 +63,9 @@ public abstract class AbstractFileTransferHandler implements Serializable {
protected final UINotification uiNotification;
protected static final RegexCharacterCollection ILLEGAL_FILENAME_CHARACTERS = new RegexCharacterCollection(
RegexChar.GREATER_THAN, RegexChar.LESS_THAN, RegexChar.SLASHES);
AbstractFileTransferHandler(final ArtifactManagement artifactManagement, final VaadinMessageSource i18n) {
this.artifactManagement = artifactManagement;
this.i18n = i18n;
@@ -70,25 +74,11 @@ public abstract class AbstractFileTransferHandler implements Serializable {
this.uiNotification = SpringContextHelper.getBean(UINotification.class);
}
protected boolean isDuplicateFile() {
return duplicateFile;
}
protected void setDuplicateFile() {
uploadInterrupted = true;
duplicateFile = true;
}
protected void setUploadInterrupted() {
uploadInterrupted = true;
}
protected boolean isUploadInterrupted() {
return uploadInterrupted;
}
protected void resetState() {
duplicateFile = false;
uploadInterrupted = false;
failureReason = null;
}
@@ -108,16 +98,25 @@ public abstract class AbstractFileTransferHandler implements Serializable {
new TransferArtifactToRepositoryRunnable(inputStream, fileUploadId, mimeType, UI.getCurrent()));
}
private void setFailureReason(final String failureReason) {
private void interruptUploadAndSetReason(final String failureReason) {
uploadInterrupted = true;
this.failureReason = failureReason;
}
protected void setFailureReasonUploadFailed() {
setFailureReason(i18n.getMessage("message.upload.failed"));
protected void interruptUploadDueToUploadFailed() {
interruptUploadAndSetReason(i18n.getMessage("message.upload.failed"));
}
protected void setFailureReasonFileSizeExceeded(final long maxSize) {
setFailureReason(i18n.getMessage("message.uploadedfile.size.exceeded", maxSize));
protected void interruptUploadDueToDuplicateFile() {
interruptUploadAndSetReason(i18n.getMessage("message.no.duplicateFiles"));
}
protected void interruptUploadDueToFileSizeExceeded(final long maxSize) {
interruptUploadAndSetReason(i18n.getMessage("message.uploadedfile.size.exceeded", maxSize));
}
protected void interruptUploadDueToIllegalFilename() {
interruptUploadAndSetReason(i18n.getMessage("message.uploadedfile.illegalFilename"));
}
protected boolean isFileAlreadyContainedInSoftwareModule(final FileUploadId newFileUploadId,
@@ -171,19 +170,17 @@ public abstract class AbstractFileTransferHandler implements Serializable {
new SoftwareModuleEvent(SoftwareModuleEventType.ARTIFACTS_CHANGED, fileUploadId.getSoftwareModuleId()));
}
protected void publishUploadFailedEvent(final FileUploadId fileUploadId, final String failureReason,
protected void publishUploadFailedAndFinishedEvent(final FileUploadId fileUploadId,
final Exception uploadException) {
LOG.info("Upload failed for file {} due to {}", fileUploadId,
StringUtils.isBlank(failureReason) ? uploadException.getMessage() : failureReason);
LOG.info("Upload failed for file {} due to reason: {}, exception: {}", fileUploadId, failureReason,
uploadException.getMessage());
final FileUploadProgress fileUploadProgress = new FileUploadProgress(fileUploadId,
FileUploadStatus.UPLOAD_FAILED,
StringUtils.isBlank(failureReason) ? uploadException.getMessage() : failureReason);
StringUtils.isBlank(failureReason) ? i18n.getMessage("message.upload.failed") : failureReason);
artifactUploadState.updateFileUploadProgress(fileUploadId, fileUploadProgress);
eventBus.publish(this, fileUploadProgress);
}
protected void publishUploadFailedEvent(final FileUploadId fileUploadId, final Exception uploadException) {
publishUploadFailedEvent(fileUploadId, failureReason, uploadException);
publishUploadFinishedEvent(fileUploadId);
}
protected void assertStateConsistency(final FileUploadId fileUploadId, final String filenameExtractedFromEvent) {
@@ -234,7 +231,8 @@ public abstract class AbstractFileTransferHandler implements Serializable {
UI.setCurrent(vaadinUi);
streamToRepository();
} catch (final RuntimeException e) {
publishUploadFailedEvent(fileUploadId, i18n.getMessage("message.upload.failed"), e);
interruptUploadDueToUploadFailed();
publishUploadFailedAndFinishedEvent(fileUploadId, e);
LOG.error("Failed to transfer file to repository", e);
} finally {
tryToCloseIOStream(inputStream);
@@ -250,6 +248,7 @@ public abstract class AbstractFileTransferHandler implements Serializable {
final Artifact artifact = uploadArtifact(filename).orElseThrow(ArtifactUploadFailedException::new);
if (isUploadInterrupted()) {
handleUploadFailure(artifact);
publishUploadFinishedEvent(fileUploadId);
return;
}
publishUploadSucceeded(fileUploadId, artifact.getSize());
@@ -259,8 +258,8 @@ public abstract class AbstractFileTransferHandler implements Serializable {
private Optional<Artifact> uploadArtifact(final String filename) {
try {
return Optional.ofNullable(artifactManagement.create(inputStream, fileUploadId.getSoftwareModuleId(),
filename, null, null, true, mimeType, -1));
return Optional.ofNullable(artifactManagement.create(new ArtifactUpload(inputStream,
fileUploadId.getSoftwareModuleId(), filename, null, null, true, mimeType, -1)));
} catch (final ArtifactUploadFailedException | InvalidSHA1HashException | InvalidMD5HashException e) {
LOG.error("Failed to transfer file to repository", e);
return Optional.empty();

View File

@@ -14,8 +14,8 @@ import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.RegexCharacterCollection;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.ui.artifacts.upload.FileUploadProgress.FileUploadStatus;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,12 +63,13 @@ public class FileTransferHandlerStreamVariable extends AbstractFileTransferHandl
public void streamingStarted(final StreamingStartEvent event) {
assertStateConsistency(fileUploadId, event.getFileName());
if (isFileAlreadyContainedInSoftwareModule(fileUploadId, selectedSoftwareModule)) {
if (RegexCharacterCollection.stringContainsCharacter(event.getFileName(), ILLEGAL_FILENAME_CHARACTERS)) {
LOG.info("Filename contains illegal characters {} for upload {}", fileUploadId.getFilename(), fileUploadId);
interruptUploadDueToIllegalFilename();
} else if (isFileAlreadyContainedInSoftwareModule(fileUploadId, selectedSoftwareModule)) {
LOG.info("File {} already contained in Software Module {}", fileUploadId.getFilename(),
selectedSoftwareModule);
getUploadState().updateFileUploadProgress(fileUploadId,
new FileUploadProgress(fileUploadId, FileUploadStatus.UPLOAD_FAILED));
setDuplicateFile();
interruptUploadDueToDuplicateFile();
}
}
@@ -90,10 +91,8 @@ public class FileTransferHandlerStreamVariable extends AbstractFileTransferHandl
LOG.error("Creating piped Stream failed {}.", e);
tryToCloseIOStream(outputStream);
tryToCloseIOStream(inputStream);
setFailureReasonUploadFailed();
setUploadInterrupted();
getUploadState().updateFileUploadProgress(fileUploadId,
new FileUploadProgress(fileUploadId, FileUploadStatus.UPLOAD_FAILED));
interruptUploadDueToUploadFailed();
publishUploadFailedAndFinishedEvent(fileUploadId, e);
return ByteStreams.nullOutputStream();
}
return outputStream;
@@ -120,8 +119,7 @@ public class FileTransferHandlerStreamVariable extends AbstractFileTransferHandl
if (event.getBytesReceived() > maxSize || event.getContentLength() > maxSize) {
LOG.error("User tried to upload more than was allowed ({}).", maxSize);
setFailureReasonFileSizeExceeded(maxSize);
setUploadInterrupted();
interruptUploadDueToFileSizeExceeded(maxSize);
return;
}
if (isUploadInterrupted()) {
@@ -153,14 +151,10 @@ public class FileTransferHandlerStreamVariable extends AbstractFileTransferHandl
public void streamingFailed(final StreamingErrorEvent event) {
assertStateConsistency(fileUploadId, event.getFileName());
if (isDuplicateFile()) {
publishUploadFailedEvent(fileUploadId, getI18n().getMessage("message.no.duplicateFiles"),
event.getException());
} else {
publishUploadFailedEvent(fileUploadId, event.getException());
if (!isUploadInterrupted()) {
interruptUploadDueToUploadFailed();
}
setUploadInterrupted();
publishUploadFinishedEvent(fileUploadId);
publishUploadFailedAndFinishedEvent(fileUploadId, event.getException());
}
@Override

View File

@@ -12,12 +12,11 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Optional;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.RegexCharacterCollection;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.ui.artifacts.upload.FileUploadProgress.FileUploadStatus;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,49 +70,41 @@ public class FileTransferHandlerVaadinUpload extends AbstractFileTransferHandler
public void uploadStarted(final StartedEvent event) {
// reset internal state here because instance is reused for next upload!
resetState();
this.fileUploadId = null;
assertThatOneSoftwareModuleIsSelected();
// selected software module at the time of this callback is
// considered
SoftwareModule softwareModule = null;
final Optional<Long> selectedSoftwareModuleId = getUploadState().getSelectedBaseSwModuleId();
final Long softwareModuleId = selectedSoftwareModuleId.orElse(null);
if (softwareModuleId != null) {
softwareModule = softwareModuleManagement.get(softwareModuleId).orElse(null);
}
final SoftwareModule softwareModule = getSelectedSoftwareModule();
this.fileUploadId = new FileUploadId(event.getFilename(), softwareModule);
if (getUploadState().isFileInUploadState(this.fileUploadId)) {
setFailureReasonUploadFailed();
// actual interrupt will happen a bit late so setting the below
// flag
setDuplicateFile();
interruptUploadDueToDuplicateFile();
event.getUpload().interruptUpload();
} else {
LOG.info("Uploading file {}", fileUploadId);
publishUploadStarted(fileUploadId);
if (isFileAlreadyContainedInSoftwareModule(fileUploadId, softwareModule)) {
if (RegexCharacterCollection.stringContainsCharacter(event.getFilename(), ILLEGAL_FILENAME_CHARACTERS)) {
LOG.info("Filename contains illegal characters {} for upload {}", fileUploadId.getFilename(),
fileUploadId);
interruptUploadDueToIllegalFilename();
event.getUpload().interruptUpload();
}else if (isFileAlreadyContainedInSoftwareModule(fileUploadId, softwareModule)) {
LOG.info("File {} already contained in Software Module {}", fileUploadId.getFilename(), softwareModule);
getUploadState().updateFileUploadProgress(fileUploadId,
new FileUploadProgress(fileUploadId, FileUploadStatus.UPLOAD_FAILED));
setDuplicateFile();
interruptUploadDueToDuplicateFile();
event.getUpload().interruptUpload();
}
}
}
private void assertThatOneSoftwareModuleIsSelected() {
// FileUpload button should be disabled if no SoftwareModul or more
// than one is selected!
if (getUploadState().isNoSoftwareModuleSelected()) {
throw new IllegalStateException("No SoftwareModul selected");
} else if (getUploadState().isMoreThanOneSoftwareModulesSelected()) {
private SoftwareModule getSelectedSoftwareModule() {
if (getUploadState().isMoreThanOneSoftwareModulesSelected()) {
throw new IllegalStateException("More than one SoftwareModul selected but only one is allowed");
}
final long selectedId = getUploadState().getSelectedBaseSwModuleId()
.orElseThrow(() -> new IllegalStateException("No SoftwareModul selected"));
return softwareModuleManagement.get(selectedId)
.orElseThrow(() -> new IllegalStateException("SoftwareModul with unknown ID selected"));
}
/**
@@ -141,11 +132,8 @@ public class FileTransferHandlerVaadinUpload extends AbstractFileTransferHandler
LOG.error("Creating piped Stream failed {}.", e);
tryToCloseIOStream(outputStream);
tryToCloseIOStream(inputStream);
setFailureReasonUploadFailed();
setUploadInterrupted();
getUploadState().updateFileUploadProgress(fileUploadId,
new FileUploadProgress(fileUploadId, FileUploadStatus.UPLOAD_FAILED));
uiNotification.displayWarning("try again");
interruptUploadDueToUploadFailed();
publishUploadFailedAndFinishedEvent(fileUploadId, e);
return ByteStreams.nullOutputStream();
}
return outputStream;
@@ -160,8 +148,7 @@ public class FileTransferHandlerVaadinUpload extends AbstractFileTransferHandler
public void updateProgress(final long readBytes, final long contentLength) {
if (readBytes > maxSize || contentLength > maxSize) {
LOG.error("User tried to upload more than was allowed ({}).", maxSize);
setFailureReasonFileSizeExceeded(maxSize);
setUploadInterrupted();
interruptUploadDueToFileSizeExceeded(maxSize);
return;
}
if (isUploadInterrupted()) {
@@ -209,14 +196,15 @@ public class FileTransferHandlerVaadinUpload extends AbstractFileTransferHandler
public void uploadFailed(final FailedEvent event) {
assertStateConsistency(fileUploadId, event.getFilename());
if (isDuplicateFile()) {
publishUploadFailedEvent(fileUploadId, getI18n().getMessage("message.no.duplicateFiles"),
event.getReason());
} else {
publishUploadFailedEvent(fileUploadId, event.getReason());
if (!isUploadInterrupted()) {
interruptUploadDueToUploadFailed();
}
setUploadInterrupted();
publishUploadFinishedEvent(fileUploadId);
publishUploadFailedAndFinishedEvent(fileUploadId, event.getReason());
}
@Override
protected void resetState() {
super.resetState();
this.fileUploadId = null;
}
}

View File

@@ -190,8 +190,7 @@ public class UploadDropAreaLayout extends AbstractComponent {
}
}
if (duplicateFound) {
uiNotification.displayValidationError(
i18n.getMessage("message.no.duplicateFiles"));
uiNotification.displayValidationError(i18n.getMessage("message.no.duplicateFiles"));
}
}

View File

@@ -435,6 +435,7 @@ message.swModule.deleted = {0} Software Module(s) deleted
message.error.swModule.notDeleted = Upload is running for Software Module(s)
message.upload.failed = Streaming Failed
message.uploadedfile.size.exceeded = File size exceeded. Allowed size {0} bytes
message.uploadedfile.illegalFilename = Filename contains illegal characters
message.artifact.deleted = Artifact with file {0} deleted successfully
@@ -672,3 +673,9 @@ message.confirm.delete.entity = Are you sure you want to delete {0} {1}{2}?
caption.entity.assign.action.confirmbox = Confirm Assignment
message.confirm.assign.entity = Are you sure you want to assign distribution {0} to {1} {2}?
message.confirm.assign.multiple.entities = Are you sure you want to assign {0} {1} to distribution {2}?
# character descriptions
character.whitespace = whitespace
character.digits = digits
character.quotationMarks = quotation marks
character.slashes = slashes