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:
Bondar Bogdan
2021-11-18 09:07:05 +01:00
committed by GitHub
parent 7e28fba104
commit 146735012a
74 changed files with 1214 additions and 324 deletions

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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']"),

View File

@@ -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())) {

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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
*/

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
UPDATE sp_base_software_module SET encrypted = 0;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
UPDATE sp_base_software_module SET encrypted = 0;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
UPDATE sp_base_software_module SET encrypted = 0;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
UPDATE sp_base_software_module SET encrypted = 0;

View File

@@ -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;

View File

@@ -0,0 +1 @@
ALTER TABLE sp_base_software_module ADD encrypted BIT DEFAULT 0;

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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));
}
/**

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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()));

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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());

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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";

View File

@@ -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),

View File

@@ -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.";

View 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())

View File

@@ -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.
*

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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)));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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)) {

View File

@@ -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());

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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.
*/

View File

@@ -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";

View File

@@ -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

View File

@@ -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>