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:
committed by
Dominic Schabel
parent
a2c1e5f132
commit
20d84a10eb
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user