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

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