Artifact Encryption plug point (#1202)
* added ArtifactEncryption interface, injected it into SM creation UI module, added encryption metadata key generation upon SM creation, used encryptor during file upload Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * add default artifact encryption implementation based on gcm aes algorithm Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * changed ArtifactEncryptor interface to manage encryption secrets by itself Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * cleaned up stale code, fixed sonar Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * fixed software module encryption within transaction Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * added artifact encryption secrets store Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * extended ArtifactEncryption interface to allow decryption, secrets store provides removeSecret, added missing javadocs Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * intriduced DbArtifact interface, use EncryptionAwareDbArtifact for artifact decryption during download Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * introduced ArtifactEncryptionService to minimize duplications and unneccessary dependency injections Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * declared ArtifactEncryptionService as a bean Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * added persistant encryption flag to software module Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * further adptations for encryption flag persistence Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * added ArtifactEncryptionException, fixed encryption check in UI Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * added encryption error handling Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * added encrypted flag to DDI/DMF, adapted exception handling Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * adapted rest docs Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * Add test to verify artifact encryption is not given by default Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io> * Add isEncrypted() to toString() of JpaSoftwareModule, fix typos Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io> * Fix sql migration scripts Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io> * Calculate encrypted artifact size by subtract encryption size overhead Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io> * publish upload failed without waiting for interuption during UI file upload Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> * upgraded cron utils to 9.1.6 Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io> Co-authored-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io>
This commit is contained in:
@@ -8,15 +8,13 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.repository.model;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Database representation of artifact.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractDbArtifact {
|
||||
public abstract class AbstractDbArtifact implements DbArtifact {
|
||||
|
||||
private final String artifactId;
|
||||
private final long size;
|
||||
@@ -34,46 +32,33 @@ public abstract class AbstractDbArtifact {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ID of the artifact
|
||||
*/
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return hashes of the artifact
|
||||
*/
|
||||
@Override
|
||||
public DbArtifactHash getHashes() {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hashes of the artifact
|
||||
*
|
||||
* @param hashes
|
||||
* artifact hashes
|
||||
*/
|
||||
public void setHashes(final DbArtifactHash hashes) {
|
||||
this.hashes = hashes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return site of the artifact in bytes
|
||||
*/
|
||||
@Override
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return content-type if known by the repository or <code>null</code>
|
||||
*/
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link InputStream} on this artifact. Caller has to take care of
|
||||
* closing the stream. Repeatable calls open a new {@link InputStream}.
|
||||
*
|
||||
* @return {@link InputStream} to read from artifact.
|
||||
*/
|
||||
public abstract InputStream getFileInputStream();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.repository.model;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Interface definition for artifact binary.
|
||||
*/
|
||||
public interface DbArtifact {
|
||||
|
||||
/**
|
||||
* @return ID of the artifact
|
||||
*/
|
||||
String getArtifactId();
|
||||
|
||||
/**
|
||||
* @return hashes of the artifact
|
||||
*/
|
||||
DbArtifactHash getHashes();
|
||||
|
||||
/**
|
||||
* @return size of the artifact in bytes
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
/**
|
||||
* @return content-type if known by the repository or <code>null</code>
|
||||
*/
|
||||
String getContentType();
|
||||
|
||||
/**
|
||||
* Creates an {@link InputStream} on this artifact. Caller has to take care of
|
||||
* closing the stream. Repeatable calls open a new {@link InputStream}.
|
||||
*
|
||||
* @return {@link InputStream} to read from artifact.
|
||||
*/
|
||||
InputStream getFileInputStream();
|
||||
}
|
||||
@@ -97,6 +97,18 @@ public enum SpServerError {
|
||||
SP_ARTIFACT_UPLOAD_FAILED("hawkbit.server.error.artifact.uploadFailed",
|
||||
"Upload of artifact failed with internal server error."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED("hawkbit.server.error.artifact.encryptionNotSupported",
|
||||
"Artifact encryption is not supported."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_ENCRYPTION_FAILED("hawkbit.server.error.artifact.encryptionFailed",
|
||||
"Artifact encryption operation failed."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -161,15 +173,15 @@ public enum SpServerError {
|
||||
"Storage quota will be exceeded if file is uploaded."),
|
||||
|
||||
/**
|
||||
* error message, which describes that the action can not be canceled cause the
|
||||
* action is inactive.
|
||||
* error message, which describes that the action can not be canceled cause
|
||||
* the action is inactive.
|
||||
*/
|
||||
SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable",
|
||||
"Only active actions which are in status pending are cancelable."),
|
||||
|
||||
/**
|
||||
* error message, which describes that the action can not be force quit cause
|
||||
* the action is inactive.
|
||||
* error message, which describes that the action can not be force quit
|
||||
* cause the action is inactive.
|
||||
*/
|
||||
SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable",
|
||||
"Only active actions which are in status pending can be force quit."),
|
||||
@@ -250,7 +262,8 @@ public enum SpServerError {
|
||||
"Information for schedule, duration or timezone is missing; or there is no valid maintenance window available in future."),
|
||||
|
||||
/**
|
||||
* Error message informing that the action type for auto-assignment is invalid.
|
||||
* Error message informing that the action type for auto-assignment is
|
||||
* invalid.
|
||||
*/
|
||||
SP_AUTO_ASSIGN_ACTION_TYPE_INVALID("hawkbit.server.error.repo.invalidAutoAssignActionType",
|
||||
"The given action type for auto-assignment is invalid: allowed values are ['forced', 'soft', 'downloadonly']"),
|
||||
|
||||
@@ -8,6 +8,18 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.api.ApiType;
|
||||
import org.eclipse.hawkbit.api.ArtifactUrl;
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||
@@ -55,18 +67,6 @@ import org.springframework.context.event.EventListener;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
|
||||
|
||||
/**
|
||||
* {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and
|
||||
* delegate the messages to a {@link AmqpMessageSenderService}.
|
||||
@@ -105,8 +105,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
* @param targetManagement
|
||||
* to access target information
|
||||
* @param serviceMatcher
|
||||
* to check in cluster case if the message is from the same cluster
|
||||
* node
|
||||
* to check in cluster case if the message is from the same
|
||||
* cluster node
|
||||
* @param distributionSetManagement
|
||||
* to retrieve modules
|
||||
*/
|
||||
@@ -129,8 +129,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send a message to a RabbitMQ Exchange after the Distribution set
|
||||
* has been assign to a Target.
|
||||
* Method to send a message to a RabbitMQ Exchange after the Distribution
|
||||
* set has been assign to a Target.
|
||||
*
|
||||
* @param assignedEvent
|
||||
* the object to be send.
|
||||
@@ -257,9 +257,9 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
|
||||
/**
|
||||
* Method to get the type of event depending on whether the action is a
|
||||
* DOWNLOAD_ONLY action or if it has a valid maintenance window available or not
|
||||
* based on defined maintenance schedule. In case of no maintenance schedule or
|
||||
* if there is a valid window available, the topic
|
||||
* DOWNLOAD_ONLY action or if it has a valid maintenance window available or
|
||||
* not based on defined maintenance schedule. In case of no maintenance
|
||||
* schedule or if there is a valid window available, the topic
|
||||
* {@link EventTopic#DOWNLOAD_AND_INSTALL} is returned else
|
||||
* {@link EventTopic#DOWNLOAD} is returned.
|
||||
*
|
||||
@@ -275,8 +275,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the {@link EventTopic} for the given {@link Action}, depending on
|
||||
* its action type.
|
||||
* Determines the {@link EventTopic} for the given {@link Action}, depending
|
||||
* on its action type.
|
||||
*
|
||||
* @param action
|
||||
* to obtain the corresponding {@link EventTopic} for
|
||||
@@ -291,8 +291,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send a message to a RabbitMQ Exchange after the assignment of the
|
||||
* Distribution set to a Target has been canceled.
|
||||
* Method to send a message to a RabbitMQ Exchange after the assignment of
|
||||
* the Distribution set to a Target has been canceled.
|
||||
*
|
||||
* @param cancelEvent
|
||||
* that is to be converted to a DMF message
|
||||
@@ -315,11 +315,12 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send a message to a RabbitMQ Exchange after a Target was deleted.
|
||||
* Method to send a message to a RabbitMQ Exchange after a Target was
|
||||
* deleted.
|
||||
*
|
||||
* @param deleteEvent
|
||||
* the TargetDeletedEvent which holds the necessary data for sending
|
||||
* a target delete message.
|
||||
* the TargetDeletedEvent which holds the necessary data for
|
||||
* sending a target delete message.
|
||||
*/
|
||||
@EventListener(classes = TargetDeletedEvent.class)
|
||||
protected void targetDelete(final TargetDeletedEvent deleteEvent) {
|
||||
@@ -345,7 +346,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
return;
|
||||
}
|
||||
|
||||
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = createDownloadAndUpdateRequest(target, action.getId(), modules);
|
||||
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = createDownloadAndUpdateRequest(target,
|
||||
action.getId(), modules);
|
||||
final Message message = getMessageConverter().toMessage(downloadAndUpdateRequest,
|
||||
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), getEventTypeForTarget(action)));
|
||||
amqpSenderService.sendMessage(message, targetAddress);
|
||||
@@ -445,6 +447,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
amqpSoftwareModule.setModuleId(entry.getKey().getId());
|
||||
amqpSoftwareModule.setModuleType(entry.getKey().getType().getKey());
|
||||
amqpSoftwareModule.setModuleVersion(entry.getKey().getVersion());
|
||||
amqpSoftwareModule.setEncrypted(entry.getKey().isEncrypted() ? Boolean.TRUE : null);
|
||||
amqpSoftwareModule.setArtifacts(convertArtifacts(target, entry.getKey().getArtifacts()));
|
||||
|
||||
if (!CollectionUtils.isEmpty(entry.getValue())) {
|
||||
|
||||
@@ -150,8 +150,8 @@ public class AmqpControllerAuthenticationTest {
|
||||
|
||||
authenticationManager.postConstruct();
|
||||
|
||||
testArtifact = new JpaArtifact(SHA1, "afilename", new JpaSoftwareModule(
|
||||
new JpaSoftwareModuleType("a key", "a name", null, 1), "a name", null, null, null));
|
||||
testArtifact = new JpaArtifact(SHA1, "afilename",
|
||||
new JpaSoftwareModule(new JpaSoftwareModuleType("a key", "a name", null, 1), "a name", "a version"));
|
||||
testArtifact.setId(1L);
|
||||
|
||||
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
|
||||
@@ -260,7 +260,7 @@ public class AmqpControllerAuthenticationTest {
|
||||
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
|
||||
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ public class DmfSoftwareModule {
|
||||
@JsonProperty
|
||||
private String moduleVersion;
|
||||
@JsonProperty
|
||||
private Boolean encrypted;
|
||||
@JsonProperty
|
||||
private List<DmfArtifact> artifacts;
|
||||
@JsonProperty
|
||||
private List<DmfMetadata> metadata;
|
||||
@@ -83,11 +85,18 @@ public class DmfSoftwareModule {
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean getEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
public void setEncrypted(final Boolean encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"DmfSoftwareModule [moduleId=%d, moduleType='%s', moduleVersion='%s', artifacts=%s, metadata=%s]",
|
||||
moduleId, moduleType, moduleVersion, artifacts, metadata);
|
||||
"DmfSoftwareModule [moduleId=%d, moduleType='%s', moduleVersion='%s', encrypted='%s' artifacts=%s, metadata=%s]",
|
||||
moduleId, moduleType, moduleVersion, encrypted, artifacts, metadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionFailedException;
|
||||
|
||||
/**
|
||||
* Interface definition for artifact encryption.
|
||||
*
|
||||
*/
|
||||
public interface ArtifactEncryption {
|
||||
|
||||
/**
|
||||
* Defines the required secret keys for particular encryption algorithm.
|
||||
*
|
||||
* @return list of required secret keys
|
||||
*/
|
||||
Set<String> requiredSecretKeys();
|
||||
|
||||
/**
|
||||
* Generates required secrets key/value pairs.
|
||||
*
|
||||
* @return secrets key/value pairs
|
||||
* @throws ArtifactEncryptionFailedException
|
||||
*/
|
||||
Map<String, String> generateSecrets();
|
||||
|
||||
/**
|
||||
* Encrypts artifact stream with provided secrets.
|
||||
*
|
||||
* @param secrets
|
||||
* secrets key/value pairs to be used for encryption
|
||||
* @param stream
|
||||
* artifact stream to encrypt
|
||||
* @return encrypted input stream
|
||||
* @throws ArtifactEncryptionFailedException
|
||||
*/
|
||||
InputStream encryptStream(final Map<String, String> secrets, final InputStream stream);
|
||||
|
||||
/**
|
||||
* Decrypts encrypted artifact stream based on provided secrets.
|
||||
*
|
||||
* @param secrets
|
||||
* secrets key/value pairs to be used for decryption
|
||||
* @param stream
|
||||
* artifact stream to decrypt
|
||||
* @return decrypted input stream
|
||||
* @throws ArtifactEncryptionFailedException
|
||||
*/
|
||||
InputStream decryptStream(final Map<String, String> secrets, final InputStream stream);
|
||||
|
||||
/**
|
||||
* Size of the underlying encryption algorithm overhead in bytes
|
||||
*
|
||||
* @return encryption overhead in byte
|
||||
*/
|
||||
int encryptionSizeOverhead();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
|
||||
/**
|
||||
* Interface definition for artifact encryption secrets store.
|
||||
*
|
||||
*/
|
||||
public interface ArtifactEncryptionSecretsStore {
|
||||
|
||||
/**
|
||||
* Adds secret key/value pair associated with particular
|
||||
* {@link SoftwareModule} id to the store.
|
||||
*
|
||||
* @param softwareModuleId
|
||||
* {@link SoftwareModule} id associated with the secret
|
||||
* @param secretKey
|
||||
* key of the secret
|
||||
* @param secretValue
|
||||
* value of the secret
|
||||
*/
|
||||
void addSecret(final long softwareModuleId, final String secretKey, final String secretValue);
|
||||
|
||||
/**
|
||||
* Checks if secret is present for particular {@link SoftwareModule} id and
|
||||
* key in the store.
|
||||
*
|
||||
* @param softwareModuleId
|
||||
* {@link SoftwareModule} id associated with the secret
|
||||
* @param secretKey
|
||||
* key of the secret
|
||||
*/
|
||||
boolean secretExists(final long softwareModuleId, final String secretKey);
|
||||
|
||||
/**
|
||||
* Retrieves secret value associated with particular {@link SoftwareModule}
|
||||
* id and key from the store.
|
||||
*
|
||||
* @param softwareModuleId
|
||||
* {@link SoftwareModule} id associated with the secret
|
||||
* @param secretKey
|
||||
* key of the secret
|
||||
*/
|
||||
Optional<String> getSecret(final long softwareModuleId, final String secretKey);
|
||||
|
||||
/**
|
||||
* Removes secret key/value pair associated with particular
|
||||
* {@link SoftwareModule} id from the store.
|
||||
*
|
||||
* @param softwareModuleId
|
||||
* {@link SoftwareModule} id associated with the secret
|
||||
* @param secretKey
|
||||
* key of the secret
|
||||
*/
|
||||
void removeSecret(final long softwareModuleId, final String secretKey);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Service responsible for encryption operations.
|
||||
*
|
||||
*/
|
||||
public final class ArtifactEncryptionService {
|
||||
|
||||
private static final ArtifactEncryptionService SINGLETON = new ArtifactEncryptionService();
|
||||
|
||||
@Autowired(required = false)
|
||||
private ArtifactEncryption artifactEncryption;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ArtifactEncryptionSecretsStore artifactEncryptionSecretsStore;
|
||||
|
||||
private ArtifactEncryptionService() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the artifact encryption service singleton instance
|
||||
*/
|
||||
public static ArtifactEncryptionService getInstance() {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if required encryption and secrets store beans are present.
|
||||
*
|
||||
* @return if encryption is supported
|
||||
*/
|
||||
public boolean isEncryptionSupported() {
|
||||
return artifactEncryption != null && artifactEncryptionSecretsStore != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates encryption secrets and saves them in secret store by software
|
||||
* module id reference.
|
||||
*
|
||||
* @param smId
|
||||
* software module id
|
||||
*/
|
||||
public void addSoftwareModuleEncryptionSecrets(final long smId) {
|
||||
if (!isEncryptionSupported()) {
|
||||
throw new ArtifactEncryptionUnsupportedException("Encryption secrets generation is not supported.");
|
||||
}
|
||||
|
||||
final Map<String, String> secrets = artifactEncryption.generateSecrets();
|
||||
secrets.forEach((key, value) -> artifactEncryptionSecretsStore.addSecret(smId, key, value));
|
||||
// we want to clear secrets from memory as soon as possible
|
||||
secrets.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts artifact stream using the keys retrieved from secrets store by
|
||||
* software module id reference.
|
||||
*
|
||||
* @param smId
|
||||
* software module id
|
||||
* @param artifactStream
|
||||
* artifact stream to encrypt
|
||||
* @return encrypted input stream
|
||||
*/
|
||||
public InputStream encryptSoftwareModuleArtifact(final long smId, final InputStream artifactStream) {
|
||||
if (!isEncryptionSupported()) {
|
||||
throw new ArtifactEncryptionUnsupportedException("Artifact encryption is not supported.");
|
||||
}
|
||||
|
||||
return artifactEncryption.encryptStream(getSoftwareModuleEncryptionSecrets(smId), artifactStream);
|
||||
}
|
||||
|
||||
private Map<String, String> getSoftwareModuleEncryptionSecrets(final long smId) {
|
||||
final Set<String> requiredSecretsKeys = artifactEncryption.requiredSecretKeys();
|
||||
final Map<String, String> requiredSecrets = new HashMap<>();
|
||||
for (final String requiredSecretsKey : requiredSecretsKeys) {
|
||||
final Optional<String> requiredSecretsValue = artifactEncryptionSecretsStore.getSecret(smId,
|
||||
requiredSecretsKey);
|
||||
requiredSecretsValue.ifPresent(secretValue -> requiredSecrets.put(requiredSecretsKey, secretValue));
|
||||
}
|
||||
|
||||
return requiredSecrets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts artifact stream using the keys retrieved from secrets store by
|
||||
* software module id reference.
|
||||
*
|
||||
* @param smId
|
||||
* software module id
|
||||
* @param encryptedArtifactStream
|
||||
* artifact stream to decrypt
|
||||
* @return decrypted input stream
|
||||
*/
|
||||
public InputStream decryptSoftwareModuleArtifact(final long smId, final InputStream encryptedArtifactStream) {
|
||||
if (!isEncryptionSupported()) {
|
||||
throw new ArtifactEncryptionUnsupportedException("Artifact decryption is not supported.");
|
||||
}
|
||||
|
||||
return artifactEncryption.decryptStream(getSoftwareModuleEncryptionSecrets(smId), encryptedArtifactStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of the underlying encryption algorithm overhead in bytes
|
||||
*
|
||||
* @return encryption overhead in byte
|
||||
*/
|
||||
public int encryptionSizeOverhead() {
|
||||
return artifactEncryption.encryptionSizeOverhead();
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
|
||||
@@ -69,8 +69,8 @@ public interface ArtifactManagement {
|
||||
|
||||
/**
|
||||
* Garbage collects artifact binaries if only referenced by given
|
||||
* {@link SoftwareModule#getId()} or {@link SoftwareModule}'s that are marked
|
||||
* as deleted.
|
||||
* {@link SoftwareModule#getId()} or {@link SoftwareModule}'s that are
|
||||
* marked as deleted.
|
||||
*
|
||||
*
|
||||
* @param artifactSha1Hash
|
||||
@@ -161,15 +161,20 @@ public interface ArtifactManagement {
|
||||
Page<Artifact> findBySoftwareModule(@NotNull Pageable pageReq, long swId);
|
||||
|
||||
/**
|
||||
* Loads {@link AbstractDbArtifact} from store for given {@link Artifact}.
|
||||
* Loads {@link DbArtifact} from store for given {@link Artifact}.
|
||||
*
|
||||
* @param sha1Hash
|
||||
* to search for
|
||||
* @return loaded {@link AbstractDbArtifact}
|
||||
* @param softwareModuleId
|
||||
* software module id.
|
||||
* @param isEncrypted
|
||||
* flag to indicate if artifact is encrypted.
|
||||
* @return loaded {@link DbArtifact}
|
||||
*
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DOWNLOAD_ARTIFACT + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
Optional<AbstractDbArtifact> loadArtifactBinary(@NotEmpty String sha1Hash);
|
||||
Optional<DbArtifact> loadArtifactBinary(@NotEmpty String sha1Hash, long softwareModuleId,
|
||||
final boolean isEncrypted);
|
||||
|
||||
}
|
||||
|
||||
@@ -70,6 +70,13 @@ public interface SoftwareModuleCreate {
|
||||
return type(Optional.ofNullable(type).map(SoftwareModuleType::getKey).orElse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encrypted
|
||||
* if should be encrypted
|
||||
* @return updated builder instance
|
||||
*/
|
||||
SoftwareModuleCreate encrypted(boolean encrypted);
|
||||
|
||||
/**
|
||||
* @return peek on current state of {@link SoftwareModule} in the builder
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Exception being thrown in case of error while generating encryption secrets,
|
||||
* encrypting or decrypting artifacts.
|
||||
*/
|
||||
public final class ArtifactEncryptionFailedException extends AbstractServerRtException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final EncryptionOperation encryptionOperation;
|
||||
|
||||
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation) {
|
||||
this(encryptionOperation, null, null);
|
||||
}
|
||||
|
||||
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message) {
|
||||
this(encryptionOperation, message, null);
|
||||
}
|
||||
|
||||
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message,
|
||||
final Throwable cause) {
|
||||
super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, cause);
|
||||
this.encryptionOperation = encryptionOperation;
|
||||
}
|
||||
|
||||
public EncryptionOperation getEncryptionOperation() {
|
||||
return encryptionOperation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption operation that caused the exception.
|
||||
*/
|
||||
public enum EncryptionOperation {
|
||||
GENERATE_SECRETS, ENCRYPT, DECRYPT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Exception being thrown when artifact encryption is not supported
|
||||
*/
|
||||
public final class ArtifactEncryptionUnsupportedException extends AbstractServerRtException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public ArtifactEncryptionUnsupportedException() {
|
||||
super(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message
|
||||
* of the error
|
||||
*/
|
||||
public ArtifactEncryptionUnsupportedException(final String message) {
|
||||
super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
@@ -67,4 +67,9 @@ public interface SoftwareModule extends NamedVersionedEntity {
|
||||
*/
|
||||
List<DistributionSet> getAssignedTo();
|
||||
|
||||
/**
|
||||
* @return {@code true} if this software module is marked as encrypted
|
||||
* otherwise {@code false}
|
||||
*/
|
||||
boolean isEncrypted();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
|
||||
/**
|
||||
* Test class to verify that no {@link ArtifactEncryptionService} required beans
|
||||
* are loaded and therefore the encryption support is not given.
|
||||
*/
|
||||
@Feature("Unit Tests - Repository")
|
||||
@Story("Artifact Encryption Service")
|
||||
class ArtifactEncryptionServiceTest {
|
||||
|
||||
@Test
|
||||
@Description("Verify that no artifact encryption support is given")
|
||||
void verifyNoArtifactEncryptionSupport() {
|
||||
final ArtifactEncryptionService artifactEncryptionService = ArtifactEncryptionService.getInstance();
|
||||
|
||||
assertThat(artifactEncryptionService.isEncryptionSupported()).isFalse();
|
||||
assertThatExceptionOfType(ArtifactEncryptionUnsupportedException.class)
|
||||
.isThrownBy(() -> artifactEncryptionService.addSoftwareModuleEncryptionSecrets(1L));
|
||||
assertThatExceptionOfType(ArtifactEncryptionUnsupportedException.class)
|
||||
.isThrownBy(() -> artifactEncryptionService.encryptSoftwareModuleArtifact(1L, null));
|
||||
assertThatExceptionOfType(ArtifactEncryptionUnsupportedException.class)
|
||||
.isThrownBy(() -> artifactEncryptionService.decryptSoftwareModuleArtifact(1L, null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
|
||||
/**
|
||||
* {@link DbArtifact} implementation that decrypts the underlying artifact
|
||||
* binary input stream.
|
||||
*/
|
||||
public class EncryptionAwareDbArtifact implements DbArtifact {
|
||||
|
||||
private final DbArtifact encryptedDbArtifact;
|
||||
private final UnaryOperator<InputStream> decryptionFunction;
|
||||
private final int encryptionOverhead;
|
||||
|
||||
public EncryptionAwareDbArtifact(final DbArtifact encryptedDbArtifact,
|
||||
final UnaryOperator<InputStream> decryptionFunction) {
|
||||
this.encryptedDbArtifact = encryptedDbArtifact;
|
||||
this.decryptionFunction = decryptionFunction;
|
||||
this.encryptionOverhead = 0;
|
||||
}
|
||||
|
||||
public EncryptionAwareDbArtifact(final DbArtifact encryptedDbArtifact,
|
||||
final UnaryOperator<InputStream> decryptionFunction, final int encryptionOverhead) {
|
||||
this.encryptedDbArtifact = encryptedDbArtifact;
|
||||
this.decryptionFunction = decryptionFunction;
|
||||
this.encryptionOverhead = encryptionOverhead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return encryptedDbArtifact.getArtifactId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbArtifactHash getHashes() {
|
||||
return encryptedDbArtifact.getHashes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return encryptedDbArtifact.getSize() - encryptionOverhead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return encryptedDbArtifact.getContentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getFileInputStream() {
|
||||
return decryptionFunction.apply(encryptedDbArtifact.getFileInputStream());
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@ import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException;
|
||||
import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
|
||||
@@ -104,15 +106,24 @@ public class JpaArtifactManagement implements ArtifactManagement {
|
||||
|
||||
assertArtifactQuota(moduleId, 1);
|
||||
|
||||
final AbstractDbArtifact artifact = storeArtifact(artifactUpload);
|
||||
final AbstractDbArtifact artifact = storeArtifact(artifactUpload, softwareModule.isEncrypted());
|
||||
return storeArtifactMetadata(softwareModule, filename, artifact, existing);
|
||||
}
|
||||
|
||||
private AbstractDbArtifact storeArtifact(final ArtifactUpload artifactUpload) {
|
||||
try (final InputStream quotaStream = wrapInQuotaStream(artifactUpload.getInputStream())) {
|
||||
return artifactRepository.store(tenantAware.getCurrentTenant(), quotaStream, artifactUpload.getFilename(),
|
||||
artifactUpload.getContentType(), new DbArtifactHash(artifactUpload.getProvidedSha1Sum(),
|
||||
artifactUpload.getProvidedMd5Sum(), artifactUpload.getProvidedSha256Sum()));
|
||||
private AbstractDbArtifact storeArtifact(final ArtifactUpload artifactUpload, final boolean isSmEncrypted) {
|
||||
final String tenant = tenantAware.getCurrentTenant();
|
||||
final long smId = artifactUpload.getModuleId();
|
||||
final InputStream stream = artifactUpload.getInputStream();
|
||||
final String fileName = artifactUpload.getFilename();
|
||||
final String contentType = artifactUpload.getContentType();
|
||||
final String providedSha1 = artifactUpload.getProvidedSha1Sum();
|
||||
final String providedMd5 = artifactUpload.getProvidedMd5Sum();
|
||||
final String providedSha256 = artifactUpload.getProvidedSha256Sum();
|
||||
|
||||
try (final InputStream wrappedStream = wrapInQuotaStream(
|
||||
isSmEncrypted ? wrapInEncryptionStream(smId, stream) : stream)) {
|
||||
return artifactRepository.store(tenant, wrappedStream, fileName, contentType,
|
||||
new DbArtifactHash(providedSha1, providedMd5, providedSha256));
|
||||
} catch (final ArtifactStoreException | IOException e) {
|
||||
throw new ArtifactUploadFailedException(e);
|
||||
} catch (final HashNotMatchException e) {
|
||||
@@ -126,6 +137,10 @@ public class JpaArtifactManagement implements ArtifactManagement {
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream wrapInEncryptionStream(final long smId, final InputStream stream) {
|
||||
return ArtifactEncryptionService.getInstance().encryptSoftwareModuleArtifact(smId, stream);
|
||||
}
|
||||
|
||||
private void assertArtifactQuota(final long id, final int requested) {
|
||||
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxArtifactsPerSoftwareModule(),
|
||||
Artifact.class, SoftwareModule.class, localArtifactRepository::countBySoftwareModuleId);
|
||||
@@ -212,10 +227,26 @@ public class JpaArtifactManagement implements ArtifactManagement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AbstractDbArtifact> loadArtifactBinary(final String sha1Hash) {
|
||||
return Optional.ofNullable(artifactRepository.existsByTenantAndSha1(tenantAware.getCurrentTenant(), sha1Hash)
|
||||
? artifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), sha1Hash)
|
||||
: null);
|
||||
public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash, final long softwareModuleId,
|
||||
final boolean isEncrypted) {
|
||||
final String tenant = tenantAware.getCurrentTenant();
|
||||
if (artifactRepository.existsByTenantAndSha1(tenant, sha1Hash)) {
|
||||
final DbArtifact dbArtifact = artifactRepository.getArtifactBySha1(tenant, sha1Hash);
|
||||
return Optional.ofNullable(
|
||||
isEncrypted ? wrapInEncryptionAwareDbArtifact(softwareModuleId, dbArtifact) : dbArtifact);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private final DbArtifact wrapInEncryptionAwareDbArtifact(final long smId, final DbArtifact dbArtifact) {
|
||||
if (dbArtifact == null) {
|
||||
return null;
|
||||
}
|
||||
final ArtifactEncryptionService encryptionService = ArtifactEncryptionService.getInstance();
|
||||
return new EncryptionAwareDbArtifact(dbArtifact,
|
||||
stream -> encryptionService.decryptSoftwareModuleArtifact(smId, stream),
|
||||
encryptionService.encryptionSizeOverhead());
|
||||
}
|
||||
|
||||
private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename,
|
||||
|
||||
@@ -31,6 +31,7 @@ import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
@@ -155,7 +156,14 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
|
||||
public SoftwareModule create(final SoftwareModuleCreate c) {
|
||||
final JpaSoftwareModuleCreate create = (JpaSoftwareModuleCreate) c;
|
||||
|
||||
return softwareModuleRepository.save(create.build());
|
||||
final JpaSoftwareModule sm = softwareModuleRepository.save(create.build());
|
||||
if (create.isEncrypted()) {
|
||||
// flush sm creation in order to get an Id
|
||||
entityManager.flush();
|
||||
ArtifactEncryptionService.getInstance().addSoftwareModuleEncryptionSecrets(sm.getId());
|
||||
}
|
||||
|
||||
return sm;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,8 +303,8 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
|
||||
|
||||
@Override
|
||||
public Page<SoftwareModule> findByRsql(final Pageable pageable, final String rsqlParam) {
|
||||
final Specification<JpaSoftwareModule> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
final Specification<JpaSoftwareModule> spec = RSQLUtility.buildRsqlSpecification(rsqlParam,
|
||||
SoftwareModuleFields.class, virtualPropertyReplacer, database);
|
||||
|
||||
return convertSmPage(softwareModuleRepository.findAll(spec, pageable), pageable);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ import javax.persistence.EntityManager;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
|
||||
import org.eclipse.hawkbit.repository.ArtifactEncryption;
|
||||
import org.eclipse.hawkbit.repository.ArtifactEncryptionSecretsStore;
|
||||
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.BaseRepositoryTypeProvider;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
@@ -245,7 +248,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @param dsTypeManagement
|
||||
* for loading {@link TargetType#getCompatibleDistributionSetTypes()}
|
||||
* for loading
|
||||
* {@link TargetType#getCompatibleDistributionSetTypes()}
|
||||
* @return TargetTypeBuilder bean
|
||||
*/
|
||||
@Bean
|
||||
@@ -261,8 +265,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @param softwareManagement
|
||||
* for loading {@link DistributionSetType#getMandatoryModuleTypes()}
|
||||
* and {@link DistributionSetType#getOptionalModuleTypes()}
|
||||
* for loading
|
||||
* {@link DistributionSetType#getMandatoryModuleTypes()} and
|
||||
* {@link DistributionSetType#getOptionalModuleTypes()}
|
||||
* @return DistributionSetTypeBuilder bean
|
||||
*/
|
||||
@Bean
|
||||
@@ -304,8 +309,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @return the {@link SystemSecurityContext} singleton bean which make it
|
||||
* accessible in beans which cannot access the service directly, e.g.
|
||||
* JPA entities.
|
||||
* accessible in beans which cannot access the service directly,
|
||||
* e.g. JPA entities.
|
||||
*/
|
||||
@Bean
|
||||
SystemSecurityContextHolder systemSecurityContextHolder() {
|
||||
@@ -313,9 +318,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link TenantConfigurationManagement} singleton bean which make
|
||||
* it accessible in beans which cannot access the service directly, e.g.
|
||||
* JPA entities.
|
||||
* @return the {@link TenantConfigurationManagement} singleton bean which
|
||||
* make it accessible in beans which cannot access the service
|
||||
* directly, e.g. JPA entities.
|
||||
*/
|
||||
@Bean
|
||||
TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
|
||||
@@ -324,8 +329,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @return the {@link SystemManagementHolder} singleton bean which holds the
|
||||
* current {@link SystemManagement} service and make it accessible in
|
||||
* beans which cannot access the service directly, e.g. JPA entities.
|
||||
* current {@link SystemManagement} service and make it accessible
|
||||
* in beans which cannot access the service directly, e.g. JPA
|
||||
* entities.
|
||||
*/
|
||||
@Bean
|
||||
SystemManagementHolder systemManagementHolder() {
|
||||
@@ -333,9 +339,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link TenantAwareHolder} singleton bean which holds the current
|
||||
* {@link TenantAware} service and make it accessible in beans which
|
||||
* cannot access the service directly, e.g. JPA entities.
|
||||
* @return the {@link TenantAwareHolder} singleton bean which holds the
|
||||
* current {@link TenantAware} service and make it accessible in
|
||||
* beans which cannot access the service directly, e.g. JPA
|
||||
* entities.
|
||||
*/
|
||||
@Bean
|
||||
TenantAwareHolder tenantAwareHolder() {
|
||||
@@ -343,9 +350,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which holds
|
||||
* the current {@link SecurityTokenGenerator} service and make it
|
||||
* accessible in beans which cannot access the service via injection
|
||||
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which
|
||||
* holds the current {@link SecurityTokenGenerator} service and make
|
||||
* it accessible in beans which cannot access the service via
|
||||
* injection
|
||||
*/
|
||||
@Bean
|
||||
SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
|
||||
@@ -940,7 +948,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Our default {@link BaseRepositoryTypeProvider} bean always provides the NoCountBaseRepository
|
||||
* Our default {@link BaseRepositoryTypeProvider} bean always provides the
|
||||
* NoCountBaseRepository
|
||||
*
|
||||
* @return a {@link BaseRepositoryTypeProvider} bean
|
||||
*/
|
||||
@@ -950,4 +959,17 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
return new NoCountBaseRepositoryTypeProvider();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default artifact encryption service bean that internally uses
|
||||
* {@link ArtifactEncryption} and {@link ArtifactEncryptionSecretsStore}
|
||||
* beans for {@link SoftwareModule} artifacts encryption/decryption
|
||||
*
|
||||
* @return a {@link ArtifactEncryptionService} bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ArtifactEncryptionService artifactEncryptionService() {
|
||||
return ArtifactEncryptionService.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,26 @@ public class JpaSoftwareModuleCreate extends AbstractSoftwareModuleUpdateCreate<
|
||||
|
||||
private final SoftwareModuleTypeManagement softwareModuleTypeManagement;
|
||||
|
||||
private boolean encrypted;
|
||||
|
||||
JpaSoftwareModuleCreate(final SoftwareModuleTypeManagement softwareModuleTypeManagement) {
|
||||
this.softwareModuleTypeManagement = softwareModuleTypeManagement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoftwareModuleCreate encrypted(final boolean encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaSoftwareModule build() {
|
||||
return new JpaSoftwareModule(getSoftwareModuleTypeFromKeyString(type), name, version, description, vendor);
|
||||
return new JpaSoftwareModule(getSoftwareModuleTypeFromKeyString(type), name, version, description, vendor,
|
||||
encrypted);
|
||||
}
|
||||
|
||||
private SoftwareModuleType getSoftwareModuleTypeFromKeyString(final String type) {
|
||||
|
||||
@@ -90,6 +90,9 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
|
||||
@OneToMany(mappedBy = "softwareModule", fetch = FetchType.LAZY, targetEntity = JpaSoftwareModuleMetadata.class)
|
||||
private List<JpaSoftwareModuleMetadata> metadata;
|
||||
|
||||
@Column(name = "encrypted")
|
||||
private boolean encrypted;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
@@ -97,6 +100,20 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
|
||||
// Default constructor for JPA
|
||||
}
|
||||
|
||||
/**
|
||||
* parameterized constructor.
|
||||
*
|
||||
* @param type
|
||||
* of the {@link SoftwareModule}
|
||||
* @param name
|
||||
* abstract name of the {@link SoftwareModule}
|
||||
* @param version
|
||||
* of the {@link SoftwareModule}
|
||||
*/
|
||||
public JpaSoftwareModule(final SoftwareModuleType type, final String name, final String version) {
|
||||
this(type, name, version, null, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* parameterized constructor.
|
||||
*
|
||||
@@ -110,12 +127,15 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
|
||||
* of the {@link SoftwareModule}
|
||||
* @param vendor
|
||||
* of the {@link SoftwareModule}
|
||||
* @param encrypted
|
||||
* encryption flag of the {@link SoftwareModule}
|
||||
*/
|
||||
public JpaSoftwareModule(final SoftwareModuleType type, final String name, final String version,
|
||||
final String description, final String vendor) {
|
||||
final String description, final String vendor, final boolean encrypted) {
|
||||
super(name, version, description);
|
||||
this.vendor = vendor;
|
||||
this.type = (JpaSoftwareModuleType) type;
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
public void addArtifact(final Artifact artifact) {
|
||||
@@ -175,8 +195,8 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
|
||||
* Marks or un-marks this software module as deleted.
|
||||
*
|
||||
* @param deleted
|
||||
* {@code true} if the software module should be marked as
|
||||
* deleted otherwise {@code false}
|
||||
* {@code true} if the software module should be marked as deleted
|
||||
* otherwise {@code false}
|
||||
*/
|
||||
public void setDeleted(final boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
@@ -186,10 +206,27 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this software module as encrypted.
|
||||
*
|
||||
* @param encrypted
|
||||
* {@code true} if the software module should be marked as encrypted
|
||||
* otherwise {@code false}
|
||||
*/
|
||||
public void setEncrypted(final boolean encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SoftwareModule [deleted=" + deleted + ", name=" + getName() + ", version=" + getVersion()
|
||||
+ ", revision=" + getOptLockRevision() + ", Id=" + getId() + ", type=" + getType().getName() + "]";
|
||||
return "SoftwareModule [deleted=" + isDeleted() + ", encrypted=" + isEncrypted() + ", name=" + getName()
|
||||
+ ", version=" + getVersion() + ", revision=" + getOptLockRevision() + ", Id=" + getId() + ", type="
|
||||
+ getType().getName() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
|
||||
|
||||
UPDATE sp_base_software_module SET encrypted = 0;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
|
||||
|
||||
UPDATE sp_base_software_module SET encrypted = 0;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
|
||||
|
||||
UPDATE sp_base_software_module SET encrypted = 0;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
|
||||
|
||||
UPDATE sp_base_software_module SET encrypted = 0;
|
||||
@@ -1,3 +1 @@
|
||||
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
|
||||
|
||||
UPDATE sp_distribution_set SET valid = 1;
|
||||
ALTER TABLE sp_distribution_set ADD valid BIT DEFAULT 1;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE sp_base_software_module ADD encrypted BIT DEFAULT 0;
|
||||
@@ -26,7 +26,7 @@ import javax.validation.ConstraintViolationException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
@@ -77,7 +77,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
.isFalse();
|
||||
|
||||
assertThat(artifactManagement.findFirstBySHA1(NOT_EXIST_ID)).isNotPresent();
|
||||
assertThat(artifactManagement.loadArtifactBinary(NOT_EXIST_ID)).isNotPresent();
|
||||
assertThat(artifactManagement.loadArtifactBinary(NOT_EXIST_ID, module.getId(), module.isEncrypted()))
|
||||
.isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,10 +117,10 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(artifactRepository.findAll()).hasSize(0);
|
||||
|
||||
final JpaSoftwareModule sm = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
|
||||
final JpaSoftwareModule sm2 = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
|
||||
softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 3", "version 3", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
|
||||
softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 3", "version 3"));
|
||||
|
||||
final int artifactSize = 5 * 1024;
|
||||
final byte[] randomBytes = randomBytes(artifactSize);
|
||||
@@ -165,8 +166,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
final String artifactData = "test";
|
||||
final int artifactSize = artifactData.length();
|
||||
|
||||
final long smID = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0", null, null)).getId();
|
||||
final long smID = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0"))
|
||||
.getId();
|
||||
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
|
||||
() -> artifactManagement.create(new ArtifactUpload(IOUtils.toInputStream(artifactData, "UTF-8"), smID,
|
||||
illegalFilename, false, artifactSize)));
|
||||
@@ -178,8 +179,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
public void createArtifactsUntilQuotaIsExceeded() throws IOException {
|
||||
|
||||
// create a software module
|
||||
final long smId = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null))
|
||||
.getId();
|
||||
final long smId = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0")).getId();
|
||||
|
||||
// now create artifacts for this module until the quota is exceeded
|
||||
final long maxArtifacts = quotaManagement.getMaxArtifactsPerSoftwareModule();
|
||||
@@ -217,14 +217,13 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
final int artifactSize = Math.toIntExact(quotaManagement.getMaxArtifactSize() / 10);
|
||||
final int numArtifacts = Math.toIntExact(maxBytes / artifactSize);
|
||||
for (int i = 0; i < numArtifacts; ++i) {
|
||||
final JpaSoftwareModule sm = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "smd" + i, "1.0", null, null));
|
||||
final JpaSoftwareModule sm = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smd" + i, "1.0"));
|
||||
artifactIds.add(createArtifactForSoftwareModule("file" + i, sm.getId(), artifactSize).getId());
|
||||
}
|
||||
|
||||
// upload one more artifact to trigger the quota exceeded error
|
||||
final JpaSoftwareModule sm = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "smd" + numArtifacts, "1.0", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "smd" + numArtifacts, "1.0"));
|
||||
assertThatExceptionOfType(StorageQuotaExceededException.class)
|
||||
.isThrownBy(() -> createArtifactForSoftwareModule("file" + numArtifacts, sm.getId(), artifactSize));
|
||||
|
||||
@@ -240,8 +239,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
public void createArtifactFailsIfTooLarge() {
|
||||
|
||||
// create a software module
|
||||
final JpaSoftwareModule sm1 = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null));
|
||||
final JpaSoftwareModule sm1 = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0"));
|
||||
|
||||
// create an artifact that exceeds the configured quota
|
||||
final long maxSize = quotaManagement.getMaxArtifactSize();
|
||||
@@ -254,7 +252,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
public void hardDeleteSoftwareModule() throws IOException {
|
||||
|
||||
final JpaSoftwareModule sm = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
|
||||
|
||||
createArtifactForSoftwareModule("file1", sm.getId(), 5 * 1024);
|
||||
assertThat(artifactRepository.findAll()).hasSize(1);
|
||||
@@ -273,9 +271,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
@Description("Tests the deletion of a local artifact including metadata.")
|
||||
public void deleteArtifact() throws IOException {
|
||||
final JpaSoftwareModule sm = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
|
||||
final JpaSoftwareModule sm2 = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
|
||||
|
||||
assertThat(artifactRepository.findAll()).isEmpty();
|
||||
|
||||
@@ -326,9 +324,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
public void deleteDuplicateArtifacts() throws IOException {
|
||||
|
||||
final JpaSoftwareModule sm = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
|
||||
final JpaSoftwareModule sm2 = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
|
||||
|
||||
final int artifactSize = 5 * 1024;
|
||||
final byte[] randomBytes = randomBytes(artifactSize);
|
||||
@@ -368,9 +366,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
public void deleteArtifactWithSameHashAndSoftwareModuleIsNotDeletedInSameTenants() throws IOException {
|
||||
|
||||
final JpaSoftwareModule sm = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
|
||||
final JpaSoftwareModule sm2 = softwareModuleRepository
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
|
||||
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
|
||||
|
||||
final int artifactSize = 5 * 1024;
|
||||
final byte[] randomBytes = randomBytes(artifactSize);
|
||||
@@ -382,7 +380,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize,
|
||||
inputStream2);
|
||||
|
||||
assertEqualFileContents(artifactManagement.loadArtifactBinary(artifact2.getSha1Hash()), randomBytes);
|
||||
assertEqualFileContents(
|
||||
artifactManagement.loadArtifactBinary(artifact2.getSha1Hash(), sm2.getId(), sm2.isEncrypted()),
|
||||
randomBytes);
|
||||
|
||||
assertThat(artifactRepository.findAll()).hasSize(2);
|
||||
|
||||
@@ -447,9 +447,11 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
final int artifactSize = 5 * 1024;
|
||||
final byte[] randomBytes = randomBytes(artifactSize);
|
||||
try (final InputStream input = new ByteArrayInputStream(randomBytes)) {
|
||||
final Artifact artifact = createArtifactForSoftwareModule("file1",
|
||||
testdataFactory.createSoftwareModuleOs().getId(), artifactSize, input);
|
||||
assertEqualFileContents(artifactManagement.loadArtifactBinary(artifact.getSha1Hash()), randomBytes);
|
||||
final SoftwareModule smOs = testdataFactory.createSoftwareModuleOs();
|
||||
final Artifact artifact = createArtifactForSoftwareModule("file1", smOs.getId(), artifactSize, input);
|
||||
assertEqualFileContents(
|
||||
artifactManagement.loadArtifactBinary(artifact.getSha1Hash(), smOs.getId(), smOs.isEncrypted()),
|
||||
randomBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +461,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
public void loadArtifactBinaryWithoutDownloadArtifactThrowsPermissionDenied() {
|
||||
assertThatExceptionOfType(InsufficientPermissionException.class)
|
||||
.as("Should not have worked with missing permission.")
|
||||
.isThrownBy(() -> artifactManagement.loadArtifactBinary("123"));
|
||||
.isThrownBy(() -> artifactManagement.loadArtifactBinary("123", 1, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -541,7 +543,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(createdArtifact.getSha256Hash()).isEqualTo(artifactHashes.getSha256());
|
||||
}
|
||||
|
||||
final Optional<AbstractDbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1());
|
||||
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1(),
|
||||
sm.getId(), sm.isEncrypted());
|
||||
assertThat(dbArtifact).isPresent();
|
||||
}
|
||||
|
||||
@@ -561,7 +564,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
final Artifact artifact = artifactManagement.create(artifactUpload);
|
||||
assertThat(artifact).isNotNull();
|
||||
}
|
||||
final Optional<AbstractDbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1());
|
||||
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1(),
|
||||
smOs.getId(), smOs.isEncrypted());
|
||||
assertThat(dbArtifact).isPresent();
|
||||
|
||||
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
|
||||
@@ -622,10 +626,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(runAsTenant(tenant, () -> artifactRepository.findAll())).hasSize(count);
|
||||
}
|
||||
|
||||
private void assertEqualFileContents(final Optional<AbstractDbArtifact> artifact, final byte[] randomBytes)
|
||||
private void assertEqualFileContents(final Optional<DbArtifact> artifact, final byte[] randomBytes)
|
||||
throws IOException {
|
||||
try (final InputStream inputStream = artifactManagement.loadArtifactBinary(artifact.get().getHashes().getSha1())
|
||||
.get().getFileInputStream()) {
|
||||
try (final InputStream inputStream = artifact.get().getFileInputStream()) {
|
||||
assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(randomBytes), inputStream),
|
||||
"The stored binary matches the given binary");
|
||||
}
|
||||
|
||||
@@ -520,13 +520,13 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
Arrays.asList(testType.getId()));
|
||||
|
||||
// found in test
|
||||
final SoftwareModule unassigned = testdataFactory.createSoftwareModule("thetype", "unassignedfound");
|
||||
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound");
|
||||
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound");
|
||||
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "a");
|
||||
final SoftwareModule unassigned = testdataFactory.createSoftwareModule("thetype", "unassignedfound", false);
|
||||
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound", false);
|
||||
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound", false);
|
||||
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "a", false);
|
||||
|
||||
// ignored
|
||||
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted");
|
||||
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted", false);
|
||||
final SoftwareModule four = testdataFactory.createSoftwareModuleOs("e");
|
||||
|
||||
final DistributionSet set = distributionSetManagement
|
||||
@@ -572,13 +572,13 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
Arrays.asList(testType.getId()));
|
||||
|
||||
// found in test
|
||||
testdataFactory.createSoftwareModule("thetype", "unassignedfound");
|
||||
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound");
|
||||
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound");
|
||||
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "d");
|
||||
testdataFactory.createSoftwareModule("thetype", "unassignedfound", false);
|
||||
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound", false);
|
||||
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound", false);
|
||||
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "d", false);
|
||||
|
||||
// ignored
|
||||
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted");
|
||||
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted", false);
|
||||
final SoftwareModule four = testdataFactory.createSoftwareModuleOs("e");
|
||||
|
||||
distributionSetManagement
|
||||
|
||||
@@ -540,7 +540,7 @@ public class TestdataFactory {
|
||||
* @return persisted {@link SoftwareModule}.
|
||||
*/
|
||||
public SoftwareModule createSoftwareModule(final String typeKey) {
|
||||
return createSoftwareModule(typeKey, "");
|
||||
return createSoftwareModule(typeKey, "", false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -552,7 +552,7 @@ public class TestdataFactory {
|
||||
* @return persisted {@link SoftwareModule}.
|
||||
*/
|
||||
public SoftwareModule createSoftwareModuleApp() {
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_APP_KEY, "");
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_APP_KEY, "", false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,7 +567,7 @@ public class TestdataFactory {
|
||||
* @return persisted {@link SoftwareModule}.
|
||||
*/
|
||||
public SoftwareModule createSoftwareModuleApp(final String prefix) {
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_APP_KEY, prefix);
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_APP_KEY, prefix, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -579,7 +579,7 @@ public class TestdataFactory {
|
||||
* @return persisted {@link SoftwareModule}.
|
||||
*/
|
||||
public SoftwareModule createSoftwareModuleOs() {
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_OS_KEY, "");
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_OS_KEY, "", false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -594,7 +594,7 @@ public class TestdataFactory {
|
||||
* @return persisted {@link SoftwareModule}.
|
||||
*/
|
||||
public SoftwareModule createSoftwareModuleOs(final String prefix) {
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_OS_KEY, prefix);
|
||||
return createSoftwareModule(Constants.SMT_DEFAULT_OS_KEY, prefix, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,10 +609,10 @@ public class TestdataFactory {
|
||||
*
|
||||
* @return persisted {@link SoftwareModule}.
|
||||
*/
|
||||
public SoftwareModule createSoftwareModule(final String typeKey, final String prefix) {
|
||||
public SoftwareModule createSoftwareModule(final String typeKey, final String prefix, final boolean encrypted) {
|
||||
return softwareModuleManagement.create(entityFactory.softwareModule().create()
|
||||
.type(findOrCreateSoftwareModuleType(typeKey)).name(prefix + typeKey).version(prefix + DEFAULT_VERSION)
|
||||
.description(LOREM.words(10)).vendor(DEFAULT_VENDOR));
|
||||
.description(LOREM.words(10)).vendor(DEFAULT_VENDOR).encrypted(encrypted));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,10 @@ public class DdiChunk {
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
@JsonProperty("encrypted")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Boolean encrypted;
|
||||
|
||||
@JsonProperty("artifacts")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private List<DdiArtifact> artifacts;
|
||||
@@ -56,16 +60,19 @@ public class DdiChunk {
|
||||
* of the artifact
|
||||
* @param name
|
||||
* of the artifact
|
||||
* @param encrypted
|
||||
* if artifacts are encrypted
|
||||
* @param artifacts
|
||||
* download information
|
||||
* @param metadata
|
||||
* optional as additional information for the target/device
|
||||
*/
|
||||
public DdiChunk(final String part, final String version, final String name, final List<DdiArtifact> artifacts,
|
||||
final List<DdiMetadata> metadata) {
|
||||
public DdiChunk(final String part, final String version, final String name, final Boolean encrypted,
|
||||
final List<DdiArtifact> artifacts, final List<DdiMetadata> metadata) {
|
||||
this.part = part;
|
||||
this.version = version;
|
||||
this.name = name;
|
||||
this.encrypted = encrypted;
|
||||
this.artifacts = artifacts;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
@@ -82,6 +89,10 @@ public class DdiChunk {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Boolean isEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
public List<DdiArtifact> getArtifacts() {
|
||||
if (artifacts == null) {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -32,21 +32,21 @@ import io.qameta.allure.Story;
|
||||
@Story("Serializability of DDI api Models")
|
||||
public class DdiChunkTest {
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
@Description("Verify the correct serialization and deserialization of the model")
|
||||
public void shouldSerializeAndDeserializeObject() throws IOException {
|
||||
// Setup
|
||||
String part = "1234";
|
||||
String version = "1.0";
|
||||
String name = "Dummy-Artifact";
|
||||
List<DdiArtifact> dummyArtifacts = Collections.emptyList();
|
||||
DdiChunk ddiChunk = new DdiChunk(part, version, name, dummyArtifacts, null);
|
||||
final String part = "1234";
|
||||
final String version = "1.0";
|
||||
final String name = "Dummy-Artifact";
|
||||
final List<DdiArtifact> dummyArtifacts = Collections.emptyList();
|
||||
final DdiChunk ddiChunk = new DdiChunk(part, version, name, null, dummyArtifacts, null);
|
||||
|
||||
// Test
|
||||
String serializedDdiChunk = mapper.writeValueAsString(ddiChunk);
|
||||
DdiChunk deserializedDdiChunk = mapper.readValue(serializedDdiChunk, DdiChunk.class);
|
||||
final String serializedDdiChunk = mapper.writeValueAsString(ddiChunk);
|
||||
final DdiChunk deserializedDdiChunk = mapper.readValue(serializedDdiChunk, DdiChunk.class);
|
||||
|
||||
assertThat(serializedDdiChunk).contains(part, version, name);
|
||||
assertThat(deserializedDdiChunk.getPart()).isEqualTo(part);
|
||||
@@ -59,10 +59,10 @@ public class DdiChunkTest {
|
||||
@Description("Verify the correct deserialization of a model with a additional unknown property")
|
||||
public void shouldDeserializeObjectWithUnknownProperty() throws IOException {
|
||||
// Setup
|
||||
String serializedDdiChunk = "{\"part\":\"1234\",\"version\":\"1.0\",\"name\":\"Dummy-Artifact\",\"artifacts\":[],\"unknownProperty\":\"test\"}";
|
||||
final String serializedDdiChunk = "{\"part\":\"1234\",\"version\":\"1.0\",\"name\":\"Dummy-Artifact\",\"artifacts\":[],\"unknownProperty\":\"test\"}";
|
||||
|
||||
// Test
|
||||
DdiChunk ddiChunk = mapper.readValue(serializedDdiChunk, DdiChunk.class);
|
||||
final DdiChunk ddiChunk = mapper.readValue(serializedDdiChunk, DdiChunk.class);
|
||||
|
||||
assertThat(ddiChunk.getPart()).isEqualTo("1234");
|
||||
assertThat(ddiChunk.getVersion()).isEqualTo("1.0");
|
||||
@@ -74,10 +74,10 @@ public class DdiChunkTest {
|
||||
@Description("Verify that deserialization fails for known properties with a wrong datatype")
|
||||
public void shouldFailForObjectWithWrongDataTypes() throws IOException {
|
||||
// Setup
|
||||
String serializedDdiChunk = "{\"part\":[\"1234\"],\"version\":\"1.0\",\"name\":\"Dummy-Artifact\",\"artifacts\":[]}";
|
||||
final String serializedDdiChunk = "{\"part\":[\"1234\"],\"version\":\"1.0\",\"name\":\"Dummy-Artifact\",\"artifacts\":[]}";
|
||||
|
||||
// Test
|
||||
assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(
|
||||
() -> mapper.readValue(serializedDdiChunk, DdiChunk.class));
|
||||
assertThatExceptionOfType(MismatchedInputException.class)
|
||||
.isThrownBy(() -> mapper.readValue(serializedDdiChunk, DdiChunk.class));
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public final class DataConversionHelper {
|
||||
|
||||
return new ResponseList<>(uAction.getDistributionSet().getModules().stream()
|
||||
.map(module -> new DdiChunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(),
|
||||
module.getName(),
|
||||
module.getName(), module.isEncrypted() ? Boolean.TRUE : null,
|
||||
createArtifacts(target, module, artifactUrlHandler, systemManagement, request),
|
||||
mapMetadata(metadata.get(module.getId()))))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
@@ -18,7 +18,7 @@ import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionHistory;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiArtifact;
|
||||
@@ -183,7 +183,8 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
@SuppressWarnings("squid:S3655")
|
||||
final Artifact artifact = module.getArtifactByFilename(fileName).get();
|
||||
|
||||
final AbstractDbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash())
|
||||
final DbArtifact file = artifactManagement
|
||||
.loadArtifactBinary(artifact.getSha1Hash(), module.getId(), module.isEncrypted())
|
||||
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
|
||||
|
||||
final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader(HttpHeaders.IF_MATCH);
|
||||
|
||||
@@ -38,6 +38,9 @@ public class MgmtSoftwareModule extends MgmtNamedEntity {
|
||||
@JsonProperty
|
||||
private boolean deleted;
|
||||
|
||||
@JsonProperty
|
||||
private boolean encrypted;
|
||||
|
||||
public void setDeleted(final boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
@@ -79,4 +82,12 @@ public class MgmtSoftwareModule extends MgmtNamedEntity {
|
||||
this.vendor = vendor;
|
||||
}
|
||||
|
||||
public void setEncrypted(final boolean encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
public boolean isEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ public class MgmtSoftwareModuleRequestBodyPost {
|
||||
@JsonProperty
|
||||
private String vendor;
|
||||
|
||||
@JsonProperty
|
||||
private boolean encrypted;
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
@@ -121,4 +124,22 @@ public class MgmtSoftwareModuleRequestBodyPost {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if encrypted
|
||||
*/
|
||||
public boolean isEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encrypted
|
||||
* if should be encrypted
|
||||
*
|
||||
* @return updated body
|
||||
*/
|
||||
public MgmtSoftwareModuleRequestBodyPost setEncrypted(final boolean encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.io.InputStream;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadArtifactRestApi;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
@@ -70,7 +70,8 @@ public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi
|
||||
final Artifact artifact = module.getArtifact(artifactId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Artifact.class, artifactId));
|
||||
|
||||
final AbstractDbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash())
|
||||
final DbArtifact file = artifactManagement
|
||||
.loadArtifactBinary(artifact.getSha1Hash(), module.getId(), module.isEncrypted())
|
||||
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
|
||||
final HttpServletRequest request = requestResponseContextHolder.getHttpServletRequest();
|
||||
final String ifMatch = request.getHeader(HttpHeaders.IF_MATCH);
|
||||
|
||||
@@ -45,7 +45,8 @@ public final class MgmtSoftwareModuleMapper {
|
||||
private static SoftwareModuleCreate fromRequest(final EntityFactory entityFactory,
|
||||
final MgmtSoftwareModuleRequestBodyPost smsRest) {
|
||||
return entityFactory.softwareModule().create().type(smsRest.getType()).name(smsRest.getName())
|
||||
.version(smsRest.getVersion()).description(smsRest.getDescription()).vendor(smsRest.getVendor());
|
||||
.version(smsRest.getVersion()).description(smsRest.getDescription()).vendor(smsRest.getVendor())
|
||||
.encrypted(smsRest.isEncrypted());
|
||||
}
|
||||
|
||||
static List<SoftwareModuleMetadataCreate> fromRequestSwMetadata(final EntityFactory entityFactory,
|
||||
@@ -107,6 +108,7 @@ public final class MgmtSoftwareModuleMapper {
|
||||
response.setType(softwareModule.getType().getKey());
|
||||
response.setVendor(softwareModule.getVendor());
|
||||
response.setDeleted(softwareModule.isDeleted());
|
||||
response.setEncrypted(softwareModule.isEncrypted());
|
||||
|
||||
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getSoftwareModule(response.getModuleId()))
|
||||
.withSelfRel());
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
@@ -199,7 +200,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
|
||||
@Test
|
||||
@Description("Verifies that artifacts which exceed the configured maximum size cannot be uploaded.")
|
||||
public void uploadArtifactFailsIfTooLarge() throws Exception {
|
||||
final SoftwareModule sm = testdataFactory.createSoftwareModule("quota", "quota");
|
||||
final SoftwareModule sm = testdataFactory.createSoftwareModule("quota", "quota", false);
|
||||
final long maxSize = quotaManagement.getMaxArtifactSize();
|
||||
|
||||
// create a file which exceeds the configured maximum size
|
||||
@@ -218,7 +219,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
|
||||
@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 SoftwareModule sm = testdataFactory.createSoftwareModule("quota", "quota", false);
|
||||
final String illegalFilename = "<img src=ernw onerror=alert(1)>.xml";
|
||||
|
||||
final byte[] randomBytes = randomBytes(5 * 1024);
|
||||
@@ -236,11 +237,12 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
|
||||
assertThat(artifactManagement.count()).as("Wrong artifact size").isEqualTo(1);
|
||||
|
||||
// binary
|
||||
try (InputStream fileInputStream = artifactManagement
|
||||
.loadArtifactBinary(softwareModuleManagement.get(sm.getId()).get().getArtifacts().get(0).getSha1Hash())
|
||||
try (final InputStream fileInputStream = artifactManagement
|
||||
.loadArtifactBinary(softwareModuleManagement.get(sm.getId()).get().getArtifacts().get(0).getSha1Hash(),
|
||||
sm.getId(), sm.isEncrypted())
|
||||
.get().getFileInputStream()) {
|
||||
assertTrue(
|
||||
IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream), "Wrong artifact content");
|
||||
assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream),
|
||||
"Wrong artifact content");
|
||||
}
|
||||
|
||||
// hashes
|
||||
@@ -662,6 +664,14 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isUnsupportedMediaType());
|
||||
|
||||
final SoftwareModule swm = entityFactory.softwareModule().create().name("encryptedModule").type(osType)
|
||||
.version("version").vendor("vendor").description("description").encrypted(true).build();
|
||||
// artifact decryption is not supported
|
||||
mvc.perform(
|
||||
post("/rest/v1/softwaremodules").content(JsonBuilder.softwareModules(Collections.singletonList(swm)))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
|
||||
|
||||
// not allowed methods
|
||||
mvc.perform(put("/rest/v1/softwaremodules")).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isMethodNotAllowed());
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.ValidationException;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
@@ -31,6 +32,8 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* General controller advice for exception handling.
|
||||
*/
|
||||
@@ -54,6 +57,8 @@ public class ResponseExceptionHandler {
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_INSUFFICIENT_PERMISSION, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA256_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH, HttpStatus.BAD_REQUEST);
|
||||
|
||||
@@ -20,7 +20,7 @@ import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -118,9 +118,9 @@ public final class FileStreamingUtil {
|
||||
* @throws FileStreamingFailedException
|
||||
* if streaming fails
|
||||
*/
|
||||
public static ResponseEntity<InputStream> writeFileResponse(final AbstractDbArtifact artifact,
|
||||
final String filename, final long lastModified, final HttpServletResponse response,
|
||||
final HttpServletRequest request, final FileStreamingProgressListener progressListener) {
|
||||
public static ResponseEntity<InputStream> writeFileResponse(final DbArtifact artifact, final String filename,
|
||||
final long lastModified, final HttpServletResponse response, final HttpServletRequest request,
|
||||
final FileStreamingProgressListener progressListener) {
|
||||
|
||||
ResponseEntity<InputStream> result;
|
||||
|
||||
@@ -189,9 +189,9 @@ public final class FileStreamingUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ResponseEntity<InputStream> handleFullFileRequest(final AbstractDbArtifact artifact,
|
||||
final String filename, final HttpServletResponse response,
|
||||
final FileStreamingProgressListener progressListener, final ByteRange full) {
|
||||
private static ResponseEntity<InputStream> handleFullFileRequest(final DbArtifact artifact, final String filename,
|
||||
final HttpServletResponse response, final FileStreamingProgressListener progressListener,
|
||||
final ByteRange full) {
|
||||
final ByteRange r = full;
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
|
||||
response.setContentLengthLong(r.getLength());
|
||||
@@ -257,7 +257,7 @@ public final class FileStreamingUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static ResponseEntity<InputStream> handleMultipartRangeRequest(final AbstractDbArtifact artifact,
|
||||
private static ResponseEntity<InputStream> handleMultipartRangeRequest(final DbArtifact artifact,
|
||||
final String filename, final HttpServletResponse response,
|
||||
final FileStreamingProgressListener progressListener, final List<ByteRange> ranges) {
|
||||
|
||||
@@ -291,7 +291,7 @@ public final class FileStreamingUtil {
|
||||
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).build();
|
||||
}
|
||||
|
||||
private static ResponseEntity<InputStream> handleStandardRangeRequest(final AbstractDbArtifact artifact,
|
||||
private static ResponseEntity<InputStream> handleStandardRangeRequest(final DbArtifact artifact,
|
||||
final String filename, final HttpServletResponse response,
|
||||
final FileStreamingProgressListener progressListener, final List<ByteRange> ranges) {
|
||||
final ByteRange r = ranges.get(0);
|
||||
|
||||
@@ -97,7 +97,8 @@ public abstract class JsonBuilder {
|
||||
builder.append(new JSONObject().put("name", module.getName()).put("description", module.getDescription())
|
||||
.put("type", module.getType().getKey()).put("id", Long.MAX_VALUE).put("vendor", module.getVendor())
|
||||
.put("version", module.getVersion()).put("createdAt", "0").put("updatedAt", "0")
|
||||
.put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh").toString());
|
||||
.put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
|
||||
.put("encrypted", module.isEncrypted()).toString());
|
||||
|
||||
if (++i < modules.size()) {
|
||||
builder.append(",");
|
||||
@@ -447,7 +448,8 @@ public abstract class JsonBuilder {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String targets(final List<Target> targets, final boolean withToken, final long targetTypeId) throws JSONException {
|
||||
public static String targets(final List<Target> targets, final boolean withToken, final long targetTypeId)
|
||||
throws JSONException {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("[");
|
||||
@@ -487,8 +489,8 @@ public abstract class JsonBuilder {
|
||||
});
|
||||
|
||||
result.put(new JSONObject().put("name", type.getName()).put("description", type.getDescription())
|
||||
.put("id", Long.MAX_VALUE).put("colour", type.getColour()).put("createdAt", "0").put("updatedAt", "0")
|
||||
.put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
|
||||
.put("id", Long.MAX_VALUE).put("colour", type.getColour()).put("createdAt", "0")
|
||||
.put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
|
||||
.put("distributionsets", dsTypes));
|
||||
|
||||
}
|
||||
@@ -510,11 +512,10 @@ public abstract class JsonBuilder {
|
||||
}
|
||||
});
|
||||
|
||||
JSONObject json = new JSONObject().put("name", type.getName()).put("description", type.getDescription())
|
||||
final JSONObject json = new JSONObject().put("name", type.getName()).put("description", type.getDescription())
|
||||
.put("colour", type.getColour());
|
||||
|
||||
if(dsTypes.length() != 0)
|
||||
{
|
||||
if (dsTypes.length() != 0) {
|
||||
json.put("compatibledistributionsettypes", dsTypes);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ final class DdiApiModelProperties {
|
||||
|
||||
static final String CHUNK_TYPE = "Type of the chunk, e.g. firmware, bundle, app. In update server mapped to Software Module Type.";
|
||||
|
||||
static final String SOFTWARE_MODUL_TYPE = "type of the software module, e.g. firmware, bundle, app";
|
||||
static final String SOFTWARE_MODULE_TYPE = "type of the software module, e.g. firmware, bundle, app";
|
||||
|
||||
static final String SOFTWARE_MODULE_VERSION = "version of the software module";
|
||||
|
||||
@@ -68,7 +68,7 @@ final class DdiApiModelProperties {
|
||||
|
||||
static final String SOFTWARE_MODULE_ARTIFACT_LINKS = "artifact links of the software module";
|
||||
|
||||
static final String SOFTWARE_MODUL_ID = "id of the software module";
|
||||
static final String SOFTWARE_MODULE_ID = "id of the software module";
|
||||
|
||||
static final String CHUNK_VERSION = "software version of the chunk";
|
||||
|
||||
@@ -102,15 +102,15 @@ final class DdiApiModelProperties {
|
||||
|
||||
static final String CHUNK = "Software chunks of an update. In server mapped by Software Module.";
|
||||
|
||||
static final String SOFTWARE_MODUL = "software modules of an update";
|
||||
static final String SOFTWARE_MODULE = "software modules of an update";
|
||||
|
||||
static final String ARTIFACT = "artifact modules of an update";
|
||||
|
||||
static final String FILENAME = "file name of artifact";
|
||||
|
||||
static final String TARGET_CONFIG_DATA = "Link which is provided whenever the provisioning target or device is supposed "
|
||||
+ "to push its configuration data (aka. \"controller attributes\") to the server. Only shown for the initial "
|
||||
+ "configuration, after a successful update action, or if requested explicitly (e.g. via the management UI).";
|
||||
+ "to push its configuration data (aka. \"controller attributes\") to the server. Only shown for the initial "
|
||||
+ "configuration, after a successful update action, or if requested explicitly (e.g. via the management UI).";
|
||||
|
||||
static final String ARTIFACT_HASHES_SHA1 = "SHA1 hash of the artifact in Base 16 format";
|
||||
static final String ARTIFACT_HASHES_MD5 = "MD5 hash of the artifact";
|
||||
|
||||
@@ -183,7 +183,9 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID),
|
||||
parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID_CANCELED)),
|
||||
requestFields(optionalRequestFieldWithPath("id").description(DdiApiModelProperties.FEEDBACK_ACTION_ID),
|
||||
requestFields(
|
||||
optionalRequestFieldWithPath("id")
|
||||
.description(DdiApiModelProperties.FEEDBACK_ACTION_ID),
|
||||
requestFieldWithPath("status").description(DdiApiModelProperties.TARGET_STATUS),
|
||||
requestFieldWithPath("status.execution")
|
||||
.description(DdiApiModelProperties.TARGET_EXEC_STATUS).type("enum")
|
||||
@@ -386,7 +388,9 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID),
|
||||
parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID)),
|
||||
|
||||
requestFields(optionalRequestFieldWithPath("id").description(DdiApiModelProperties.FEEDBACK_ACTION_ID),
|
||||
requestFields(
|
||||
optionalRequestFieldWithPath("id")
|
||||
.description(DdiApiModelProperties.FEEDBACK_ACTION_ID),
|
||||
requestFieldWithPath("status").description(DdiApiModelProperties.TARGET_STATUS),
|
||||
requestFieldWithPath("status.execution")
|
||||
.description(DdiApiModelProperties.TARGET_EXEC_STATUS).type("enum")
|
||||
@@ -428,7 +432,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID),
|
||||
parameterWithName("moduleId").description(DdiApiModelProperties.SOFTWARE_MODUL_ID)),
|
||||
parameterWithName("moduleId").description(DdiApiModelProperties.SOFTWARE_MODULE_ID)),
|
||||
responseFields(fieldWithPath("[]filename").description(DdiApiModelProperties.ARTIFACTS),
|
||||
fieldWithPath("[]hashes").description(DdiApiModelProperties.ARTIFACTS),
|
||||
fieldWithPath("[]hashes.sha1").description(DdiApiModelProperties.ARTIFACT_HASHES_SHA1),
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class MgmtApiModelProperties {
|
||||
|
||||
// software module
|
||||
public static final String SM_TYPE = "The software module type " + ApiModelPropertiesGeneric.ENDING;
|
||||
public static final String ENCRYPTED = "Encryption flag, used to identify that artifacts should be encrypted upon upload.";
|
||||
public static final String ARTIFACT_HASHES = "Hashes of the artifact.";
|
||||
public static final String ARTIFACT_SIZE = "Size of the artifact.";
|
||||
public static final String ARTIFACT_PROVIDED_FILE = "Binary of file.";
|
||||
|
||||
@@ -84,6 +84,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
|
||||
fieldWithPath("content[].name").description(ApiModelPropertiesGeneric.NAME),
|
||||
fieldWithPath("content[].description").description(ApiModelPropertiesGeneric.DESCRPTION),
|
||||
fieldWithPath("content[].vendor").description(MgmtApiModelProperties.VENDOR),
|
||||
fieldWithPath("content[].encrypted").description(MgmtApiModelProperties.ENCRYPTED),
|
||||
fieldWithPath("content[].createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
|
||||
fieldWithPath("content[].createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
|
||||
fieldWithPath("content[].lastModifiedBy")
|
||||
@@ -141,6 +142,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
|
||||
fieldWithPath("[].name").description(ApiModelPropertiesGeneric.NAME),
|
||||
fieldWithPath("[].description").description(ApiModelPropertiesGeneric.DESCRPTION),
|
||||
fieldWithPath("[].vendor").description(MgmtApiModelProperties.VENDOR),
|
||||
fieldWithPath("[].encrypted").description(MgmtApiModelProperties.ENCRYPTED),
|
||||
fieldWithPath("[].deleted").description(ApiModelPropertiesGeneric.DELETED),
|
||||
fieldWithPath("[].createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
|
||||
fieldWithPath("[].createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
|
||||
@@ -188,6 +190,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
|
||||
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY),
|
||||
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT),
|
||||
fieldWithPath("vendor").description(MgmtApiModelProperties.VENDOR),
|
||||
fieldWithPath("encrypted").description(MgmtApiModelProperties.ENCRYPTED),
|
||||
fieldWithPath("deleted").description(ApiModelPropertiesGeneric.DELETED),
|
||||
fieldWithPath("type").description(MgmtApiModelProperties.SM_TYPE),
|
||||
fieldWithPath("version").description(MgmtApiModelProperties.VERSION),
|
||||
@@ -227,6 +230,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
|
||||
fieldWithPath("type").description(MgmtApiModelProperties.SM_TYPE),
|
||||
fieldWithPath("version").description(MgmtApiModelProperties.VERSION),
|
||||
fieldWithPath("vendor").description(MgmtApiModelProperties.VENDOR),
|
||||
fieldWithPath("encrypted").description(MgmtApiModelProperties.ENCRYPTED),
|
||||
fieldWithPath("deleted").description(ApiModelPropertiesGeneric.DELETED),
|
||||
fieldWithPath("_links.self").ignored(),
|
||||
fieldWithPath("_links.type").description(MgmtApiModelProperties.SM_TYPE),
|
||||
@@ -279,7 +283,9 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
|
||||
final byte random[] = RandomStringUtils.random(5).getBytes();
|
||||
final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, random);
|
||||
|
||||
mockMvc.perform(fileUpload(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts", sm.getId()).file(file))
|
||||
mockMvc.perform(
|
||||
fileUpload(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts",
|
||||
sm.getId()).file(file))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andDo(this.document.document(
|
||||
@@ -313,7 +319,8 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
|
||||
final byte random[] = RandomStringUtils.random(5).getBytes();
|
||||
final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, random);
|
||||
|
||||
mockMvc.perform(fileUpload(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts",
|
||||
mockMvc.perform(
|
||||
fileUpload(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts",
|
||||
sm.getId()).file(file).param("filename", "filename").param("file", "s")
|
||||
.param("md5sum", "md5sum").param("sha1sum", "sha1sum"))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest())
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.ui.common.data.suppliers.TargetFilterStateDataSupplie
|
||||
import org.eclipse.hawkbit.ui.common.data.suppliers.TargetManagementStateDataSupplier;
|
||||
import org.eclipse.hawkbit.ui.common.data.suppliers.TargetManagementStateDataSupplierImpl;
|
||||
import org.eclipse.hawkbit.ui.error.HawkbitUIErrorHandler;
|
||||
import org.eclipse.hawkbit.ui.error.extractors.ArtifactEncryptionErrorExtractor;
|
||||
import org.eclipse.hawkbit.ui.error.extractors.ConstraintViolationErrorExtractor;
|
||||
import org.eclipse.hawkbit.ui.error.extractors.EntityNotFoundErrorExtractor;
|
||||
import org.eclipse.hawkbit.ui.error.extractors.IncompatibleTargetTypeErrorExtractor;
|
||||
@@ -182,6 +183,18 @@ public class MgmtUiConfiguration {
|
||||
return new InvalidDistributionSetErrorExtractor(i18n);
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Artifact ecnryption operations Error details extractor bean.
|
||||
*
|
||||
* @param i18n
|
||||
* VaadinMessageSource
|
||||
* @return UI Artifact ecnryption operations Error details extractor
|
||||
*/
|
||||
@Bean
|
||||
UiErrorDetailsExtractor artifactEncryptionErrorExtractor(final VaadinMessageSource i18n) {
|
||||
return new ArtifactEncryptionErrorExtractor(i18n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vaadin4Spring servlet bean.
|
||||
*
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.ui.common.CommonUiDependencies;
|
||||
import org.eclipse.hawkbit.ui.common.builder.GridComponentBuilder;
|
||||
@@ -33,11 +33,14 @@ import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.UINotification;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.vaadin.icons.VaadinIcons;
|
||||
import com.vaadin.server.FileDownloader;
|
||||
import com.vaadin.server.StreamResource;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.UI;
|
||||
|
||||
/**
|
||||
* Artifact Details grid which is shown on the Upload View.
|
||||
@@ -45,6 +48,8 @@ import com.vaadin.ui.Button;
|
||||
public class ArtifactDetailsGrid extends AbstractGrid<ProxyArtifact, Long> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ArtifactDetailsGrid.class);
|
||||
|
||||
private static final String ARTIFACT_NAME_ID = "artifactName";
|
||||
private static final String ARTIFACT_SIZE_ID = "artifactSize";
|
||||
private static final String ARTIFACT_MODIFIED_DATE_ID = "artifactModifiedDate";
|
||||
@@ -60,6 +65,8 @@ public class ArtifactDetailsGrid extends AbstractGrid<ProxyArtifact, Long> {
|
||||
private final transient DeleteSupport<ProxyArtifact> artifactDeleteSupport;
|
||||
private final transient MasterEntitySupport<ProxySoftwareModule> masterEntitySupport;
|
||||
|
||||
private boolean artifactsEncrypted;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -82,7 +89,8 @@ public class ArtifactDetailsGrid extends AbstractGrid<ProxyArtifact, Long> {
|
||||
new FilterSupport<>(new ArtifactDataProvider(artifactManagement, new ArtifactToProxyArtifactMapper())));
|
||||
initFilterMappings();
|
||||
|
||||
this.masterEntitySupport = new MasterEntitySupport<>(getFilterSupport());
|
||||
this.masterEntitySupport = new MasterEntitySupport<>(getFilterSupport(), null,
|
||||
sm -> artifactsEncrypted = sm != null && sm.isEncrypted());
|
||||
|
||||
init();
|
||||
}
|
||||
@@ -107,9 +115,8 @@ public class ArtifactDetailsGrid extends AbstractGrid<ProxyArtifact, Long> {
|
||||
.map(ProxyIdentifiableEntity::getId).collect(Collectors.toList());
|
||||
artifactToBeDeletedIds.forEach(artifactManagement::delete);
|
||||
|
||||
eventBus.publish(EventTopics.ENTITY_MODIFIED, this,
|
||||
new EntityModifiedEventPayload(EntityModifiedEventType.ENTITY_UPDATED, ProxySoftwareModule.class,
|
||||
getMasterEntitySupport().getMasterId()));
|
||||
eventBus.publish(EventTopics.ENTITY_MODIFIED, this, new EntityModifiedEventPayload(
|
||||
EntityModifiedEventType.ENTITY_UPDATED, ProxySoftwareModule.class, masterEntitySupport.getMasterId()));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -171,12 +178,20 @@ public class ArtifactDetailsGrid extends AbstractGrid<ProxyArtifact, Long> {
|
||||
|
||||
private void attachFileDownloader(final ProxyArtifact artifact, final Button downloadButton) {
|
||||
final StreamResource artifactStreamResource = new StreamResource(() -> artifactManagement
|
||||
.loadArtifactBinary(artifact.getSha1Hash()).map(AbstractDbArtifact::getFileInputStream).orElse(null),
|
||||
artifact.getFilename());
|
||||
.loadArtifactBinary(artifact.getSha1Hash(), masterEntitySupport.getMasterId(), artifactsEncrypted)
|
||||
.map(DbArtifact::getFileInputStream).orElse(null), artifact.getFilename());
|
||||
|
||||
final FileDownloader fileDownloader = new FileDownloader(artifactStreamResource);
|
||||
fileDownloader.setErrorHandler(event -> notification
|
||||
.displayValidationError(i18n.getMessage(UIMessageIdProvider.ARTIFACT_DOWNLOAD_FAILURE_MSG)));
|
||||
fileDownloader.setErrorHandler(event -> {
|
||||
LOG.error("Download failed for artifact with id {}, filename {}", artifact.getId(), artifact.getFilename(),
|
||||
event.getThrowable());
|
||||
notification.displayValidationError(i18n.getMessage(UIMessageIdProvider.ARTIFACT_DOWNLOAD_FAILURE_MSG));
|
||||
UI.getCurrent().access(() -> {
|
||||
// give error details extractors a chance to process specific
|
||||
// error
|
||||
throw new DownloadException(event.getThrowable());
|
||||
});
|
||||
});
|
||||
|
||||
fileDownloader.extend(downloadButton);
|
||||
}
|
||||
@@ -225,4 +240,12 @@ public class ArtifactDetailsGrid extends AbstractGrid<ProxyArtifact, Long> {
|
||||
public MasterEntitySupport<ProxySoftwareModule> getMasterEntitySupport() {
|
||||
return masterEntitySupport;
|
||||
}
|
||||
|
||||
private static class DownloadException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DownloadException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.artifacts.smtable;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.builder.SoftwareModuleCreate;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
@@ -62,6 +63,13 @@ public class AddSmWindowController
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void adaptLayout(final ProxySoftwareModule proxyEntity) {
|
||||
if (!ArtifactEncryptionService.getInstance().isEncryptionSupported()) {
|
||||
layout.disableEncryptionField();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProxySoftwareModule buildEntityFromProxy(final ProxySoftwareModule proxyEntity) {
|
||||
// We ignore the method parameter, because we are interested in the
|
||||
@@ -73,7 +81,7 @@ public class AddSmWindowController
|
||||
protected SoftwareModule persistEntityInRepository(final ProxySoftwareModule entity) {
|
||||
final SoftwareModuleCreate smCreate = getEntityFactory().softwareModule().create()
|
||||
.type(entity.getTypeInfo().getKey()).name(entity.getName()).version(entity.getVersion())
|
||||
.vendor(entity.getVendor()).description(entity.getDescription());
|
||||
.vendor(entity.getVendor()).description(entity.getDescription()).encrypted(entity.isEncrypted());
|
||||
|
||||
return smManagement.create(smCreate);
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ public class SmWindowBuilder extends AbstractEntityWindowBuilder<ProxySoftwareMo
|
||||
*/
|
||||
@Override
|
||||
public Window getWindowForAdd() {
|
||||
return getWindowForNewEntity(
|
||||
new AddSmWindowController(uiDependencies, smManagement, new SmWindowLayout(getI18n(), smTypeManagement), view));
|
||||
return getWindowForNewEntity(new AddSmWindowController(uiDependencies, smManagement,
|
||||
new SmWindowLayout(getI18n(), smTypeManagement), view));
|
||||
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class SmWindowBuilder extends AbstractEntityWindowBuilder<ProxySoftwareMo
|
||||
*/
|
||||
@Override
|
||||
public Window getWindowForUpdate(final ProxySoftwareModule proxySm) {
|
||||
return getWindowForEntity(proxySm,
|
||||
new UpdateSmWindowController(uiDependencies, smManagement, new SmWindowLayout(getI18n(), smTypeManagement)));
|
||||
return getWindowForEntity(proxySm, new UpdateSmWindowController(uiDependencies, smManagement,
|
||||
new SmWindowLayout(getI18n(), smTypeManagement)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
package org.eclipse.hawkbit.ui.artifacts.smtable;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.ui.common.AbstractEntityWindowLayout;
|
||||
import org.eclipse.hawkbit.ui.common.data.mappers.TypeToTypeInfoMapper;
|
||||
import org.eclipse.hawkbit.ui.common.data.providers.SoftwareModuleTypeDataProvider;
|
||||
@@ -17,6 +16,7 @@ import org.eclipse.hawkbit.ui.common.data.proxies.ProxySoftwareModule;
|
||||
import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTypeInfo;
|
||||
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.ComponentContainer;
|
||||
import com.vaadin.ui.FormLayout;
|
||||
@@ -34,6 +34,7 @@ public class SmWindowLayout extends AbstractEntityWindowLayout<ProxySoftwareModu
|
||||
private final TextField smVersion;
|
||||
private final TextField smVendor;
|
||||
private final TextArea smDescription;
|
||||
private final CheckBox artifactEncryption;
|
||||
|
||||
/**
|
||||
* Constructor for AbstractTagWindowLayout
|
||||
@@ -47,7 +48,7 @@ public class SmWindowLayout extends AbstractEntityWindowLayout<ProxySoftwareModu
|
||||
super();
|
||||
|
||||
final SoftwareModuleTypeDataProvider<ProxyTypeInfo> smTypeDataProvider = new SoftwareModuleTypeDataProvider<>(
|
||||
smTypeManagement, new TypeToTypeInfoMapper<SoftwareModuleType>());
|
||||
smTypeManagement, new TypeToTypeInfoMapper<>());
|
||||
this.smComponentBuilder = new SmWindowLayoutComponentBuilder(i18n, smTypeDataProvider);
|
||||
|
||||
this.smTypeSelect = smComponentBuilder.createSoftwareModuleTypeCombo(binder);
|
||||
@@ -55,6 +56,7 @@ public class SmWindowLayout extends AbstractEntityWindowLayout<ProxySoftwareModu
|
||||
this.smVersion = smComponentBuilder.createVersionField(binder);
|
||||
this.smVendor = smComponentBuilder.createVendorField(binder);
|
||||
this.smDescription = smComponentBuilder.createDescription(binder);
|
||||
this.artifactEncryption = smComponentBuilder.createArtifactEncryptionCheck(binder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +81,8 @@ public class SmWindowLayout extends AbstractEntityWindowLayout<ProxySoftwareModu
|
||||
|
||||
smWindowLayout.addComponent(smDescription);
|
||||
|
||||
smWindowLayout.addComponent(artifactEncryption);
|
||||
|
||||
return smWindowLayout;
|
||||
}
|
||||
|
||||
@@ -102,4 +106,11 @@ public class SmWindowLayout extends AbstractEntityWindowLayout<ProxySoftwareModu
|
||||
public void disableVersionField() {
|
||||
smVersion.setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the software module artifact encryption
|
||||
*/
|
||||
public void disableEncryptionField() {
|
||||
artifactEncryption.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||
|
||||
import com.vaadin.data.Binder;
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.TextArea;
|
||||
import com.vaadin.ui.TextField;
|
||||
@@ -28,6 +29,7 @@ import com.vaadin.ui.TextField;
|
||||
public class SmWindowLayoutComponentBuilder {
|
||||
|
||||
public static final String TEXTFIELD_VENDOR = "textfield.vendor";
|
||||
public static final String ARTIFACT_ENCRYPTION = "artifact.encryption";
|
||||
|
||||
private final VaadinMessageSource i18n;
|
||||
private final SoftwareModuleTypeDataProvider<ProxyTypeInfo> smTypeDataProvider;
|
||||
@@ -56,7 +58,8 @@ public class SmWindowLayoutComponentBuilder {
|
||||
*/
|
||||
public ComboBox<ProxyTypeInfo> createSoftwareModuleTypeCombo(final Binder<ProxySoftwareModule> binder) {
|
||||
return FormComponentBuilder
|
||||
.createTypeCombo(binder, smTypeDataProvider, i18n, UIComponentIdProvider.SW_MODULE_TYPE, true).getComponent();
|
||||
.createTypeCombo(binder, smTypeDataProvider, i18n, UIComponentIdProvider.SW_MODULE_TYPE, true)
|
||||
.getComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,4 +116,18 @@ public class SmWindowLayoutComponentBuilder {
|
||||
return FormComponentBuilder
|
||||
.createDescriptionInput(binder, i18n, UIComponentIdProvider.ADD_SW_MODULE_DESCRIPTION).getComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create checkbox for artifact encryption
|
||||
*
|
||||
* @param binder
|
||||
* binder the input will be bound to
|
||||
*
|
||||
* @return input component
|
||||
*/
|
||||
public CheckBox createArtifactEncryptionCheck(final Binder<ProxySoftwareModule> binder) {
|
||||
return FormComponentBuilder.createCheckBox(i18n.getMessage(ARTIFACT_ENCRYPTION),
|
||||
UIComponentIdProvider.ARTIFACT_ENCRYPTION_ID, binder, ProxySoftwareModule::isEncrypted,
|
||||
ProxySoftwareModule::setEncrypted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public class UpdateSmWindowController
|
||||
sm.setVersion(proxyEntity.getVersion());
|
||||
sm.setVendor(proxyEntity.getVendor());
|
||||
sm.setDescription(proxyEntity.getDescription());
|
||||
sm.setEncrypted(proxyEntity.isEncrypted());
|
||||
|
||||
nameBeforeEdit = proxyEntity.getName();
|
||||
versionBeforeEdit = proxyEntity.getVersion();
|
||||
@@ -78,6 +79,7 @@ public class UpdateSmWindowController
|
||||
layout.disableSmTypeSelect();
|
||||
layout.disableNameField();
|
||||
layout.disableVersionField();
|
||||
layout.disableEncryptionField();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,8 @@ import java.util.concurrent.locks.Lock;
|
||||
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.ArtifactEncryptionFailedException;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.FileSizeQuotaExceededException;
|
||||
@@ -137,6 +139,10 @@ public abstract class AbstractFileTransferHandler implements Serializable {
|
||||
interruptUploadAndSetReason(i18n.getMessage("message.uploadedfile.illegalFilename"));
|
||||
}
|
||||
|
||||
protected void interruptUploadDueToEncryptionError() {
|
||||
interruptUploadAndSetReason(i18n.getMessage("message.encryption.failed"));
|
||||
}
|
||||
|
||||
protected boolean isFileAlreadyContainedInSoftwareModule(final FileUploadId newFileUploadId,
|
||||
final SoftwareModule softwareModule) {
|
||||
for (final Artifact artifact : softwareModule.getArtifacts()) {
|
||||
@@ -215,7 +221,7 @@ public abstract class AbstractFileTransferHandler implements Serializable {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (final IOException e1) {
|
||||
LOG.warn("Closing output stream caused an exception {}", e1);
|
||||
LOG.warn("Closing output stream caused by", e1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +232,7 @@ public abstract class AbstractFileTransferHandler implements Serializable {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (final IOException e1) {
|
||||
LOG.warn("Closing input stream caused an exception {}", e1);
|
||||
LOG.warn("Closing input stream caused by", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,13 +282,20 @@ public abstract class AbstractFileTransferHandler implements Serializable {
|
||||
streamToRepository();
|
||||
} catch (final FileSizeQuotaExceededException e) {
|
||||
interruptUploadDueToFileSizeQuotaExceeded(e.getExceededQuotaValueString());
|
||||
publishUploadFailedAndFinishedEvent(fileUploadId);
|
||||
LOG.debug("Upload failed due to file size quota exceeded:", e);
|
||||
} catch (final StorageQuotaExceededException e) {
|
||||
interruptUploadDueToStorageQuotaExceeded(e.getExceededQuotaValueString());
|
||||
publishUploadFailedAndFinishedEvent(fileUploadId);
|
||||
LOG.debug("Upload failed due to storage quota exceeded:", e);
|
||||
} catch (final AssignmentQuotaExceededException e) {
|
||||
interruptUploadDueToAssignmentQuotaExceeded();
|
||||
publishUploadFailedAndFinishedEvent(fileUploadId);
|
||||
LOG.debug("Upload failed due to assignment quota exceeded:", e);
|
||||
} catch (final ArtifactEncryptionUnsupportedException | ArtifactEncryptionFailedException e) {
|
||||
interruptUploadDueToEncryptionError();
|
||||
publishUploadFailedAndFinishedEvent(fileUploadId);
|
||||
LOG.warn("Upload failed due to encryption error", e);
|
||||
} catch (final RuntimeException e) {
|
||||
interruptUploadDueToUploadFailed();
|
||||
publishUploadFailedAndFinishedEvent(fileUploadId);
|
||||
@@ -297,25 +310,28 @@ public abstract class AbstractFileTransferHandler implements Serializable {
|
||||
if (fileUploadId == null) {
|
||||
throw new ArtifactUploadFailedException();
|
||||
}
|
||||
|
||||
final String filename = fileUploadId.getFilename();
|
||||
LOG.debug("Transfering file {} directly to repository", filename);
|
||||
final Artifact artifact = uploadArtifact(filename);
|
||||
|
||||
if (isUploadInterrupted()) {
|
||||
LOG.warn("Upload of {} was interrupted", filename);
|
||||
handleUploadFailure(artifact);
|
||||
publishUploadFinishedEvent(fileUploadId);
|
||||
return;
|
||||
}
|
||||
|
||||
publishUploadSucceeded(fileUploadId, artifact.getSize());
|
||||
publishUploadFinishedEvent(fileUploadId);
|
||||
publishArtifactsChanged(fileUploadId);
|
||||
}
|
||||
|
||||
private Artifact uploadArtifact(final String filename) {
|
||||
LOG.debug("Transfering file {} directly to repository", filename);
|
||||
try {
|
||||
return artifactManagement.create(new ArtifactUpload(inputStream, fileUploadId.getSoftwareModuleId(),
|
||||
filename, null, null, null, true, mimeType, -1));
|
||||
} catch (final ArtifactUploadFailedException | InvalidSHA1HashException | InvalidMD5HashException e) {
|
||||
} catch (final InvalidSHA1HashException | InvalidMD5HashException e) {
|
||||
throw new ArtifactUploadFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
package org.eclipse.hawkbit.ui.artifacts.upload;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
|
||||
|
||||
@@ -21,19 +20,14 @@ import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
|
||||
*
|
||||
*/
|
||||
public class FileUploadId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String filename;
|
||||
|
||||
private final Long softwareModuleId;
|
||||
private final String softwareModuleName;
|
||||
|
||||
private final String softwareModuleVersion;
|
||||
|
||||
private Long softwareModuleId;
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FileUploadId} instance.
|
||||
*
|
||||
@@ -44,36 +38,25 @@ public class FileUploadId implements Serializable {
|
||||
*/
|
||||
public FileUploadId(final String filename, final SoftwareModule softwareModule) {
|
||||
this.filename = filename;
|
||||
this.softwareModuleId = softwareModule.getId();
|
||||
this.softwareModuleName = softwareModule.getName();
|
||||
this.softwareModuleVersion = softwareModule.getVersion();
|
||||
this.softwareModuleId = softwareModule.getId();
|
||||
this.id = createFileUploadIdString(filename, softwareModuleName, softwareModuleVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FileUploadId} instance.
|
||||
*
|
||||
* @param filename
|
||||
* the name of the file
|
||||
* @param softwareModuleName
|
||||
* the name of a {@link SoftwareModule} for which the file is
|
||||
* uploaded
|
||||
* @param softwareModuleVersion
|
||||
* the version of a {@link SoftwareModule} for which the file is
|
||||
* uploaded
|
||||
*/
|
||||
public FileUploadId(final String filename, final String softwareModuleName, final String softwareModuleVersion) {
|
||||
this.filename = filename;
|
||||
this.softwareModuleName = softwareModuleName;
|
||||
this.softwareModuleVersion = softwareModuleVersion;
|
||||
this.id = createFileUploadIdString(filename, softwareModuleName, softwareModuleVersion);
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
private static String createFileUploadIdString(final String filename, final String softwareModuleName,
|
||||
final String softwareModuleVersion) {
|
||||
return new StringBuilder(filename).append(":")
|
||||
.append(HawkbitCommonUtil.getFormattedNameVersion(softwareModuleName, softwareModuleVersion))
|
||||
.toString();
|
||||
public Long getSoftwareModuleId() {
|
||||
return softwareModuleId;
|
||||
}
|
||||
|
||||
public String getSoftwareModuleName() {
|
||||
return softwareModuleName;
|
||||
}
|
||||
|
||||
public String getSoftwareModuleVersion() {
|
||||
return softwareModuleVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,52 +71,21 @@ public class FileUploadId implements Serializable {
|
||||
return false;
|
||||
}
|
||||
final FileUploadId other = (FileUploadId) obj;
|
||||
return new EqualsBuilder().append(id, other.id).isEquals();
|
||||
return Objects.equals(this.getFilename(), other.getFilename())
|
||||
&& Objects.equals(this.getSoftwareModuleId(), other.getSoftwareModuleId())
|
||||
&& Objects.equals(this.getSoftwareModuleName(), other.getSoftwareModuleName())
|
||||
&& Objects.equals(this.getSoftwareModuleVersion(), other.getSoftwareModuleVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder().append(id).toHashCode();
|
||||
return Objects.hash(getFilename(), getSoftwareModuleId(), getSoftwareModuleName(), getSoftwareModuleVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the uploaded file name
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the software module name
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getSoftwareModuleName() {
|
||||
return softwareModuleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the software module version
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getSoftwareModuleVersion() {
|
||||
return softwareModuleVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the software module ID
|
||||
*
|
||||
* @return Long
|
||||
*/
|
||||
public Long getSoftwareModuleId() {
|
||||
return softwareModuleId;
|
||||
return new StringBuilder(filename).append(":")
|
||||
.append(HawkbitCommonUtil.getFormattedNameVersion(softwareModuleName, softwareModuleVersion))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,9 @@ public final class FormComponentBuilder {
|
||||
* setter for the binder
|
||||
* @return the bound box
|
||||
*/
|
||||
public static <T> CheckBox getCheckBox(final String id, final Binder<T> binder,
|
||||
public static <T> CheckBox createCheckBox(final String id, final Binder<T> binder,
|
||||
final ValueProvider<T, Boolean> getter, final Setter<T, Boolean> setter) {
|
||||
return getCheckBox(null, id, binder, getter, setter);
|
||||
return createCheckBox(null, id, binder, getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,7 +399,7 @@ public final class FormComponentBuilder {
|
||||
* setter for the binder
|
||||
* @return the bound box
|
||||
*/
|
||||
public static <T> CheckBox getCheckBox(final String caption, final String id, final Binder<T> binder,
|
||||
public static <T> CheckBox createCheckBox(final String caption, final String id, final Binder<T> binder,
|
||||
final ValueProvider<T, Boolean> getter, final Setter<T, Boolean> setter) {
|
||||
final CheckBox checkBox;
|
||||
if (StringUtils.isEmpty(caption)) {
|
||||
|
||||
@@ -31,6 +31,7 @@ public class SoftwareModuleToProxyMapper
|
||||
proxySoftwareModule.setNameAndVersion(
|
||||
HawkbitCommonUtil.concatStrings(":", softwareModule.getName(), softwareModule.getVersion()));
|
||||
proxySoftwareModule.setVendor(softwareModule.getVendor());
|
||||
proxySoftwareModule.setEncrypted(softwareModule.isEncrypted());
|
||||
|
||||
final SoftwareModuleType type = softwareModule.getType();
|
||||
final ProxyTypeInfo typeInfo = new ProxyTypeInfo(type.getId(), type.getName(), type.getKey());
|
||||
|
||||
@@ -29,6 +29,8 @@ public class ProxySoftwareModule extends ProxyNamedEntity implements VersionAwar
|
||||
|
||||
private boolean assigned;
|
||||
|
||||
private boolean encrypted;
|
||||
|
||||
/**
|
||||
* Gets the software module vendor
|
||||
*
|
||||
@@ -129,4 +131,12 @@ public class ProxySoftwareModule extends ProxyNamedEntity implements VersionAwar
|
||||
public void setVersion(final String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public boolean isEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
public void setEncrypted(final boolean encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class MetaDataAddUpdateWindowLayoutComponentBuilder {
|
||||
* @return Target field CheckBox
|
||||
*/
|
||||
public CheckBox createVisibleForTargetsField(final Binder<ProxyMetaData> binder) {
|
||||
return FormComponentBuilder.getCheckBox(i18n.getMessage(TARGET_VISIBLE),
|
||||
return FormComponentBuilder.createCheckBox(i18n.getMessage(TARGET_VISIBLE),
|
||||
UIComponentIdProvider.METADATA_TARGET_VISIBLE_ID, binder, ProxyMetaData::isVisibleForTargets,
|
||||
ProxyMetaData::setVisibleForTargets);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@ public class SoftwareModuleDetails extends AbstractGridDetailsLayout<ProxySoftwa
|
||||
* @param smMetaDataWindowBuilder
|
||||
* SmMetaDataWindowBuilder
|
||||
*/
|
||||
public SoftwareModuleDetails(final CommonUiDependencies uiDependencies, final SoftwareModuleManagement softwareManagement,
|
||||
public SoftwareModuleDetails(final CommonUiDependencies uiDependencies,
|
||||
final SoftwareModuleManagement softwareManagement,
|
||||
final SoftwareModuleTypeManagement softwareModuleTypeManagement,
|
||||
final SmMetaDataWindowBuilder smMetaDataWindowBuilder) {
|
||||
super(uiDependencies.getI18n());
|
||||
@@ -93,6 +94,10 @@ public class SoftwareModuleDetails extends AbstractGridDetailsLayout<ProxySoftwa
|
||||
: i18n.getMessage("label.multiAssign.type")));
|
||||
});
|
||||
|
||||
details.add(new ProxyKeyValueDetails(UIComponentIdProvider.SWM_DTLS_ENCRYPTION,
|
||||
i18n.getMessage("label.artifact.encryption"),
|
||||
entity.isEncrypted() ? i18n.getMessage("label.enabled") : i18n.getMessage("label.disabled")));
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
package org.eclipse.hawkbit.ui.common.grid.support;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.hawkbit.ui.common.data.proxies.ProxyIdentifiableEntity;
|
||||
@@ -24,6 +25,7 @@ import org.eclipse.hawkbit.ui.common.layout.MasterEntityAwareComponent;
|
||||
public class MasterEntitySupport<M extends ProxyIdentifiableEntity> implements MasterEntityAwareComponent<M> {
|
||||
private final FilterSupport<?, ?> filterSupport;
|
||||
private final Function<M, ?> masterEntityToFilterMapper;
|
||||
private final Consumer<M> postMasterChangeCallback;
|
||||
|
||||
private Long masterId;
|
||||
|
||||
@@ -47,8 +49,24 @@ public class MasterEntitySupport<M extends ProxyIdentifiableEntity> implements M
|
||||
*/
|
||||
public MasterEntitySupport(final FilterSupport<?, ?> filterSupport,
|
||||
final Function<M, ?> masterEntityToFilterMapper) {
|
||||
this(filterSupport, masterEntityToFilterMapper, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for MasterEntitySupport
|
||||
*
|
||||
* @param filterSupport
|
||||
* Filter support
|
||||
* @param masterEntityToFilterMapper
|
||||
* Master entity to filter mapper
|
||||
* @param postMasterChangeCallback
|
||||
* Callback called after master entity change
|
||||
*/
|
||||
public MasterEntitySupport(final FilterSupport<?, ?> filterSupport, final Function<M, ?> masterEntityToFilterMapper,
|
||||
final Consumer<M> postMasterChangeCallback) {
|
||||
this.filterSupport = filterSupport;
|
||||
this.masterEntityToFilterMapper = masterEntityToFilterMapper;
|
||||
this.postMasterChangeCallback = postMasterChangeCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,6 +78,10 @@ public class MasterEntitySupport<M extends ProxyIdentifiableEntity> implements M
|
||||
filterSupport.updateFilter(FilterType.MASTER, getMasterEntityFilter(masterEntity));
|
||||
|
||||
masterId = masterEntity != null ? masterEntity.getId() : null;
|
||||
|
||||
if (postMasterChangeCallback != null) {
|
||||
postMasterChangeCallback.accept(masterEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private Object getMasterEntityFilter(final M masterEntity) {
|
||||
|
||||
@@ -82,6 +82,6 @@ public class SmTypeSelectedGrid extends Grid<ProxyType> {
|
||||
binder.addValueChangeListener(event -> mandatoryPropertyChangedCallback.run());
|
||||
|
||||
final String id = "selected.sm.type." + smType.getId();
|
||||
return FormComponentBuilder.getCheckBox(id, binder, ProxyType::isMandatory, ProxyType::setMandatory);
|
||||
return FormComponentBuilder.createCheckBox(id, binder, ProxyType::isMandatory, ProxyType::setMandatory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class DsWindowLayoutComponentBuilder {
|
||||
* @return Migration step required checkbox
|
||||
*/
|
||||
public CheckBox createMigrationStepField(final Binder<ProxyDistributionSet> binder) {
|
||||
final CheckBox migrationRequired = FormComponentBuilder.getCheckBox(i18n.getMessage(MIGRATION_STEP),
|
||||
final CheckBox migrationRequired = FormComponentBuilder.createCheckBox(i18n.getMessage(MIGRATION_STEP),
|
||||
UIComponentIdProvider.DIST_ADD_MIGRATION_CHECK, binder, ProxyDistributionSet::isRequiredMigrationStep,
|
||||
ProxyDistributionSet::setRequiredMigrationStep);
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.error.extractors;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionFailedException;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.eclipse.hawkbit.ui.error.UiErrorDetails;
|
||||
import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||
|
||||
/**
|
||||
* UI error details extractor for {@link ArtifactEncryptionUnsupportedException}
|
||||
* and {@link ArtifactEncryptionFailedException}.
|
||||
*/
|
||||
public class ArtifactEncryptionErrorExtractor extends AbstractSingleUiErrorDetailsExtractor {
|
||||
private final VaadinMessageSource i18n;
|
||||
|
||||
/**
|
||||
* Constructor for {@link ArtifactEncryptionErrorExtractor}.
|
||||
*
|
||||
* @param i18n
|
||||
* Message source used for localization
|
||||
*/
|
||||
public ArtifactEncryptionErrorExtractor(final VaadinMessageSource i18n) {
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<UiErrorDetails> findDetails(final Throwable error) {
|
||||
return findExceptionOf(error, ArtifactEncryptionUnsupportedException.class)
|
||||
.map(ex -> UiErrorDetails.create(i18n.getMessage(UIMessageIdProvider.CAPTION_ERROR),
|
||||
i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_ENCRYPTION_NOT_SUPPORTED)))
|
||||
.map(Optional::of)
|
||||
.orElseGet(() -> findExceptionOf(error, ArtifactEncryptionFailedException.class)
|
||||
.map(ex -> UiErrorDetails.create(i18n.getMessage(UIMessageIdProvider.CAPTION_ERROR),
|
||||
getEncryptionFailedDetailsMsg(ex))));
|
||||
}
|
||||
|
||||
private String getEncryptionFailedDetailsMsg(final ArtifactEncryptionFailedException ex) {
|
||||
switch (ex.getEncryptionOperation()) {
|
||||
case GENERATE_SECRETS:
|
||||
return i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_ENCRYPTION_SECRETS_FAILED);
|
||||
case ENCRYPT:
|
||||
return i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_ENCRYPTION_FAILED);
|
||||
case DECRYPT:
|
||||
return i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_DECRYPTION_FAILED);
|
||||
default:
|
||||
return i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_ENCRYPTION_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class UploadErrorExtractor extends AbstractSingleUiErrorDetailsExtractor
|
||||
|
||||
@Override
|
||||
protected Optional<UiErrorDetails> findDetails(final Throwable error) {
|
||||
// UploadException is ignored
|
||||
// UploadException is ignored as it is handled explicitly
|
||||
return findExceptionOf(error, UploadException.class).map(ex -> UiErrorDetails.empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class AutoAssignmentWindowLayoutComponentBuilder {
|
||||
*/
|
||||
public CheckBox createEnableCheckbox(final Binder<ProxyTargetFilterQuery> binder) {
|
||||
final String caption = i18n.getMessage(UIMessageIdProvider.LABEL_AUTO_ASSIGNMENT_ENABLE);
|
||||
return FormComponentBuilder.getCheckBox(caption, UIComponentIdProvider.DIST_SET_SELECT_ENABLE_ID, binder,
|
||||
return FormComponentBuilder.createCheckBox(caption, UIComponentIdProvider.DIST_SET_SELECT_ENABLE_ID, binder,
|
||||
ProxyTargetFilterQuery::isAutoAssignmentEnabled, ProxyTargetFilterQuery::setAutoAssignmentEnabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ public class AssignmentWindowLayoutComponentBuilder {
|
||||
* @return Maintenance window checkbox
|
||||
*/
|
||||
public CheckBox createEnableMaintenanceWindowToggle(final Binder<ProxyAssignmentWindow> binder) {
|
||||
final CheckBox maintenanceWindowToggle = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox maintenanceWindowToggle = FormComponentBuilder.createCheckBox(
|
||||
i18n.getMessage("caption.maintenancewindow.enabled"),
|
||||
UIComponentIdProvider.MAINTENANCE_WINDOW_ENABLED_ID, binder,
|
||||
ProxyAssignmentWindow::isMaintenanceWindowEnabled, ProxyAssignmentWindow::setMaintenanceWindowEnabled);
|
||||
|
||||
@@ -106,7 +106,7 @@ public class AuthenticationConfigurationView extends BaseConfigurationView<Proxy
|
||||
}
|
||||
|
||||
protected void initCertificateAuthConfiguration(GridLayout gridLayout, int row) {
|
||||
final CheckBox certificateAuthCheckbox = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox certificateAuthCheckbox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.CERT_AUTH_ALLOWED_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigAuthentication::isCertificateAuth,
|
||||
ProxySystemConfigAuthentication::setCertificateAuth);
|
||||
@@ -123,7 +123,7 @@ public class AuthenticationConfigurationView extends BaseConfigurationView<Proxy
|
||||
}
|
||||
|
||||
protected void initTargetTokenConfiguration(GridLayout gridLayout, int row) {
|
||||
final CheckBox targetSecTokenCheckBox = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox targetSecTokenCheckBox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.TARGET_SEC_TOKEN_ALLOWED_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigAuthentication::isTargetSecToken, ProxySystemConfigAuthentication::setTargetSecToken);
|
||||
targetSecTokenCheckBox.setStyleName(DIST_CHECKBOX_STYLE);
|
||||
@@ -132,7 +132,7 @@ public class AuthenticationConfigurationView extends BaseConfigurationView<Proxy
|
||||
}
|
||||
|
||||
protected void initGatewayTokenConfiguration(GridLayout gridLayout, int row) {
|
||||
final CheckBox gatewaySecTokenCheckBox = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox gatewaySecTokenCheckBox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.GATEWAY_SEC_TOKEN_ALLOWED_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigAuthentication::isGatewaySecToken,
|
||||
ProxySystemConfigAuthentication::setGatewaySecToken);
|
||||
@@ -149,7 +149,7 @@ public class AuthenticationConfigurationView extends BaseConfigurationView<Proxy
|
||||
}
|
||||
|
||||
protected void initAnonymousDownloadConfiguration(GridLayout gridLayout, int row) {
|
||||
final CheckBox downloadAnonymousCheckBox = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox downloadAnonymousCheckBox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.DOWNLOAD_ANONYMOUS_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigAuthentication::isDownloadAnonymous,
|
||||
ProxySystemConfigAuthentication::setDownloadAnonymous);
|
||||
|
||||
@@ -98,7 +98,7 @@ public class RepositoryConfigurationView extends BaseConfigurationView<ProxySyst
|
||||
gridLayout.setColumnExpandRatio(1, 1.0F);
|
||||
gridLayout.setSizeFull();
|
||||
|
||||
final CheckBox actionAutoCloseCheckBox = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox actionAutoCloseCheckBox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLOSE_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigRepository::isActionAutoclose, ProxySystemConfigRepository::setActionAutoclose);
|
||||
actionAutoCloseCheckBox.setStyleName(DIST_CHECKBOX_STYLE);
|
||||
@@ -107,7 +107,7 @@ public class RepositoryConfigurationView extends BaseConfigurationView<ProxySyst
|
||||
gridLayout.addComponent(actionAutoCloseCheckBox, 0, 0);
|
||||
gridLayout.addComponent(actionAutocloseConfigurationItem, 1, 0);
|
||||
|
||||
multiAssignmentsCheckBox = FormComponentBuilder.getCheckBox(
|
||||
multiAssignmentsCheckBox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.REPOSITORY_MULTI_ASSIGNMENTS_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigRepository::isMultiAssignments, ProxySystemConfigRepository::setMultiAssignments);
|
||||
multiAssignmentsCheckBox.setStyleName(DIST_CHECKBOX_STYLE);
|
||||
@@ -125,7 +125,7 @@ public class RepositoryConfigurationView extends BaseConfigurationView<ProxySyst
|
||||
gridLayout.addComponent(multiAssignmentsCheckBox, 0, 1);
|
||||
gridLayout.addComponent(multiAssignmentsConfigurationItem, 1, 1);
|
||||
|
||||
final CheckBox actionAutoCleanupCheckBox = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox actionAutoCleanupCheckBox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLEANUP_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigRepository::isActionAutocleanup, ProxySystemConfigRepository::setActionAutocleanup);
|
||||
actionAutoCleanupCheckBox.setStyleName(DIST_CHECKBOX_STYLE);
|
||||
|
||||
@@ -71,7 +71,7 @@ public class RolloutConfigurationView extends BaseConfigurationView<ProxySystemC
|
||||
gridLayout.setColumnExpandRatio(1, 1.0F);
|
||||
gridLayout.setSizeFull();
|
||||
|
||||
final CheckBox approvalCheckbox = FormComponentBuilder.getCheckBox(
|
||||
final CheckBox approvalCheckbox = FormComponentBuilder.createCheckBox(
|
||||
UIComponentIdProvider.ROLLOUT_APPROVAL_ENABLED_CHECKBOX, getBinder(),
|
||||
ProxySystemConfigRollout::isRolloutApproval, ProxySystemConfigRollout::setRolloutApproval);
|
||||
|
||||
|
||||
@@ -682,7 +682,7 @@ public final class UIComponentIdProvider {
|
||||
/**
|
||||
* Software module table details vendor label id.
|
||||
*/
|
||||
public static final String DETAILS_VENDOR_LABEL_ID = "details.vendor";
|
||||
public static final String DETAILS_VENDOR_LABEL_ID = "sm.details.vendor";
|
||||
|
||||
/**
|
||||
* Software module table details description label id.
|
||||
@@ -697,7 +697,7 @@ public final class UIComponentIdProvider {
|
||||
/**
|
||||
* Software module table details type label id.
|
||||
*/
|
||||
public static final String DETAILS_TYPE_LABEL_ID = "details.type";
|
||||
public static final String DETAILS_TYPE_LABEL_ID = "sm.details.type";
|
||||
|
||||
/**
|
||||
* Table details Required Migration Step label id.
|
||||
@@ -785,9 +785,14 @@ public final class UIComponentIdProvider {
|
||||
public static final String TARGET_MAX_MIN_TABLE_ICON = "target.max.min.table.icon";
|
||||
|
||||
/**
|
||||
* Software module table in upload UI.
|
||||
* Id of Assignment type in Software Module Details.
|
||||
*/
|
||||
public static final String SWM_DTLS_MAX_ASSIGN = "max.assign";
|
||||
public static final String SWM_DTLS_MAX_ASSIGN = "sm.details.max.assign";
|
||||
|
||||
/**
|
||||
* Id of encryption mode in Software Module Details.
|
||||
*/
|
||||
public static final String SWM_DTLS_ENCRYPTION = "sm.details.encryption";
|
||||
|
||||
/**
|
||||
* Documentation Link in Login view and menu.
|
||||
@@ -1431,7 +1436,8 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
public static final String INVALIDATE_DS_STOP_ROLLOUTS = "invalidate.distributionset.consequences.stop.rollouts.checkbox";
|
||||
/**
|
||||
* Distribution set invalidate consequences window, cancelation type radio button group
|
||||
* Distribution set invalidate consequences window, cancelation type radio
|
||||
* button group
|
||||
*/
|
||||
public static final String INVALIDATE_DS_CANCELATION_TYPE = "invalidate.distributionset.consequences.cancelation.type.radio";
|
||||
/**
|
||||
@@ -1581,6 +1587,11 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
public static final String UPLOAD_QUEUE_CLEAR_CONFIRMATION_DIALOG = "upload.queue.clear.confirmation.window";
|
||||
|
||||
/**
|
||||
* Artifact encryption checkbox id.
|
||||
*/
|
||||
public static final String ARTIFACT_ENCRYPTION_ID = "artifact.encryption.id";
|
||||
|
||||
/**
|
||||
* /* Private Constructor.
|
||||
*/
|
||||
|
||||
@@ -167,6 +167,14 @@ public final class UIMessageIdProvider {
|
||||
|
||||
public static final String MESSAGE_ERROR_TARGET_TYPE_INCOMPATIBLE = "message.target.type.incompatible";
|
||||
|
||||
public static final String MESSAGE_ERROR_ENCRYPTION_NOT_SUPPORTED = "message.encryption.unsupported";
|
||||
|
||||
public static final String MESSAGE_ERROR_ENCRYPTION_SECRETS_FAILED = "message.encryption.secrets.failed";
|
||||
|
||||
public static final String MESSAGE_ERROR_ENCRYPTION_FAILED = "message.encryption.failed";
|
||||
|
||||
public static final String MESSAGE_ERROR_DECRYPTION_FAILED = "message.decryption.failed";
|
||||
|
||||
public static final String CRON_VALIDATION_ERROR = "message.maintenancewindow.schedule.validation.error";
|
||||
|
||||
public static final String TOOLTIP_OVERDUE = "tooltip.overdue";
|
||||
|
||||
@@ -198,6 +198,9 @@ label.target.count = Targets
|
||||
label.description = Description
|
||||
label.ip = Address
|
||||
label.type = Type
|
||||
label.artifact.encryption = Artifact encryption
|
||||
label.enabled = Enabled
|
||||
label.disabled = Disabled
|
||||
label.assigned.type = Assignment type
|
||||
label.assigned.count = {0} Assigned
|
||||
label.installed.count = {0} Installed
|
||||
@@ -566,6 +569,10 @@ message.upload.storageQuota = Storage quota exceeded, {0} left
|
||||
message.uploadedfile.illegalFilename = Filename contains illegal characters
|
||||
message.artifact.deleted = Artifact with file {0} deleted successfully
|
||||
message.artifact.download.failure = Artifact Download Failed
|
||||
message.encryption.unsupported = Artifact encryption not supported
|
||||
message.encryption.secrets.failed = Artifact encryption secrets generation failed
|
||||
message.encryption.failed = Artifact encryption failed
|
||||
message.decryption.failed = Artifact decryption failed
|
||||
|
||||
artifact.upload.popup.caption = Upload status
|
||||
artifact.upload.status.caption = Status
|
||||
@@ -573,6 +580,7 @@ artifact.upload.progress.caption = Progress
|
||||
artifact.upload.reason.caption = Reason
|
||||
artifact.filename.caption = File name
|
||||
artifact.filesize.bytes.caption = Size(B)
|
||||
artifact.encryption = Enable artifact encryption
|
||||
|
||||
tooltip.upload.status.inprogress = In progress
|
||||
tooltip.upload.status.finished = Finished
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -162,7 +162,7 @@
|
||||
<maven.site.plugin.version>3.9.0</maven.site.plugin.version>
|
||||
|
||||
<!-- Misc libraries versions - START -->
|
||||
<cron-utils.version>9.1.3</cron-utils.version>
|
||||
<cron-utils.version>9.1.6</cron-utils.version>
|
||||
<jsoup.version>1.14.2</jsoup.version>
|
||||
<allure.version>2.13.6</allure.version>
|
||||
<eclipselink.version>2.7.9</eclipselink.version>
|
||||
|
||||
Reference in New Issue
Block a user