diff --git a/README.md b/README.md index d7950469..01a9af6c 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Lowkey Vault is far from supporting all Azure Key Vault features. The list suppo ### Keys -- API version supported: ```7.2```, partially ```7.3```, ```7.4``` +- API version supported: ```7.2```, partially ```7.3```, ```7.4```, ```7.5``` - Create key (```RSA```, ```EC```, ```OCT```) - Including metadata - Import key (```RSA```, ```EC```, ```OCT```) @@ -128,7 +128,7 @@ Lowkey Vault is far from supporting all Azure Key Vault features. The list suppo ### Secrets -- API version supported: ```7.2```, ```7.3```, ```7.4``` +- API version supported: ```7.2```, ```7.3```, ```7.4```, ```7.5``` - Set secret - Including metadata - Get available secret versions @@ -147,7 +147,7 @@ Lowkey Vault is far from supporting all Azure Key Vault features. The list suppo ### Certificates -- API version supported: ```7.3```, ```7.4``` +- API version supported: ```7.3```, ```7.4```, ```7.5``` - Create certificate - Self-signed only - Using `PKCS12` (`.pfx`) or `PEM` (`.pem`) formats diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/context/ApiVersionAware.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/context/ApiVersionAware.java index ab259e77..bb043ae6 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/context/ApiVersionAware.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/context/ApiVersionAware.java @@ -11,11 +11,11 @@ public interface ApiVersionAware { /** * Supported API version collection containing all 7.3+ versions. */ - SortedSet V7_3_AND_LATER = new TreeSet<>(Set.of(ApiConstants.V_7_3, ApiConstants.V_7_4)); + SortedSet V7_3_AND_LATER = new TreeSet<>(Set.of(ApiConstants.V_7_3, ApiConstants.V_7_4, ApiConstants.V_7_5)); /** - * Supported API version collection containing all versions (7.2, 7.3 and 7.4). + * Supported API version collection containing all versions (7.2, 7.3, 7.4 and 7.5). */ - SortedSet ALL_VERSIONS = new TreeSet<>(Set.of(ApiConstants.V_7_2, ApiConstants.V_7_3, ApiConstants.V_7_4)); + SortedSet ALL_VERSIONS = new TreeSet<>(Set.of(ApiConstants.V_7_2, ApiConstants.V_7_3, ApiConstants.V_7_4, ApiConstants.V_7_5)); SortedSet supportedVersions(); } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateBackupRestoreController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateBackupRestoreController.java new file mode 100644 index 00000000..efd68842 --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateBackupRestoreController.java @@ -0,0 +1,62 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonCertificateBackupRestoreController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.CertificateConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.backup.CertificateBackupModel; +import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.KeyVaultCertificateModel; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@DependsOn({"certificateBackupConverter", "certificateModelConverter"}) +@Component("CertificateBackupRestoreControllerV75") +public class CertificateBackupRestoreController extends CommonCertificateBackupRestoreController { + + @Autowired + public CertificateBackupRestoreController( + @NonNull final CertificateConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @PostMapping(value = {"/certificates/{certificateName}/backup", "/certificates/{certificateName}/backup/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity backup(@PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.backup(certificateName, baseUri); + } + + @Override + @PostMapping(value = {"/certificates/restore", "/certificates/restore/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity restore(@RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final CertificateBackupModel certificateBackupModel) { + return super.restore(baseUri, certificateBackupModel); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateController.java new file mode 100644 index 00000000..e05d9938 --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateController.java @@ -0,0 +1,184 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonCertificateController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.CertificateConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.KeyVaultItemListModel; +import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.*; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@Component("CertificateControllerV75") +public class CertificateController extends CommonCertificateController { + @Autowired + public CertificateController(@NonNull final CertificateConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @PostMapping( + value = {"/certificates/{certificateName}/create", "/certificates/{certificateName}/create/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity create( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final CreateCertificateRequest request) { + return super.create(certificateName, baseUri, request); + } + + @Override + @PostMapping( + value = {"/certificates/{certificateName}/import", "/certificates/{certificateName}/import/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity importCertificate( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final CertificateImportRequest request) { + return super.importCertificate(certificateName, baseUri, request); + } + + @Override + @GetMapping( + value = {"/certificates/{certificateName}", "/certificates/{certificateName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity get( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.get(certificateName, baseUri); + } + + @Override + @GetMapping( + value = {"/certificates/{certificateName}/{certificateVersion}", "/certificates/{certificateName}/{certificateVersion}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getWithVersion( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String certificateVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getWithVersion(certificateName, certificateVersion, baseUri); + } + + @Override + @DeleteMapping( + value = {"/certificates/{certificateName}", "/certificates/{certificateName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity delete( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.delete(certificateName, baseUri); + } + + @Override + @GetMapping( + value = {"/deletedcertificates/{certificateName}", "/deletedcertificates/{certificateName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getDeletedCertificate( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getDeletedCertificate(certificateName, baseUri); + } + + @Override + @PostMapping( + value = {"/deletedcertificates/{certificateName}/recover", "/deletedcertificates/{certificateName}/recover/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity recoverDeletedCertificate( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.recoverDeletedCertificate(certificateName, baseUri); + } + + @Override + @DeleteMapping( + value = {"/deletedcertificates/{certificateName}", "/deletedcertificates/{certificateName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity purgeDeleted( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.purgeDeleted(certificateName, baseUri); + } + + @Override + @GetMapping( + value = {"/certificates/{certificateName}/versions", "/certificates/{certificateName}/versions/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> versions( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken) { + return super.versions(certificateName, baseUri, maxResults, skipToken); + } + + @Override + @GetMapping( + value = {"/certificates", "/certificates/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> listCertificates( + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken, + @RequestParam(name = INCLUDE_PENDING_PARAM, required = false, defaultValue = TRUE) final boolean includePending) { + return super.listCertificates(baseUri, maxResults, skipToken, includePending); + } + + @Override + @GetMapping( + value = {"/deletedcertificates", "/deletedcertificates/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> listDeletedCertificates( + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken, + @RequestParam(name = INCLUDE_PENDING_PARAM, required = false, defaultValue = TRUE) final boolean includePending) { + return super.listDeletedCertificates(baseUri, maxResults, skipToken, includePending); + } + + @Override + @PatchMapping( + value = {"/certificates/{certificateName}/{certificateVersion}", "/certificates/{certificateName}/{certificateVersion}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity updateCertificateProperties( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String certificateVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final UpdateCertificateRequest request) { + return super.updateCertificateProperties(certificateName, certificateVersion, baseUri, request); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyController.java new file mode 100644 index 00000000..e419ec75 --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyController.java @@ -0,0 +1,80 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonCertificatePolicyController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.CertificateConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.CertificatePolicyModel; +import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.KeyVaultPendingCertificateModel; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@Component("CertificatePolicyControllerV75") +public class CertificatePolicyController extends CommonCertificatePolicyController { + @Autowired + public CertificatePolicyController(@NonNull final CertificateConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @GetMapping(value = {"/certificates/{certificateName}/pending", "/certificates/{certificateName}/pending/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity pendingCreate( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.pendingCreate(certificateName, baseUri); + } + + @Override + @DeleteMapping(value = {"/certificates/{certificateName}/pending", "/certificates/{certificateName}/pending/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity pendingDelete( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.pendingDelete(certificateName, baseUri); + } + + @Override + @GetMapping(value = {"/certificates/{certificateName}/policy", "/certificates/{certificateName}/policy/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getPolicy( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getPolicy(certificateName, baseUri); + } + + @Override + @PatchMapping(value = {"/certificates/{certificateName}/policy", "/certificates/{certificateName}/policy/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity updatePolicy( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final CertificatePolicyModel request) { + return super.updatePolicy(certificateName, baseUri, request); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyBackupRestoreController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyBackupRestoreController.java new file mode 100644 index 00000000..109d952f --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyBackupRestoreController.java @@ -0,0 +1,62 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonPolicyAwareKeyBackupRestoreController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.backup.KeyBackupModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@DependsOn({"keyModelConverter", "keyRotationPolicyEntityConverter"}) +@Component("KeyBackupRestoreControllerV75") +public class KeyBackupRestoreController extends CommonPolicyAwareKeyBackupRestoreController { + + @Autowired + public KeyBackupRestoreController(@NonNull final KeyConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @PostMapping(value = {"/keys/{keyName}/backup", "/keys/{keyName}/backup/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity + backup(@PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.backup(keyName, baseUri); + } + + @Override + @PostMapping(value = {"/keys/restore", "/keys/restore/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity restore(@RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final KeyBackupModel keyBackupModel) { + return super.restore(baseUri, keyBackupModel); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyController.java new file mode 100644 index 00000000..6614112e --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyController.java @@ -0,0 +1,194 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonKeyController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.KeyVaultItemListModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.DeletedKeyVaultKeyItemModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.DeletedKeyVaultKeyModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyItemModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.CreateKeyRequest; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.ImportKeyRequest; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.UpdateKeyRequest; +import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@Component("KeyControllerV75") +public class KeyController extends CommonKeyController { + + @Autowired + public KeyController(@NonNull final KeyConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @PostMapping(value = {"/keys/{keyName}/create", "/keys/{keyName}/create/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity create( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final CreateKeyRequest request) { + return super.create(keyName, baseUri, request); + } + + @Override + @PutMapping(value = {"/keys/{keyName}", "/keys/{keyName}/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity importKey( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final ImportKeyRequest request) { + return super.importKey(keyName, baseUri, request); + } + + @Override + @DeleteMapping(value = {"/keys/{keyName}", "/keys/{keyName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity delete( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.delete(keyName, baseUri); + } + + @Override + @GetMapping(value = {"/keys/{keyName}/versions", "/keys/{keyName}/versions/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> versions( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken) { + return super.versions(keyName, baseUri, maxResults, skipToken); + } + + @Override + @GetMapping(value = {"/keys", "/keys/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> listKeys( + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken) { + return super.listKeys(baseUri, maxResults, skipToken); + } + + @Override + @GetMapping(value = {"/deletedkeys", "/deletedkeys/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> listDeletedKeys( + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken) { + return super.listDeletedKeys(baseUri, maxResults, skipToken); + } + + @Override + @GetMapping(value = {"/keys/{keyName}", "/keys/{keyName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity get( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.get(keyName, baseUri); + } + + @Override + @GetMapping(value = {"/keys/{keyName}/{keyVersion}", "/keys/{keyName}/{keyVersion}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getWithVersion( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String keyVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getWithVersion(keyName, keyVersion, baseUri); + } + + @Override + @PatchMapping(value = {"/keys/{keyName}/{keyVersion}", "/keys/{keyName}/{keyVersion}/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity updateVersion( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String keyVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @NonNull @Valid @RequestBody final UpdateKeyRequest request) { + return super.updateVersion(keyName, keyVersion, baseUri, request); + } + + @PostMapping(value = {"/keys/{keyName}/rotate", "/keys/{keyName}/rotate/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity rotateKey( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + log.info("Received request to {} rotate key: {} using API version: {}", + baseUri.toString(), keyName, apiVersion()); + + final VersionedKeyEntityId rotatedKeyId = getVaultByUri(baseUri).rotateKey(entityId(baseUri, keyName)); + final ReadOnlyKeyVaultKeyEntity keyVaultKeyEntity = getEntityByNameAndVersion(baseUri, keyName, rotatedKeyId.version()); + return ResponseEntity.ok(convertDetails(keyVaultKeyEntity, baseUri)); + } + + @Override + @GetMapping(value = {"/deletedkeys/{keyName}", "/deletedkeys/{keyName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getDeletedKey( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getDeletedKey(keyName, baseUri); + } + + @Override + @PostMapping(value = {"/deletedkeys/{keyName}/recover", "/deletedkeys/{keyName}/recover/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity recoverDeletedKey( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.recoverDeletedKey(keyName, baseUri); + } + + @Override + @DeleteMapping(value = {"/deletedkeys/{keyName}", "/deletedkeys/{keyName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity purgeDeleted( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.purgeDeleted(keyName, baseUri); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyCryptoController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyCryptoController.java new file mode 100644 index 00000000..15b85204 --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyCryptoController.java @@ -0,0 +1,113 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonKeyCryptoController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyOperationsResult; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeySignResult; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVerifyResult; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.KeyOperationsParameters; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.KeySignParameters; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.KeyVerifyParameters; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.RandomBytesRequest; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.RandomBytesResponse; +import com.github.nagyesta.lowkeyvault.service.key.util.KeyGenUtil; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@Component("KeyCryptoControllerV75") +public class KeyCryptoController extends CommonKeyCryptoController { + + @Autowired + public KeyCryptoController(@NonNull final KeyConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @PostMapping(value = {"/keys/{keyName}/{keyVersion}/encrypt", "/keys/{keyName}/{keyVersion}/encrypt/", + "/keys/{keyName}/{keyVersion}/wrapkey", "/keys/{keyName}/{keyVersion}/wrapkey/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity encrypt( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String keyVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final KeyOperationsParameters request) { + return super.encrypt(keyName, keyVersion, baseUri, request); + } + + @Override + @PostMapping(value = {"/keys/{keyName}/{keyVersion}/decrypt", "/keys/{keyName}/{keyVersion}/decrypt/", + "/keys/{keyName}/{keyVersion}/unwrapkey", "/keys/{keyName}/{keyVersion}/unwrapkey/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity decrypt( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String keyVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final KeyOperationsParameters request) { + return super.decrypt(keyName, keyVersion, baseUri, request); + } + + @Override + @PostMapping(value = {"/keys/{keyName}/{keyVersion}/sign", "/keys/{keyName}/{keyVersion}/sign/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity sign( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String keyVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final KeySignParameters request) { + return super.sign(keyName, keyVersion, baseUri, request); + } + + @Override + @PostMapping(value = {"/keys/{keyName}/{keyVersion}/verify", "/keys/{keyName}/{keyVersion}/verify/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity verify( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String keyVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final KeyVerifyParameters request) { + return super.verify(keyName, keyVersion, baseUri, request); + } + + @PostMapping(value = {"/rng", "/rng/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getRandomBytes( + @Valid @RequestBody final RandomBytesRequest request) { + log.info("Received request to generate {} random bytes using API version: {}", request.getCount(), apiVersion()); + + final byte[] randomBytes = KeyGenUtil.generateRandomBytes(request.getCount()); + return ResponseEntity.ok(new RandomBytesResponse(randomBytes)); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyPolicyController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyPolicyController.java new file mode 100644 index 00000000..cd65d135 --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyPolicyController.java @@ -0,0 +1,59 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonKeyPolicyController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyRotationPolicyModel; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.validator.Update; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@Component("KeyPolicyControllerV75") +public class KeyPolicyController extends CommonKeyPolicyController { + + public KeyPolicyController(@NonNull final KeyConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @GetMapping(value = {"/keys/{keyName}/rotationpolicy", "/keys/{keyName}/rotationpolicy/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getRotationPolicy( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getRotationPolicy(keyName, baseUri); + } + + @PutMapping(value = {"/keys/{keyName}/rotationpolicy", "/keys/{keyName}/rotationpolicy/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity updateRotationPolicy( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @NonNull @Valid @Validated(Update.class) @RequestBody final KeyRotationPolicyModel request) { + return super.updateRotationPolicy(keyName, baseUri, request); + } + + @Override + protected String apiVersion() { + return V_7_5; + } + +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretBackupRestoreController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretBackupRestoreController.java new file mode 100644 index 00000000..5342dc15 --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretBackupRestoreController.java @@ -0,0 +1,61 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonSecretBackupRestoreController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.SecretConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.backup.SecretBackupModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.KeyVaultSecretModel; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@DependsOn("secretModelConverter") +@Component("SecretBackupRestoreControllerV75") +public class SecretBackupRestoreController extends CommonSecretBackupRestoreController { + + @Autowired + public SecretBackupRestoreController(@NonNull final SecretConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @PostMapping(value = {"/secrets/{secretName}/backup", "/secrets/{secretName}/backup/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity backup(@PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.backup(secretName, baseUri); + } + + @Override + @PostMapping(value = {"/secrets/restore", "/secrets/restore/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity restore(@RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final SecretBackupModel secretBackupModel) { + return super.restore(baseUri, secretBackupModel); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretController.java new file mode 100644 index 00000000..1ab58d8d --- /dev/null +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretController.java @@ -0,0 +1,165 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.controller.common.CommonSecretController; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.SecretConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.KeyVaultItemListModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.DeletedKeyVaultSecretItemModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.DeletedKeyVaultSecretModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.KeyVaultSecretItemModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.KeyVaultSecretModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.request.CreateSecretRequest; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.request.UpdateSecretRequest; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_5; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@Validated +@Component("SecretControllerV75") +public class SecretController extends CommonSecretController { + + @Autowired + public SecretController(@NonNull final SecretConverterRegistry registry, @NonNull final VaultService vaultService) { + super(registry, vaultService); + } + + @Override + @PutMapping(value = {"/secrets/{secretName}", "/secrets/{secretName}/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity create( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @Valid @RequestBody final CreateSecretRequest request) { + return super.create(secretName, baseUri, request); + } + + @Override + @DeleteMapping(value = {"/secrets/{secretName}", "/secrets/{secretName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity delete( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.delete(secretName, baseUri); + } + + @Override + @GetMapping(value = {"/secrets/{secretName}/versions", "/secrets/{secretName}/versions/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> versions( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken) { + return super.versions(secretName, baseUri, maxResults, skipToken); + } + + @Override + @GetMapping(value = {"/secrets", "/secrets/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> listSecrets( + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken) { + return super.listSecrets(baseUri, maxResults, skipToken); + } + + @Override + @GetMapping(value = {"/deletedsecrets", "/deletedsecrets/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity> listDeletedSecrets( + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @RequestParam(name = MAX_RESULTS_PARAM, required = false, defaultValue = DEFAULT_MAX) final int maxResults, + @RequestParam(name = SKIP_TOKEN_PARAM, required = false, defaultValue = SKIP_ZERO) final int skipToken) { + return super.listDeletedSecrets(baseUri, maxResults, skipToken); + } + + @Override + @GetMapping(value = {"/secrets/{secretName}", "/secrets/{secretName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity get( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.get(secretName, baseUri); + } + + @Override + @GetMapping(value = {"/secrets/{secretName}/{secretVersion}", "/secrets/{secretName}/{secretVersion}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getWithVersion( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String secretVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getWithVersion(secretName, secretVersion, baseUri); + } + + @Override + @PatchMapping(value = {"/secrets/{secretName}/{secretVersion}", "/secrets/{secretName}/{secretVersion}/"}, + params = API_VERSION_7_5, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity updateVersion( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @PathVariable @Valid @Pattern(regexp = VERSION_NAME_PATTERN) final String secretVersion, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri, + @NonNull @Valid @RequestBody final UpdateSecretRequest request) { + return super.updateVersion(secretName, secretVersion, baseUri, request); + } + + @Override + @GetMapping(value = {"/deletedsecrets/{secretName}", "/deletedsecrets/{secretName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity getDeletedSecret( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.getDeletedSecret(secretName, baseUri); + } + + @Override + @DeleteMapping(value = {"/deletedsecrets/{secretName}", "/deletedsecrets/{secretName}/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity purgeDeleted( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.purgeDeleted(secretName, baseUri); + } + + @Override + @PostMapping(value = {"/deletedsecrets/{secretName}/recover", "/deletedsecrets/{secretName}/recover/"}, + params = API_VERSION_7_5, + produces = APPLICATION_JSON_VALUE) + public ResponseEntity recoverDeletedSecret( + @PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, + @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) { + return super.recoverDeletedSecret(secretName, baseUri); + } + + @Override + protected String apiVersion() { + return V_7_5; + } +} diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/common/ApiConstants.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/common/ApiConstants.java index 84f77835..a5d47e96 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/common/ApiConstants.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/common/ApiConstants.java @@ -18,6 +18,10 @@ public final class ApiConstants { * The version of the v7.4 API. */ public static final String V_7_4 = "7.4"; + /** + * The version of the v7.5 API. + */ + public static final String V_7_5 = "7.5"; /** * API version prefix. */ @@ -34,6 +38,10 @@ public final class ApiConstants { * API version param for 7.4. */ public static final String API_VERSION_7_4 = API_VERSION_PREFIX + V_7_4; + /** + * API version param for 7.5. + */ + public static final String API_VERSION_7_5 = API_VERSION_PREFIX + V_7_5; private ApiConstants() { throw new IllegalCallerException("Utility cannot be instantiated."); diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateBackupRestoreControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateBackupRestoreControllerIntegrationTest.java new file mode 100644 index 00000000..2c28bede --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateBackupRestoreControllerIntegrationTest.java @@ -0,0 +1,181 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.model.common.backup.CertificateBackupModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType; +import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.*; +import com.github.nagyesta.lowkeyvault.service.certificate.CertificateLifetimeActionActivity; +import com.github.nagyesta.lowkeyvault.service.certificate.id.CertificateEntityId; +import com.github.nagyesta.lowkeyvault.service.certificate.impl.CertContentType; +import com.github.nagyesta.lowkeyvault.service.certificate.impl.KeyUsageEnum; +import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId; +import com.github.nagyesta.lowkeyvault.service.secret.ReadOnlyKeyVaultSecretEntity; +import com.github.nagyesta.lowkeyvault.service.secret.id.VersionedSecretEntityId; +import com.github.nagyesta.lowkeyvault.service.vault.VaultFake; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.net.URI; +import java.util.Deque; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.getRandomVaultUri; + +@SpringBootTest +class CertificateBackupRestoreControllerIntegrationTest { + + private static final String CN_TEST = "CN=Test"; + private static final int VALIDITY_MONTHS = 10; + private static final Set EXTENDED_KEY_USAGE = Set.of("1.3.6.1.5.5.7.3.1"); + private static final Set KEY_USAGE = Set.of(KeyUsageEnum.ENCIPHER_ONLY, KeyUsageEnum.DECIPHER_ONLY); + private static final Set SANS = Set.of("test.com", "test2.com"); + private static final String SELF = "Self"; + private static final boolean CERT_TRANSPARENCY = false; + private static final String SELF_SIGNED = "SelfSigned"; + private static final boolean ENABLED = true; + private static final int KEY_SIZE = 2048; + private static final boolean REUSE_KEY = true; + private static final boolean EXPORTABLE = true; + private static final int SECONDS_IN_FIVE_DAYS = 5 * 24 * 60 * 60; + private static final String CERTIFICATE_BACKUP_TEST = "certificate-backup-test"; + @Autowired + private VaultService vaultService; + @Autowired + private CertificateController certificateController; + @Autowired + @Qualifier("CertificateBackupRestoreControllerV75") + private CertificateBackupRestoreController underTest; + + public static Stream certTypeProvider() { + return Stream.builder() + .add(Arguments.of(CertContentType.PEM, getRandomVaultUri())) + .add(Arguments.of(CertContentType.PKCS12, getRandomVaultUri())) + .build(); + } + + @ParameterizedTest + @MethodSource("certTypeProvider") + void testRestoreShouldRestoreOriginalCertificateAndSecretValuesWhenCalledWithValidBackupOutput( + final CertContentType contentType, final URI baseUri) { + //given + vaultService.create(baseUri, + RecoveryLevel.RECOVERABLE_AND_PURGEABLE, RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE, Set.of()); + final CreateCertificateRequest creteRequest = prepareCertificateCreateRequest(contentType); + + certificateController.create(CERTIFICATE_BACKUP_TEST, baseUri, creteRequest); + final VaultFake vaultFake = vaultService.findByUri(baseUri); + vaultFake.timeShift(SECONDS_IN_FIVE_DAYS, true); + + final CertificateEntityId entityId = new CertificateEntityId(baseUri, CERTIFICATE_BACKUP_TEST); + final Deque versions = vaultFake.certificateVaultFake().getEntities().getVersions(entityId); + final List expectedCerts = getAllCertificateModelVersions(baseUri, versions); + final List expectedSecrets = getAllSecretValuesForVersions(entityId, vaultFake, versions); + final ReadOnlyKeyVaultKeyEntity expectedKey = getOnlyKeyForVersions(entityId, vaultFake, versions); + + //when + final CertificateBackupModel backupModel = underTest.backup(CERTIFICATE_BACKUP_TEST, baseUri).getBody(); + Assertions.assertNotNull(backupModel); + vaultService.delete(baseUri); + vaultService.purge(baseUri); + vaultService.create(baseUri, + RecoveryLevel.RECOVERABLE_AND_PURGEABLE, RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE, Set.of()); + final ResponseEntity restored = underTest.restore(baseUri, backupModel); + + //then + Assertions.assertNotNull(restored); + Assertions.assertEquals(HttpStatus.OK, restored.getStatusCode()); + Assertions.assertNotNull(restored.getBody()); + Assertions.assertEquals(expectedCerts.get(1), restored.getBody()); + final List actualCerts = getAllCertificateModelVersions(baseUri, versions); + final List actualSecrets = getAllSecretValuesForVersions(entityId, vaultFake, versions); + final ReadOnlyKeyVaultKeyEntity actualKey = getOnlyKeyForVersions(entityId, vaultFake, versions); + Assertions.assertIterableEquals(expectedCerts, actualCerts); + Assertions.assertIterableEquals(expectedSecrets, actualSecrets); + Assertions.assertEquals(expectedKey, actualKey); + } + + private static CreateCertificateRequest prepareCertificateCreateRequest(final CertContentType contentType) { + final CreateCertificateRequest creteRequest = new CreateCertificateRequest(); + final CertificatePolicyModel policy = new CertificatePolicyModel(); + final X509CertificateModel x509 = new X509CertificateModel(); + x509.setSubject(CN_TEST); + x509.setValidityMonths(VALIDITY_MONTHS); + x509.setKeyUsage(KEY_USAGE); + x509.setExtendedKeyUsage(EXTENDED_KEY_USAGE); + x509.setSubjectAlternativeNames(new SubjectAlternativeNames(SANS, Set.of(), Set.of())); + policy.setX509Properties(x509); + + final IssuerParameterModel issuer = new IssuerParameterModel(); + issuer.setIssuer(SELF); + issuer.setCertTransparency(CERT_TRANSPARENCY); + issuer.setCertType(SELF_SIGNED); + policy.setIssuer(issuer); + + final CertificatePropertiesModel attributes = new CertificatePropertiesModel(); + attributes.setEnabled(ENABLED); + policy.setAttributes(attributes); + + final CertificateSecretModel secret = new CertificateSecretModel(); + secret.setContentType(contentType.getMimeType()); + policy.setSecretProperties(secret); + + final CertificateKeyModel key = new CertificateKeyModel(); + key.setKeySize(KEY_SIZE); + key.setReuseKey(REUSE_KEY); + key.setKeyType(KeyType.RSA); + key.setExportable(EXPORTABLE); + policy.setKeyProperties(key); + + final CertificateLifetimeActionModel lifetimeAction = new CertificateLifetimeActionModel(); + lifetimeAction.setAction(CertificateLifetimeActionActivity.AUTO_RENEW); + final CertificateLifetimeActionTriggerModel trigger = new CertificateLifetimeActionTriggerModel(); + trigger.setLifetimePercentage(1); + lifetimeAction.setTrigger(trigger); + policy.setLifetimeActions(List.of(lifetimeAction)); + creteRequest.setPolicy(policy); + return creteRequest; + } + + private static List getAllSecretValuesForVersions( + final CertificateEntityId entityId, final VaultFake vaultFake, final Deque versions) { + final List secrets = versions.stream() + .map(v -> new VersionedSecretEntityId(entityId.vault(), entityId.id(), v)) + .map(vaultFake.secretVaultFake().getEntities()::getReadOnlyEntity) + .map(ReadOnlyKeyVaultSecretEntity::getValue) + .collect(Collectors.toList()); + Assertions.assertEquals(2, secrets.size()); + return secrets; + } + + private static ReadOnlyKeyVaultKeyEntity getOnlyKeyForVersions( + final CertificateEntityId entityId, final VaultFake vaultFake, final Deque versions) { + final List keys = versions.stream() + .map(v -> new VersionedKeyEntityId(entityId.vault(), entityId.id(), v)) + .filter(vaultFake.keyVaultFake().getEntities()::containsEntity) + .map(vaultFake.keyVaultFake().getEntities()::getReadOnlyEntity) + .toList(); + Assertions.assertEquals(1, keys.size()); + return keys.get(0); + } + + private List getAllCertificateModelVersions( + final URI baseUri, final Deque versions) { + final List certs = versions.stream() + .map(v -> certificateController.getWithVersion(CERTIFICATE_BACKUP_TEST, v, baseUri).getBody()) + .collect(Collectors.toList()); + Assertions.assertEquals(2, certs.size()); + return certs; + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateControllerIntegrationTest.java new file mode 100644 index 00000000..7c122ef7 --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateControllerIntegrationTest.java @@ -0,0 +1,708 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.abortmission.booster.jupiter.annotation.LaunchAbortArmed; +import com.github.nagyesta.lowkeyvault.ResourceUtils; +import com.github.nagyesta.lowkeyvault.TestConstants; +import com.github.nagyesta.lowkeyvault.controller.v7_3.BaseCertificateControllerIntegrationTest; +import com.github.nagyesta.lowkeyvault.model.common.DeletedModel; +import com.github.nagyesta.lowkeyvault.model.common.KeyVaultItemListModel; +import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.*; +import com.github.nagyesta.lowkeyvault.service.certificate.CertificateLifetimeActionActivity; +import com.github.nagyesta.lowkeyvault.service.certificate.CertificateVaultFake; +import com.github.nagyesta.lowkeyvault.service.certificate.id.CertificateEntityId; +import com.github.nagyesta.lowkeyvault.service.certificate.id.VersionedCertificateEntityId; +import com.github.nagyesta.lowkeyvault.service.certificate.impl.CertContentType; +import jakarta.validation.ConstraintViolationException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.ResponseEntity; + +import java.net.URI; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstantsCertificates.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.getRandomVaultUri; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.getRandomVaultUriAsString; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static com.github.nagyesta.lowkeyvault.service.certificate.CertificateLifetimeActionActivity.AUTO_RENEW; +import static com.github.nagyesta.lowkeyvault.service.certificate.CertificateLifetimeActionActivity.EMAIL_CONTACTS; +import static org.springframework.http.HttpStatus.*; + +@LaunchAbortArmed +@SpringBootTest +class CertificateControllerIntegrationTest extends BaseCertificateControllerIntegrationTest { + + private static final int A_HUNDRED_YEARS = 36500; + private static final int ONE_HUNDRED = 100; + private static final URI VAULT_URI_1 = getRandomVaultUri(); + private static final URI VAULT_URI_2 = getRandomVaultUri(); + @Autowired + @Qualifier("CertificateControllerV75") + private CertificateController underTest; + + @BeforeEach + void setUp() { + prepareVaultIfNotExists(VAULT_URI_1); + prepareVaultIfNotExists(VAULT_URI_2); + } + + @SuppressWarnings("checkstyle:MagicNumber") + public static Stream certificateListProvider() { + final String uri1 = getRandomVaultUriAsString(); + final String uri2 = getRandomVaultUriAsString(); + final String uri3 = getRandomVaultUriAsString(); + return Stream.builder() + .add(Arguments.of(uri1, 5, 0, + uri1 + "/certificates?api-version=7.5&$skiptoken=5&maxresults=5&includePending=true")) + .add(Arguments.of(uri2, 3, 1, + uri2 + "/certificates?api-version=7.5&$skiptoken=4&maxresults=3&includePending=true")) + .add(Arguments.of(uri3, 3, 8, + null)) + .build(); + } + + @Test + void testCreateShouldReturnPendingCertificateWhenCalled() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + + //when + final ResponseEntity actual = underTest + .create("create-" + CERT_NAME_1, VAULT_URI_1, request); + + //then + Assertions.assertEquals(ACCEPTED, actual.getStatusCode()); + final KeyVaultPendingCertificateModel body = actual.getBody(); + assertPendingCreateResponseIsAsExpected(body); + } + + @Test + void testCreateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsWithZeroPercentageTrigger() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final List actions = List.of( + lifetimeActivity(EMAIL_CONTACTS, lifeTimePercentageTrigger(0))); + request.getPolicy().setLifetimeActions(actions); + + //when + Assertions.assertThrows(ConstraintViolationException.class, () -> underTest + .create("create-invalid-" + CERT_NAME_1, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testCreateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsWithTriggerUsingTooManyDaysBeforeExpiry() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final List actions = List.of( + lifetimeActivity(EMAIL_CONTACTS, daysBeforeExpiryTrigger(A_HUNDRED_YEARS))); + request.getPolicy().setLifetimeActions(actions); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest + .create("create-invalid-" + CERT_NAME_1, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testCreateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsUsingTwoTriggers() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final List actions = List.of( + lifetimeActivity(AUTO_RENEW, daysBeforeExpiryTrigger(1)), + lifetimeActivity(EMAIL_CONTACTS, daysBeforeExpiryTrigger(1)) + ); + request.getPolicy().setLifetimeActions(actions); + + //when + Assertions.assertThrows(ConstraintViolationException.class, () -> underTest + .create("create-invalid-" + CERT_NAME_1, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testCreateShouldReturnPendingCertificateWhenCalledWithValidLifetimeActions() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + request.getPolicy().setLifetimeActions(List.of(lifetimeActivity(EMAIL_CONTACTS, daysBeforeExpiryTrigger(1)))); + + //when + final ResponseEntity actual = underTest + .create("create-lifetime-" + CERT_NAME_1, VAULT_URI_1, request); + + //then + Assertions.assertEquals(ACCEPTED, actual.getStatusCode()); + final KeyVaultPendingCertificateModel body = actual.getBody(); + assertPendingCreateResponseIsAsExpected(body); + } + + @Test + void testGetShouldReturnModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + underTest.create(CERT_NAME_2, VAULT_URI_1, request); + final Deque versions = findByUri(VAULT_URI_1) + .certificateVaultFake() + .getEntities() + .getVersions(new CertificateEntityId(VAULT_URI_1, CERT_NAME_2)); + + //when + final ResponseEntity actual = underTest.get(CERT_NAME_2, VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNotNull(body.getCertificate()); + Assertions.assertNotNull(body.getThumbprint()); + Assertions.assertEquals(Collections.emptyMap(), body.getTags()); + final URI id = new VersionedCertificateEntityId(VAULT_URI_1, CERT_NAME_2, versions.getFirst()) + .asUri(VAULT_URI_1); + Assertions.assertEquals(id.toString(), body.getId()); + } + + @Test + void testGetWithVersionShouldReturnModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + underTest.create(CERT_NAME_3, VAULT_URI_1, request); + final Deque versions = findByUri(VAULT_URI_1) + .certificateVaultFake() + .getEntities() + .getVersions(new CertificateEntityId(VAULT_URI_1, CERT_NAME_3)); + + //when + final ResponseEntity actual = underTest + .getWithVersion(CERT_NAME_3, versions.getFirst(), VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNotNull(body.getCertificate()); + Assertions.assertNotNull(body.getThumbprint()); + Assertions.assertEquals(Collections.emptyMap(), body.getTags()); + final URI id = new VersionedCertificateEntityId(VAULT_URI_1, CERT_NAME_3, versions.getFirst()) + .asUri(VAULT_URI_1); + Assertions.assertEquals(id.toString(), body.getId()); + } + + @Test + void testApiVersionShouldReturnV75WhenCalled() { + //given + + //when + final String actual = underTest.apiVersion(); + + //then + Assertions.assertEquals(V_7_5, actual); + } + + @Test + void testImportCertificateShouldReturnModelWhenCalledWithValidPemData() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.pem", CertContentType.PEM); + final String name = CERT_NAME_3 + "-import-pem"; + + //when + final ResponseEntity actual = underTest + .importCertificate(name, VAULT_URI_1, request); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNotNull(body.getCertificate()); + Assertions.assertNotNull(body.getThumbprint()); + Assertions.assertEquals(Collections.emptyMap(), body.getTags()); + Assertions.assertTrue(body.getId().startsWith(VAULT_URI_1.toString())); + Assertions.assertTrue(body.getId().contains(name)); + } + + @Test + void testImportCertificateShouldReturnModelWhenCalledWithValidPkcs12Data() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PKCS12); + final String name = CERT_NAME_3 + "-import-pkcs"; + + //when + final ResponseEntity actual = underTest + .importCertificate(name, VAULT_URI_1, request); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNotNull(body.getCertificate()); + Assertions.assertNotNull(body.getThumbprint()); + Assertions.assertEquals(Collections.emptyMap(), body.getTags()); + Assertions.assertTrue(body.getId().startsWith(VAULT_URI_1.toString())); + Assertions.assertTrue(body.getId().contains(name)); + } + + @Test + void testImportCertificateShouldReturnModelWhenCalledWithValidPemDataAndNoType() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.pem", null); + final String name = CERT_NAME_3 + "-import-pem-auto"; + + //when + final ResponseEntity actual = underTest + .importCertificate(name, VAULT_URI_1, request); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNotNull(body.getCertificate()); + Assertions.assertNotNull(body.getThumbprint()); + Assertions.assertEquals(Collections.emptyMap(), body.getTags()); + Assertions.assertTrue(body.getId().startsWith(VAULT_URI_1.toString())); + Assertions.assertTrue(body.getId().contains(name)); + } + + @Test + void testImportCertificateShouldReturnModelWhenCalledWithValidPkcs12DataAndNoType() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", null); + final String name = CERT_NAME_3 + "-import-pkcs-auto"; + + //when + final ResponseEntity actual = underTest + .importCertificate(name, VAULT_URI_1, request); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNotNull(body.getCertificate()); + Assertions.assertNotNull(body.getThumbprint()); + Assertions.assertEquals(Collections.emptyMap(), body.getTags()); + Assertions.assertTrue(body.getId().startsWith(VAULT_URI_1.toString())); + Assertions.assertTrue(body.getId().contains(name)); + } + + @Test + void testImportCertificateShouldReturnModelWhenCalledWithValidPkcs12DataAndLifetimeAction() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", null); + final String name = CERT_NAME_3 + "-import-pkcs-lifetime"; + final CertificatePolicyModel policy = new CertificatePolicyModel(); + policy.setLifetimeActions(List.of(lifetimeActivity(EMAIL_CONTACTS, daysBeforeExpiryTrigger(ONE_HUNDRED)))); + request.setPolicy(policy); + + //when + final ResponseEntity actual = underTest + .importCertificate(name, VAULT_URI_1, request); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNotNull(body.getCertificate()); + Assertions.assertNotNull(body.getThumbprint()); + final List lifetimeActions = body.getPolicy().getLifetimeActions(); + Assertions.assertNotNull(lifetimeActions); + Assertions.assertEquals(1, lifetimeActions.size()); + final CertificateLifetimeActionModel lifetimeActionModel = lifetimeActions.get(0); + Assertions.assertEquals(EMAIL_CONTACTS, lifetimeActionModel.getAction()); + Assertions.assertEquals(ONE_HUNDRED, lifetimeActionModel.getTrigger().getDaysBeforeExpiry()); + } + + @Test + void testImportCertificateShouldThrowExceptionWhenCalledWithNotMatchingCertTypes() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PEM); + final String name = CERT_NAME_3 + "-import-invalid"; + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.importCertificate(name, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testImportCertificateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsTriggeringZeroDaysBeforeExpiry() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PKCS12); + final String name = CERT_NAME_3 + "-import-invalid"; + request.getPolicy().setLifetimeActions(List.of(lifetimeActivity(EMAIL_CONTACTS, daysBeforeExpiryTrigger(0)))); + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.importCertificate(name, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testImportCertificateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsTriggeringTooManyDaysBeforeExpiry() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PKCS12); + final String name = CERT_NAME_3 + "-import-invalid"; + request.getPolicy().setLifetimeActions(List.of(lifetimeActivity(EMAIL_CONTACTS, daysBeforeExpiryTrigger(A_HUNDRED_YEARS)))); + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.importCertificate(name, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testImportCertificateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsAskingForAutoRenewal() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PKCS12); + final String name = CERT_NAME_3 + "-import-invalid"; + request.getPolicy().setLifetimeActions(List.of(lifetimeActivity(AUTO_RENEW, daysBeforeExpiryTrigger(ONE_HUNDRED)))); + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.importCertificate(name, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testImportCertificateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsTriggeringAtZeroPercent() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PKCS12); + final String name = CERT_NAME_3 + "-import-invalid"; + request.getPolicy().setLifetimeActions(List.of(lifetimeActivity(EMAIL_CONTACTS, lifeTimePercentageTrigger(0)))); + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.importCertificate(name, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testImportCertificateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsTriggeringAtAHundredPercents() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PKCS12); + final String name = CERT_NAME_3 + "-import-invalid"; + request.getPolicy().setLifetimeActions(List.of(lifetimeActivity(EMAIL_CONTACTS, lifeTimePercentageTrigger(ONE_HUNDRED)))); + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.importCertificate(name, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testImportCertificateShouldThrowExceptionWhenCalledWithInvalidLifetimeActionsWithTwoActions() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.p12", CertContentType.PKCS12); + final String name = CERT_NAME_3 + "-import-invalid"; + request.getPolicy().setLifetimeActions(List.of( + lifetimeActivity(AUTO_RENEW, daysBeforeExpiryTrigger(1)), + lifetimeActivity(EMAIL_CONTACTS, daysBeforeExpiryTrigger(1)) + )); + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.importCertificate(name, VAULT_URI_1, request)); + + //then + exception + } + + @Test + void testVersionsShouldReturnAPageOfVersionsWhenTheCertificateExists() { + //given + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.pem", CertContentType.PEM); + final String name = CERT_NAME_1 + "-versions"; + final KeyVaultCertificateModel imported = Objects + .requireNonNull(underTest.importCertificate(name, VAULT_URI_1, request).getBody()); + + //when + final ResponseEntity> actual = underTest + .versions(name, VAULT_URI_1, 1, 0); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNull(actual.getBody().getNextLink()); + final List list = actual.getBody().getValue(); + Assertions.assertEquals(1, list.size()); + final KeyVaultCertificateItemModel item = list.get(0); + Assertions.assertEquals(imported.getId(), item.getCertificateId()); + Assertions.assertArrayEquals(imported.getThumbprint(), item.getThumbprint()); + Assertions.assertEquals(imported.getAttributes(), item.getAttributes()); + } + + @ParameterizedTest + @MethodSource("certificateListProvider") + void testListCertificatesShouldReturnAPageOfVersionsWhenTheCertificateExists( + final URI vault, final int pageSize, final int offset, final String nextLink) { + //given + create(vault); + final CertificateImportRequest request = getCreateImportRequest("/cert/ec.pem", CertContentType.PEM); + final String namePrefix = CERT_NAME_1; + final int certCount = 10; + final Map fullList = IntStream.range(0, certCount) + .mapToObj(i -> namePrefix + i) + .collect(Collectors.toMap(Function.identity(), n -> Objects + .requireNonNull(underTest.importCertificate(n, vault, request).getBody()))); + + //when + final ResponseEntity> actual = underTest + .listCertificates(vault, pageSize, offset, true); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertEquals(nextLink, actual.getBody().getNextLink()); + final List list = actual.getBody().getValue(); + final int expectedSize = Math.min(pageSize, certCount - offset); + Assertions.assertEquals(expectedSize, list.size()); + for (int i = 0; i < expectedSize; i++) { + final KeyVaultCertificateItemModel item = list.get(i); + final KeyVaultCertificateModel expected = fullList.get(namePrefix + (offset + i)); + Assertions.assertEquals(expected.getId().replaceAll("/[0-9a-f]+$", ""), item.getCertificateId()); + Assertions.assertArrayEquals(expected.getThumbprint(), item.getThumbprint()); + Assertions.assertEquals(expected.getAttributes(), item.getAttributes()); + } + } + + @Test + void testDeleteShouldReturnDeleteModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "-delete"; + underTest.create(certificateName, VAULT_URI_1, request); + final CertificateVaultFake certificateVaultFake = findByUri(VAULT_URI_1) + .certificateVaultFake(); + final Deque versions = certificateVaultFake + .getEntities() + .getVersions(new CertificateEntityId(VAULT_URI_1, certificateName)); + + //when + final ResponseEntity actual = underTest.delete(certificateName, VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final DeletedKeyVaultCertificateModel body = actual.getBody(); + final VersionedCertificateEntityId expectedId = + new VersionedCertificateEntityId(VAULT_URI_1, certificateName, versions.getFirst()); + + assertExpectedCertificateModel(request, expectedId, body); + assertIsDeletedModel(body, expectedId); + Assertions.assertFalse(certificateVaultFake.getEntities().containsName(certificateName)); + Assertions.assertTrue(certificateVaultFake.getDeletedEntities().containsName(certificateName)); + } + + @Test + void testPurgeDeletedShouldRemoveEntityFromDeletedMapWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "-purge"; + underTest.create(certificateName, VAULT_URI_1, request); + final CertificateVaultFake certificateVaultFake = findByUri(VAULT_URI_1) + .certificateVaultFake(); + certificateVaultFake.delete(new CertificateEntityId(VAULT_URI_1, certificateName)); + + //when + final ResponseEntity actual = underTest.purgeDeleted(certificateName, VAULT_URI_1); + + //then + Assertions.assertEquals(NO_CONTENT, actual.getStatusCode()); + Assertions.assertFalse(certificateVaultFake.getEntities().containsName(certificateName)); + Assertions.assertFalse(certificateVaultFake.getDeletedEntities().containsName(certificateName)); + } + + @Test + void testRecoverDeletedCertificateShouldReturnRecoveredEntityWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "-recover"; + underTest.create(certificateName, VAULT_URI_1, request); + final CertificateVaultFake certificateVaultFake = findByUri(VAULT_URI_1) + .certificateVaultFake(); + final CertificateEntityId entityId = new CertificateEntityId(VAULT_URI_1, certificateName); + final Deque versions = certificateVaultFake + .getEntities() + .getVersions(entityId); + certificateVaultFake.delete(entityId); + + //when + final ResponseEntity actual = underTest + .recoverDeletedCertificate(certificateName, VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + final VersionedCertificateEntityId expectedId = + new VersionedCertificateEntityId(VAULT_URI_1, certificateName, versions.getFirst()); + + assertExpectedCertificateModel(request, expectedId, body); + Assertions.assertTrue(certificateVaultFake.getEntities().containsName(certificateName)); + Assertions.assertFalse(certificateVaultFake.getDeletedEntities().containsName(certificateName)); + } + + @Test + void testGetDeletedCertificateShouldReturnDeleteModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "-get-deleted"; + underTest.create(certificateName, VAULT_URI_1, request); + final CertificateVaultFake certificateVaultFake = findByUri(VAULT_URI_1) + .certificateVaultFake(); + final CertificateEntityId entityId = new CertificateEntityId(VAULT_URI_1, certificateName); + final Deque versions = certificateVaultFake + .getEntities() + .getVersions(entityId); + certificateVaultFake.delete(entityId); + + //when + final ResponseEntity actual = underTest + .getDeletedCertificate(certificateName, VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final DeletedKeyVaultCertificateModel body = actual.getBody(); + final VersionedCertificateEntityId expectedId = + new VersionedCertificateEntityId(VAULT_URI_1, certificateName, versions.getFirst()); + + assertExpectedCertificateModel(request, expectedId, body); + assertIsDeletedModel(body, expectedId); + Assertions.assertFalse(certificateVaultFake.getEntities().containsName(certificateName)); + Assertions.assertTrue(certificateVaultFake.getDeletedEntities().containsName(certificateName)); + } + + @Test + void testListDeletedCertificatesShouldReturnDeletedItemModelsWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2; + underTest.create(certificateName, VAULT_URI_2, request); + final CertificateVaultFake certificateVaultFake = findByUri(VAULT_URI_2) + .certificateVaultFake(); + final CertificateEntityId entityId = new CertificateEntityId(VAULT_URI_2, certificateName); + certificateVaultFake.delete(entityId); + + //when + final ResponseEntity> actual = + underTest.listDeletedCertificates(VAULT_URI_2, 1, 0, true); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultItemListModel body = actual.getBody(); + Assertions.assertNotNull(body); + Assertions.assertNull(body.getNextLink()); + Assertions.assertEquals(1, body.getValue().size()); + final DeletedKeyVaultCertificateItemModel itemModel = body.getValue().get(0); + Assertions.assertEquals(entityId.asUriNoVersion(VAULT_URI_2).toString(), itemModel.getCertificateId()); + assertIsDeletedModel(itemModel, entityId); + } + + @Test + void testUpdateShouldReturnModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "-update-properties"; + underTest.create(certificateName, VAULT_URI_1, request); + final Deque versions = findByUri(VAULT_URI_1) + .certificateVaultFake() + .getEntities() + .getVersions(new CertificateEntityId(VAULT_URI_1, certificateName)); + final UpdateCertificateRequest properties = new UpdateCertificateRequest(); + final CertificatePropertiesModel attributes = new CertificatePropertiesModel(); + attributes.setEnabled(false); + properties.setTags(TestConstants.TAGS_THREE_KEYS); + properties.setAttributes(attributes); + + //when + final ResponseEntity actual = underTest + .updateCertificateProperties(certificateName, versions.getLast(), VAULT_URI_1, properties); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + Assertions.assertNotNull(body); + //policy is removed from the response + Assertions.assertNull(body.getPolicy()); + Assertions.assertEquals(TestConstants.TAGS_THREE_KEYS, body.getTags()); + Assertions.assertNotEquals(attributes, body.getAttributes()); + Assertions.assertEquals(attributes.isEnabled(), body.getAttributes().isEnabled()); + Assertions.assertNotNull(body.getAttributes().getCreatedOn()); + Assertions.assertNotNull(body.getAttributes().getUpdatedOn()); + Assertions.assertNotNull(body.getAttributes().getNotBefore()); + Assertions.assertNotNull(body.getAttributes().getExpiresOn()); + } + + private CertificateImportRequest getCreateImportRequest(final String resource, final CertContentType type) { + final CertificateImportRequest request = new CertificateImportRequest(); + request.setCertificate(ResourceUtils.loadResourceAsByteArray(resource)); + if (resource.endsWith("p12")) { + request.setPassword("changeit"); + } + if (type != null) { + final CertificateSecretModel secretProperties = new CertificateSecretModel(); + secretProperties.setContentType(type.getMimeType()); + final CertificatePolicyModel policy = new CertificatePolicyModel(); + policy.setSecretProperties(secretProperties); + request.setPolicy(policy); + } + return request; + } + + private void assertExpectedCertificateModel( + final CreateCertificateRequest request, + final VersionedCertificateEntityId expectedId, + final KeyVaultCertificateModel body) { + Assertions.assertNotNull(body); + final URI id = expectedId.asUri(VAULT_URI_1); + Assertions.assertEquals(id.toString(), body.getId()); + Assertions.assertEquals(request.getPolicy().getSecretProperties(), body.getPolicy().getSecretProperties()); + Assertions.assertEquals(request.getPolicy().getKeyProperties(), body.getPolicy().getKeyProperties()); + Assertions.assertEquals(request.getPolicy().getX509Properties(), body.getPolicy().getX509Properties()); + Assertions.assertTrue(body.getAttributes().isEnabled()); + Assertions.assertTrue(body.getAttributes().getRecoveryLevel().isPurgeable()); + Assertions.assertTrue(body.getAttributes().getRecoveryLevel().isRecoverable()); + } + + private void assertIsDeletedModel(final DeletedModel body, final CertificateEntityId expectedId) { + final URI recoveryUri = expectedId.asRecoveryUri(expectedId.vault()); + Assertions.assertNotNull(body); + Assertions.assertEquals(recoveryUri.toString(), body.getRecoveryId()); + Assertions.assertNotNull(body.getDeletedDate()); + Assertions.assertNotNull(body.getScheduledPurgeDate()); + } + + private CertificateLifetimeActionModel lifetimeActivity( + final CertificateLifetimeActionActivity action, final CertificateLifetimeActionTriggerModel trigger) { + final CertificateLifetimeActionModel activity = new CertificateLifetimeActionModel(); + activity.setAction(action); + activity.setTrigger(trigger); + return activity; + } + + private CertificateLifetimeActionTriggerModel lifeTimePercentageTrigger(final int value) { + final CertificateLifetimeActionTriggerModel trigger = new CertificateLifetimeActionTriggerModel(); + trigger.setLifetimePercentage(value); + return trigger; + } + + private CertificateLifetimeActionTriggerModel daysBeforeExpiryTrigger(final int value) { + final CertificateLifetimeActionTriggerModel trigger = new CertificateLifetimeActionTriggerModel(); + trigger.setDaysBeforeExpiry(value); + return trigger; + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateControllerTest.java new file mode 100644 index 00000000..678d7254 --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificateControllerTest.java @@ -0,0 +1,39 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.mapper.common.registry.CertificateConverterRegistry; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.mockito.Mockito.mock; + +class CertificateControllerTest { + + public static Stream nullProvider() { + final CertificateConverterRegistry registry = mock(CertificateConverterRegistry.class); + final VaultService vaultService = mock(VaultService.class); + return Stream.builder() + .add(Arguments.of(null, null)) + .add(Arguments.of(registry, null)) + .add(Arguments.of(null, vaultService)) + .build(); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNulls( + final CertificateConverterRegistry registry, + final VaultService vaultService) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new CertificateController(registry, vaultService)); + + //then + exception + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyControllerIntegrationTest.java new file mode 100644 index 00000000..ca604f1d --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyControllerIntegrationTest.java @@ -0,0 +1,195 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.abortmission.booster.jupiter.annotation.LaunchAbortArmed; +import com.github.nagyesta.lowkeyvault.controller.v7_3.BaseCertificateControllerIntegrationTest; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType; +import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.*; +import com.github.nagyesta.lowkeyvault.service.certificate.id.CertificateEntityId; +import com.github.nagyesta.lowkeyvault.service.certificate.id.VersionedCertificateEntityId; +import com.github.nagyesta.lowkeyvault.service.certificate.impl.CertAuthorityType; +import com.github.nagyesta.lowkeyvault.service.certificate.impl.CertContentType; +import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.ResponseEntity; + +import java.net.URI; +import java.util.Deque; +import java.util.Set; + +import static com.github.nagyesta.lowkeyvault.TestConstantsCertificates.CERT_NAME_2; +import static com.github.nagyesta.lowkeyvault.TestConstantsCertificates.CERT_NAME_3; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.getRandomVaultUri; +import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_5; +import static org.springframework.http.HttpStatus.OK; + +@LaunchAbortArmed +@SpringBootTest +class CertificatePolicyControllerIntegrationTest extends BaseCertificateControllerIntegrationTest { + + private static final URI VAULT_URI_1 = getRandomVaultUri(); + @Autowired + @Qualifier("CertificatePolicyControllerV75") + private CertificatePolicyController underTest; + @Autowired + @Qualifier("CertificateControllerV75") + private CertificateController certificateController; + + @BeforeEach + void setUp() { + prepareVaultIfNotExists(VAULT_URI_1); + } + + @Test + void testPendingCreateShouldReturnPendingCertificateWhenExists() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + certificateController.create("pending-" + CERT_NAME_2, VAULT_URI_1, request); + + //when + final ResponseEntity actual = underTest + .pendingCreate("pending-" + CERT_NAME_2, VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultPendingCertificateModel body = actual.getBody(); + assertPendingCreateResponseIsAsExpected(body); + } + + @Test + void testPendingCreateShouldThrowExceptionWhenNotFound() { + //given + + //when + Assertions.assertThrows(NotFoundException.class, () -> underTest + .pendingCreate("pending-" + CERT_NAME_3, VAULT_URI_1)); + + //then + exception + } + + @Test + void testPendingDeleteShouldReturnPendingCertificateWhenExists() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = "pending-del-" + CERT_NAME_2; + certificateController.create(certificateName, VAULT_URI_1, request); + certificateController.delete(certificateName, VAULT_URI_1); + + //when + final ResponseEntity actual = underTest + .pendingDelete(certificateName, VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultPendingCertificateModel body = actual.getBody(); + assertPendingCreateResponseIsAsExpected(body); + } + + @Test + void testPendingDeleteShouldThrowExceptionWhenNotFound() { + //given + + //when + Assertions.assertThrows(NotFoundException.class, () -> underTest + .pendingDelete("pending-del-" + CERT_NAME_3, VAULT_URI_1)); + + //then + exception + } + + @Test + void testApiVersionShouldReturnV75WhenCalled() { + //given + + //when + final String actual = underTest.apiVersion(); + + //then + Assertions.assertEquals(V_7_5, actual); + } + + @Test + void testGetPolicyShouldReturnModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "policy"; + certificateController.create(certificateName, VAULT_URI_1, request); + final Deque versions = findByUri(VAULT_URI_1) + .certificateVaultFake() + .getEntities() + .getVersions(new CertificateEntityId(VAULT_URI_1, certificateName)); + + //when + final ResponseEntity actual = underTest.getPolicy(certificateName, VAULT_URI_1); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final CertificatePolicyModel body = actual.getBody(); + Assertions.assertNotNull(body); + final URI id = new VersionedCertificateEntityId(VAULT_URI_1, certificateName, versions.getFirst()) + .asPolicyUri(VAULT_URI_1); + Assertions.assertEquals(request.getPolicy().getSecretProperties(), body.getSecretProperties()); + Assertions.assertEquals(request.getPolicy().getKeyProperties(), body.getKeyProperties()); + Assertions.assertEquals(request.getPolicy().getX509Properties(), body.getX509Properties()); + Assertions.assertTrue(body.getAttributes().isEnabled()); + Assertions.assertNull(body.getAttributes().getRecoveryLevel()); + Assertions.assertEquals(id.toString(), body.getId()); + } + + @Test + void testUpdatePolicyShouldReturnModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "-update-policy"; + certificateController.create(certificateName, VAULT_URI_1, request); + final Deque versions = findByUri(VAULT_URI_1) + .certificateVaultFake() + .getEntities() + .getVersions(new CertificateEntityId(VAULT_URI_1, certificateName)); + final CertificatePolicyModel update = getUpdatePolicyRequest(); + + //when + final ResponseEntity actual = underTest.updatePolicy(certificateName, VAULT_URI_1, update); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final CertificatePolicyModel body = actual.getBody(); + Assertions.assertNotNull(body); + final URI id = new VersionedCertificateEntityId(VAULT_URI_1, certificateName, versions.getFirst()) + .asPolicyUri(VAULT_URI_1); + Assertions.assertEquals(update.getSecretProperties(), body.getSecretProperties()); + Assertions.assertEquals(update.getKeyProperties(), body.getKeyProperties()); + Assertions.assertEquals(update.getX509Properties(), body.getX509Properties()); + Assertions.assertTrue(body.getAttributes().isEnabled()); + Assertions.assertNull(body.getAttributes().getRecoveryLevel()); + Assertions.assertEquals(id.toString(), body.getId()); + } + + private static CertificatePolicyModel getUpdatePolicyRequest() { + final CertificateKeyModel keyProperties = new CertificateKeyModel(); + keyProperties.setKeyType(KeyType.RSA); + keyProperties.setKeySize(KeyType.RSA.validateOrDefault(null, Integer.class)); + keyProperties.setReuseKey(false); + keyProperties.setExportable(true); + + final CertificateSecretModel secretProperties = new CertificateSecretModel(); + secretProperties.setContentType(CertContentType.PEM.getMimeType()); + + final X509CertificateModel x509Properties = new X509CertificateModel(); + x509Properties.setSubject("CN=localhost"); + x509Properties.setValidityMonths(1); + x509Properties.setSubjectAlternativeNames(new SubjectAlternativeNames(Set.of("example.com", "*.example.com"), Set.of(), Set.of())); + x509Properties.setKeyUsage(Set.of()); + x509Properties.setExtendedKeyUsage(Set.of()); + + final CertificatePolicyModel policy = new CertificatePolicyModel(); + policy.setKeyProperties(keyProperties); + policy.setSecretProperties(secretProperties); + policy.setX509Properties(x509Properties); + policy.setIssuer(new IssuerParameterModel(CertAuthorityType.SELF_SIGNED)); + return policy; + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyControllerTest.java new file mode 100644 index 00000000..6a8d6ee9 --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/CertificatePolicyControllerTest.java @@ -0,0 +1,39 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.mapper.common.registry.CertificateConverterRegistry; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.mockito.Mockito.mock; + +class CertificatePolicyControllerTest { + + public static Stream nullProvider() { + final CertificateConverterRegistry registry = mock(CertificateConverterRegistry.class); + final VaultService vaultService = mock(VaultService.class); + return Stream.builder() + .add(Arguments.of(null, null)) + .add(Arguments.of(registry, null)) + .add(Arguments.of(null, vaultService)) + .build(); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNull( + final CertificateConverterRegistry registry, + final VaultService vaultService) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new CertificatePolicyController(registry, vaultService)); + + //then + exception + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyBackupRestoreControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyBackupRestoreControllerIntegrationTest.java new file mode 100644 index 00000000..6f047c54 --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyBackupRestoreControllerIntegrationTest.java @@ -0,0 +1,343 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.abortmission.booster.jupiter.annotation.LaunchAbortArmed; +import com.github.nagyesta.lowkeyvault.TestConstantsUri; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.backup.KeyBackupList; +import com.github.nagyesta.lowkeyvault.model.common.backup.KeyBackupListItem; +import com.github.nagyesta.lowkeyvault.model.common.backup.KeyBackupModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyPropertiesModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyCurveName; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyOperation; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.JsonWebKeyImportRequest; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.*; +import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException; +import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake; +import com.github.nagyesta.lowkeyvault.service.key.LifetimeAction; +import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyRotationPolicy; +import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType; +import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.impl.EcKeyCreationInput; +import com.github.nagyesta.lowkeyvault.service.key.impl.KeyCreateDetailedInput; +import com.github.nagyesta.lowkeyvault.service.key.util.KeyGenUtil; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.net.URI; +import java.security.KeyPair; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.time.Period; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstants.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsKeys.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.getRandomVaultUri; +import static com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType.ROTATE; +import static org.mockito.Mockito.mock; + +@LaunchAbortArmed +@SpringBootTest +class KeyBackupRestoreControllerIntegrationTest { + + private static final Period EXPIRY_TIME = Period.ofDays(LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS); + private static final Period TRIGGER_TIME = Period.ofDays(LifetimeActionTriggerType.MINIMUM_THRESHOLD_BEFORE_EXPIRY); + @Autowired + @Qualifier("KeyBackupRestoreControllerV75") + private com.github.nagyesta.lowkeyvault.controller.v7_5.KeyBackupRestoreController underTest; + @Autowired + private VaultService vaultService; + private URI uri; + + public static Stream nullProvider() { + return Stream.builder() + .add(Arguments.of(mock(VaultService.class), null)) + .add(Arguments.of(null, mock(KeyConverterRegistry.class))) + .build(); + } + + @BeforeEach + void setUp() { + uri = getRandomVaultUri(); + vaultService.create(uri, RecoveryLevel.RECOVERABLE_AND_PURGEABLE, RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE, null); + } + + @AfterEach + void tearDown() { + vaultService.delete(uri); + vaultService.purge(uri); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNulls(final VaultService vaultService, final KeyConverterRegistry registry) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new KeyBackupRestoreController(registry, vaultService)); + + //then + exception + } + + @Test + void testRestoreEntityShouldRestoreASingleKeyWhenCalledWithValidInput() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + final KeyPair expectedKey = addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, TAGS_THREE_KEYS); + + //when + final ResponseEntity actual = underTest.restore(uri, backupModel); + + //then + Assertions.assertNotNull(actual); + final KeyVaultKeyModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + assertRestoredKeyMatchesExpectations(actualBody, (ECPublicKey) expectedKey.getPublic(), KEY_VERSION_1, TAGS_THREE_KEYS); + } + + @Test + void testRestoreEntityShouldRestoreThreeKeysWhenCalledWithValidInput() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, null); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_2, backupModel, TAGS_THREE_KEYS); + final KeyPair expectedKey = addVersionToList(uri, KEY_NAME_1, KEY_VERSION_3, backupModel, TAGS_EMPTY); + + //when + final ResponseEntity actual = underTest.restore(uri, backupModel); + + //then + Assertions.assertNotNull(actual); + final KeyVaultKeyModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + assertRestoredKeyMatchesExpectations(actualBody, (ECPublicKey) expectedKey.getPublic(), KEY_VERSION_3, TAGS_EMPTY); + } + + @Test + void testRestoreEntityShouldRestoreRotationPolicyWhenCalledWithValidInput() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + final KeyPair expectedKey = addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, TAGS_EMPTY); + final KeyEntityId keyEntityId = new KeyEntityId(uri, KEY_NAME_1); + backupModel.getValue().setKeyRotationPolicy(keyRotationPolicy(keyEntityId)); + + //when + final ResponseEntity actual = underTest.restore(uri, backupModel); + + //then + Assertions.assertNotNull(actual); + final KeyVaultKeyModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + assertRestoredKeyMatchesExpectations(actualBody, (ECPublicKey) expectedKey.getPublic(), KEY_VERSION_1, TAGS_EMPTY); + final ReadOnlyRotationPolicy rotationPolicy = vaultService.findByUri(uri).keyVaultFake().rotationPolicy(keyEntityId); + Assertions.assertEquals(keyEntityId, rotationPolicy.getId()); + Assertions.assertEquals(TIME_10_MINUTES_AGO, rotationPolicy.getCreatedOn()); + Assertions.assertEquals(NOW, rotationPolicy.getUpdatedOn()); + Assertions.assertEquals(EXPIRY_TIME, rotationPolicy.getExpiryTime()); + Assertions.assertIterableEquals(Collections.singleton(ROTATE), rotationPolicy.getLifetimeActions().keySet()); + final LifetimeAction lifetimeAction = rotationPolicy.getLifetimeActions().get(ROTATE); + Assertions.assertEquals(ROTATE, lifetimeAction.actionType()); + Assertions.assertEquals(LifetimeActionTriggerType.TIME_AFTER_CREATE, lifetimeAction.trigger().triggerType()); + Assertions.assertEquals(TRIGGER_TIME, lifetimeAction.trigger().timePeriod()); + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenCalledWithMoreThanOneUris() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, null); + addVersionToList(TestConstantsUri.HTTPS_DEFAULT_LOWKEY_VAULT, KEY_NAME_1, KEY_VERSION_2, backupModel, TAGS_THREE_KEYS); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenCalledWithMoreThanOneNames() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, null); + addVersionToList(uri, KEY_NAME_2, KEY_VERSION_2, backupModel, TAGS_THREE_KEYS); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenCalledWithUnknownUri() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + addVersionToList(URI.create("https://uknknown.uri"), KEY_NAME_1, KEY_VERSION_1, backupModel, null); + + //when + Assertions.assertThrows(NotFoundException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenNameMatchesActiveKey() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, TAGS_EMPTY); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_2, backupModel, TAGS_ONE_KEY); + vaultService.findByUri(uri).keyVaultFake() + .createKeyVersion(KEY_NAME_1, KeyCreateDetailedInput.builder() + .key(new EcKeyCreationInput(KeyType.EC, KeyCurveName.P_256)) + .build()); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenNameMatchesDeletedKey() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, TAGS_EMPTY); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_2, backupModel, TAGS_ONE_KEY); + final KeyVaultFake vaultFake = vaultService.findByUri(uri).keyVaultFake(); + final VersionedKeyEntityId keyVersion = vaultFake + .createKeyVersion(KEY_NAME_1, KeyCreateDetailedInput.builder() + .key(new EcKeyCreationInput(KeyType.EC, KeyCurveName.P_256)) + .build()); + vaultFake.delete(keyVersion); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testBackupEntityShouldReturnTheOriginalBackupModelWhenCalledAfterRestoreEntity() { + //given + final KeyBackupModel backupModel = new KeyBackupModel(); + backupModel.setValue(new KeyBackupList()); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, TAGS_EMPTY); + addVersionToList(uri, KEY_NAME_1, KEY_VERSION_2, backupModel, TAGS_ONE_KEY); + underTest.restore(uri, backupModel); + + //when + final ResponseEntity actual = underTest.backup(KEY_NAME_1, uri); + + //then + Assertions.assertNotNull(actual); + final KeyBackupModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(backupModel, actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + } + + private void assertRestoredKeyMatchesExpectations( + final KeyVaultKeyModel actualBody, final ECPublicKey publicKey, + final String version, final Map expectedTags) { + Assertions.assertEquals(new VersionedKeyEntityId(uri, KEY_NAME_1, version).asUri(uri).toString(), actualBody.getKey().getId()); + Assertions.assertEquals(KeyCurveName.P_256, actualBody.getKey().getCurveName()); + Assertions.assertEquals(KeyType.EC, actualBody.getKey().getKeyType()); + Assertions.assertIterableEquals(List.of(KeyOperation.SIGN, KeyOperation.VERIFY), actualBody.getKey().getKeyOps()); + Assertions.assertArrayEquals(publicKey.getW().getAffineX().toByteArray(), actualBody.getKey().getX()); + Assertions.assertArrayEquals(publicKey.getW().getAffineY().toByteArray(), actualBody.getKey().getY()); + //do not return private key material in response + Assertions.assertNull(actualBody.getKey().getD()); + Assertions.assertEquals(TIME_10_MINUTES_AGO, actualBody.getAttributes().getCreatedOn()); + Assertions.assertEquals(NOW, actualBody.getAttributes().getUpdatedOn()); + Assertions.assertEquals(TIME_IN_10_MINUTES, actualBody.getAttributes().getNotBefore()); + Assertions.assertEquals(TIME_IN_10_MINUTES.plusDays(1), actualBody.getAttributes().getExpiresOn()); + Assertions.assertEquals(RecoveryLevel.RECOVERABLE_AND_PURGEABLE, actualBody.getAttributes().getRecoveryLevel()); + Assertions.assertEquals(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE, actualBody.getAttributes().getRecoverableDays()); + Assertions.assertTrue(actualBody.getAttributes().isEnabled()); + Assertions.assertEquals(expectedTags, actualBody.getTags()); + } + + private KeyPair addVersionToList(final URI baseUri, final String name, final String version, + final KeyBackupModel backupModel, final Map tags) { + final KeyPair keyPair = KeyGenUtil.generateEc(KeyCurveName.P_256); + final JsonWebKeyImportRequest keyMaterial = new JsonWebKeyImportRequest(); + keyMaterial.setKeyType(KeyType.EC); + keyMaterial.setCurveName(KeyCurveName.P_256); + keyMaterial.setKeyOps(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); + keyMaterial.setD(((ECPrivateKey) keyPair.getPrivate()).getS().toByteArray()); + keyMaterial.setX(((ECPublicKey) keyPair.getPublic()).getW().getAffineX().toByteArray()); + keyMaterial.setY(((ECPublicKey) keyPair.getPublic()).getW().getAffineY().toByteArray()); + keyMaterial.setId(new VersionedKeyEntityId(baseUri, name, version).asUri(uri).toString()); + final KeyBackupListItem listItem = new KeyBackupListItem(); + listItem.setKeyMaterial(keyMaterial); + listItem.setVaultBaseUri(baseUri); + listItem.setId(name); + listItem.setVersion(version); + final KeyPropertiesModel propertiesModel = new KeyPropertiesModel(); + propertiesModel.setCreatedOn(TIME_10_MINUTES_AGO); + propertiesModel.setUpdatedOn(NOW); + propertiesModel.setNotBefore(TIME_IN_10_MINUTES); + propertiesModel.setExpiresOn(TIME_IN_10_MINUTES.plusDays(1)); + propertiesModel.setRecoveryLevel(RecoveryLevel.RECOVERABLE_AND_PURGEABLE); + propertiesModel.setRecoverableDays(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE); + listItem.setAttributes(propertiesModel); + listItem.setTags(tags); + final List list = new ArrayList<>(backupModel.getValue().getVersions()); + list.add(listItem); + backupModel.getValue().setVersions(list); + return keyPair; + } + + private KeyRotationPolicyModel keyRotationPolicy(final KeyEntityId keyEntityId) { + final KeyRotationPolicyModel model = new KeyRotationPolicyModel(); + model.setId(keyEntityId.asRotationPolicyUri(keyEntityId.vault())); + model.setLifetimeActions(List.of(actionModel())); + model.setAttributes(rotationPolicyAttributes()); + return model; + } + + private KeyRotationPolicyAttributes rotationPolicyAttributes() { + final KeyRotationPolicyAttributes attributes = new KeyRotationPolicyAttributes(); + attributes.setCreated(TIME_10_MINUTES_AGO); + attributes.setUpdated(NOW); + attributes.setExpiryTime(EXPIRY_TIME); + return attributes; + } + + private KeyLifetimeActionModel actionModel() { + final KeyLifetimeActionModel actionModel = new KeyLifetimeActionModel(); + actionModel.setAction(new KeyLifetimeActionTypeModel(ROTATE)); + actionModel.setTrigger(new KeyLifetimeActionTriggerModel(null, TRIGGER_TIME)); + return actionModel; + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyControllerIntegrationTest.java new file mode 100644 index 00000000..bf945e86 --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyControllerIntegrationTest.java @@ -0,0 +1,183 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.nagyesta.abortmission.booster.jupiter.annotation.LaunchAbortArmed; +import com.github.nagyesta.lowkeyvault.ResourceUtils; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.EncryptionAlgorithm; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyCurveName; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.SignatureAlgorithm; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.ImportKeyRequest; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.JsonWebKeyImportRequest; +import com.github.nagyesta.lowkeyvault.service.common.ReadOnlyVersionedEntityMultiMap; +import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.impl.AesKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.impl.EcKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.impl.RsaKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.ResponseEntity; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.getRandomVaultUri; + +@LaunchAbortArmed +@SpringBootTest +class KeyControllerIntegrationTest { + + private static final byte[] IV = "_iv-param-value_".getBytes(StandardCharsets.UTF_8); + private static final int AES_256 = 256; + private static final int RSA_2048 = 2048; + private static final String SHA_256 = "SHA-256"; + + @Autowired + private VaultService vaultService; + @Autowired + @Qualifier("KeyControllerV75") + private KeyController underTest; + @Autowired + private ObjectMapper objectMapper; + + public static Stream invalidHsmProvider() { + return Stream.builder() + .add(Arguments.of(false, KeyType.EC_HSM)) + .add(Arguments.of(true, KeyType.EC)) + .build(); + } + + @ParameterizedTest + @MethodSource("invalidHsmProvider") + void testImportShouldThrowExceptionWhenCalledWithMisalignedHsmConfiguration(final Boolean hsm, final KeyType keyType) { + //given + final ImportKeyRequest input = new ImportKeyRequest(); + input.setHsm(hsm); + final JsonWebKeyImportRequest key = new JsonWebKeyImportRequest(); + key.setKeyType(keyType); + key.setCurveName(KeyCurveName.P_256); + key.setD(new byte[2]); + key.setX(new byte[2]); + key.setY(new byte[2]); + input.setKey(key); + final URI baseUri = getRandomVaultUri(); + vaultService.create(baseUri); + final String name = "invalid-ec-name"; + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.importKey(name, baseUri, input)); + + //then + exception + } + + @Test + void testImportRsaShouldUseImportKeyWhenCalledWithValidPayload() throws IOException { + //given + final String resource = "/key/import/rsa-import-valid.json"; + final ImportKeyRequest input = loadResourceAsObject(resource); + final URI baseUri = getRandomVaultUri(); + vaultService.create(baseUri); + final String name = "rsa-name"; + final ReadOnlyVersionedEntityMultiMap entities = vaultService + .findByUri(baseUri) + .keyVaultFake() + .getEntities(); + + //when + final ResponseEntity response = underTest.importKey(name, baseUri, input); + final VersionedKeyEntityId id = entities.getLatestVersionOfEntity(new KeyEntityId(baseUri, name)); + final RsaKeyVaultKeyEntity actual = entities.getEntity(id, RsaKeyVaultKeyEntity.class); + + //then + Assertions.assertNotNull(response); + Assertions.assertNotNull(actual); + Assertions.assertEquals(RSA_2048, actual.getKeySize()); + final byte[] encrypted = actual.encryptBytes(name.getBytes(StandardCharsets.UTF_8), EncryptionAlgorithm.RSA_OAEP_256, null); + final byte[] decrypted = actual.decryptToBytes(encrypted, EncryptionAlgorithm.RSA_OAEP_256, null); + Assertions.assertEquals(name, new String(decrypted)); + } + + @Test + void testImportAesShouldUseImportKeyWhenCalledWithValidPayload() throws IOException { + //given + final String resource = "/key/import/aes-import-valid.json"; + final ImportKeyRequest input = loadResourceAsObject(resource); + final URI baseUri = getRandomVaultUri(); + vaultService.create(baseUri); + final String name = "aes-name"; + final ReadOnlyVersionedEntityMultiMap entities = vaultService + .findByUri(baseUri) + .keyVaultFake() + .getEntities(); + + //when + final ResponseEntity response = underTest.importKey(name, baseUri, input); + final VersionedKeyEntityId id = entities.getLatestVersionOfEntity(new KeyEntityId(baseUri, name)); + final AesKeyVaultKeyEntity actual = entities.getEntity(id, AesKeyVaultKeyEntity.class); + + //then + Assertions.assertNotNull(response); + Assertions.assertNotNull(actual); + Assertions.assertEquals(AES_256, actual.getKeySize()); + final byte[] encrypted = actual.encryptBytes(name.getBytes(StandardCharsets.UTF_8), EncryptionAlgorithm.A256CBCPAD, IV); + final byte[] decrypted = actual.decryptToBytes(encrypted, EncryptionAlgorithm.A256CBCPAD, IV); + Assertions.assertEquals(name, new String(decrypted)); + } + + @Test + void testImportEcShouldUseImportKeyWhenCalledWithValidPayload() throws IOException { + //given + final String resource = "/key/import/ec-import-valid.json"; + final ImportKeyRequest input = loadResourceAsObject(resource); + final URI baseUri = getRandomVaultUri(); + vaultService.create(baseUri); + final String name = "ec-name"; + final ReadOnlyVersionedEntityMultiMap entities = vaultService + .findByUri(baseUri) + .keyVaultFake() + .getEntities(); + final byte[] digest = hash(name.getBytes(StandardCharsets.UTF_8)); + + //when + final ResponseEntity response = underTest.importKey(name, baseUri, input); + final VersionedKeyEntityId id = entities.getLatestVersionOfEntity(new KeyEntityId(baseUri, name)); + final EcKeyVaultKeyEntity actual = entities.getEntity(id, EcKeyVaultKeyEntity.class); + + //then + Assertions.assertNotNull(response); + Assertions.assertNotNull(actual); + Assertions.assertEquals(KeyCurveName.P_256, actual.getKeyCurveName()); + final byte[] signature = actual.signBytes(digest, SignatureAlgorithm.ES256); + final boolean valid = actual.verifySignedBytes(digest, SignatureAlgorithm.ES256, signature); + Assertions.assertTrue(valid); + } + + private ImportKeyRequest loadResourceAsObject(final String resource) throws IOException { + final String json = ResourceUtils.loadResourceAsString(resource); + return objectMapper.reader().readValue(json, ImportKeyRequest.class); + } + + private byte[] hash(final byte[] text) { + try { + final MessageDigest md = MessageDigest.getInstance(SHA_256); + md.update(text); + return md.digest(); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyControllerTest.java new file mode 100644 index 00000000..c0bd43e0 --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyControllerTest.java @@ -0,0 +1,1002 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72KeyItemModelConverter; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72KeyVersionItemModelConverter; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72ModelConverter; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.ErrorMessage; +import com.github.nagyesta.lowkeyvault.model.common.ErrorModel; +import com.github.nagyesta.lowkeyvault.model.common.KeyVaultItemListModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.BasePropertiesUpdateModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.*; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyOperation; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.CreateKeyRequest; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.UpdateKeyRequest; +import com.github.nagyesta.lowkeyvault.service.common.ReadOnlyVersionedEntityMultiMap; +import com.github.nagyesta.lowkeyvault.service.exception.AlreadyExistsException; +import com.github.nagyesta.lowkeyvault.service.exception.CryptoException; +import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException; +import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake; +import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.impl.KeyCreateDetailedInput; +import com.github.nagyesta.lowkeyvault.service.key.impl.KeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.impl.RsaKeyCreationInput; +import com.github.nagyesta.lowkeyvault.service.key.impl.RsaKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.vault.VaultFake; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.assertj.core.util.Arrays; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstants.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsKeys.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.HTTPS_LOCALHOST_8443; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.*; + +class KeyControllerTest { + + private static final KeyVaultKeyModel RESPONSE = createResponse(); + private static final DeletedKeyVaultKeyModel DELETED_RESPONSE = createDeletedResponse(); + @Mock + private KeyEntityToV72ModelConverter keyEntityToV72ModelConverter; + @Mock + private KeyEntityToV72KeyItemModelConverter keyEntityToV72KeyItemModelConverter; + @Mock + private KeyEntityToV72KeyVersionItemModelConverter keyEntityToV72KeyVersionItemModelConverter; + @Mock + private VaultService vaultService; + @Mock + private VaultFake vaultFake; + @Mock + private KeyVaultFake keyVaultFake; + @Mock + private KeyConverterRegistry registry; + @Mock + private ReadOnlyVersionedEntityMultiMap entities; + @Mock + private ReadOnlyVersionedEntityMultiMap deletedEntities; + private com.github.nagyesta.lowkeyvault.controller.v7_5.KeyController underTest; + private AutoCloseable openMocks; + + private static KeyVaultKeyModel createResponse() { + final KeyVaultKeyModel model = new KeyVaultKeyModel(); + model.setKey(new JsonWebKeyModel()); + model.setAttributes(new KeyPropertiesModel()); + model.setTags(Map.of()); + return model; + } + + private static DeletedKeyVaultKeyModel createDeletedResponse() { + final DeletedKeyVaultKeyModel model = new DeletedKeyVaultKeyModel(); + model.setKey(new JsonWebKeyModel()); + model.setAttributes(new KeyPropertiesModel()); + model.setTags(Map.of()); + model.setDeletedDate(TIME_10_MINUTES_AGO); + model.setScheduledPurgeDate(TIME_IN_10_MINUTES); + model.setRecoveryId(VERSIONED_KEY_ENTITY_ID_1_VERSION_1.asRecoveryUri(HTTPS_LOCALHOST_8443).toString()); + return model; + } + + @SuppressWarnings("checkstyle:MagicNumber") + public static Stream keyAttributeProvider() { + return Stream.builder() + .add(Arguments.of(null, + null, null, null, null)) + .add(Arguments.of(List.of(), + null, null, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(List.of(), + RecoveryLevel.RECOVERABLE, 90, null, null)) + .add(Arguments.of(List.of(), + RecoveryLevel.RECOVERABLE, 90, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(List.of(), + RecoveryLevel.RECOVERABLE_AND_PURGEABLE, 90, null, null)) + .add(Arguments.of(List.of(), + RecoveryLevel.RECOVERABLE_AND_PURGEABLE, 90, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(List.of(KeyOperation.ENCRYPT), + RecoveryLevel.CUSTOMIZED_RECOVERABLE, 42, null, null)) + .add(Arguments.of(List.of(KeyOperation.ENCRYPT), + RecoveryLevel.CUSTOMIZED_RECOVERABLE, 42, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(Arrays.asList(KeyOperation.values()), + RecoveryLevel.PURGEABLE, null, null, null)) + .add(Arguments.of(Arrays.asList(KeyOperation.values()), + RecoveryLevel.PURGEABLE, null, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .build(); + } + + public static Stream nullProvider() { + final KeyConverterRegistry registry = mock(KeyConverterRegistry.class); + return Stream.builder() + .add(Arguments.of(null, null)) + .add(Arguments.of(registry, null)) + .add(Arguments.of(null, mock(VaultService.class))) + .build(); + } + + public static Stream updateAttributeProvider() { + return Stream.builder() + .add(Arguments.of(null, null, null, null, null)) + .add(Arguments.of(List.of(), TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES, null, TAGS_EMPTY)) + .add(Arguments.of(List.of(), null, TIME_IN_10_MINUTES, null, null)) + .add(Arguments.of(List.of(), null, null, true, TAGS_THREE_KEYS)) + .add(Arguments.of(List.of(KeyOperation.ENCRYPT), TIME_10_MINUTES_AGO, null, false, TAGS_TWO_KEYS)) + .add(Arguments.of(List.of(KeyOperation.ENCRYPT), TIME_IN_10_MINUTES, null, null, TAGS_TWO_KEYS)) + .add(Arguments.of(List.of(KeyOperation.ENCRYPT, KeyOperation.DECRYPT), + TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES, false, TAGS_TWO_KEYS)) + .build(); + } + + public static Stream exceptionProvider() { + final String message = "Message"; + final String failed = "failed"; + return Stream.builder() + .add(Arguments.of(new IllegalStateException(message), + HttpStatus.INTERNAL_SERVER_ERROR, message, null)) + .add(Arguments.of(new NotFoundException(message), + HttpStatus.NOT_FOUND, message, null)) + .add(Arguments.of(new AlreadyExistsException(message), + HttpStatus.CONFLICT, message, null)) + .add(Arguments.of(new CryptoException(message, new RuntimeException(failed)), + HttpStatus.INTERNAL_SERVER_ERROR, message, failed)) + .build(); + } + + @BeforeEach + void setUp() { + openMocks = MockitoAnnotations.openMocks(this); + when(registry.modelConverter(eq(ApiConstants.V_7_5))).thenReturn(keyEntityToV72ModelConverter); + when(registry.itemConverter(eq(ApiConstants.V_7_5))).thenReturn(keyEntityToV72KeyItemModelConverter); + when(registry.versionedItemConverter(eq(ApiConstants.V_7_5))).thenReturn(keyEntityToV72KeyVersionItemModelConverter); + when(registry.versionedEntityId(any(URI.class), anyString(), anyString())).thenCallRealMethod(); + when(registry.entityId(any(URI.class), anyString())).thenCallRealMethod(); + underTest = new com.github.nagyesta.lowkeyvault.controller.v7_5.KeyController(registry, vaultService); + when(vaultService.findByUri(eq(HTTPS_LOCALHOST_8443))).thenReturn(vaultFake); + when(vaultFake.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + when(vaultFake.keyVaultFake()).thenReturn(keyVaultFake); + } + + @AfterEach + void tearDown() throws Exception { + openMocks.close(); + } + + @ParameterizedTest + @MethodSource("exceptionProvider") + void testErrorHandlerConvertsExceptionWhenCaught(final Exception exception, final HttpStatus status, + final String message, final String innerMessage) { + //given + + //when + final ResponseEntity actual = underTest.handleException(exception); + + //then + Assertions.assertEquals(status, actual.getStatusCode()); + final ErrorModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertNotNull(actualBody.error()); + Assertions.assertEquals(message, actualBody.error().getMessage()); + Assertions.assertEquals(exception.getClass().getName(), actualBody.error().getCode()); + final ErrorMessage actualInnerError = actualBody.error().getInnerError(); + if (innerMessage != null) { + Assertions.assertNotNull(actualInnerError); + Assertions.assertEquals(exception.getCause().getClass().getName(), actualInnerError.getCode()); + Assertions.assertEquals(innerMessage, actualInnerError.getMessage()); + } else { + Assertions.assertNull(actualInnerError); + } + } + + @Test + void testErrorHandlerConvertsIllegalArgumentExceptionWhenCaught() { + //given + final HttpStatus status = HttpStatus.BAD_REQUEST; + final String message = "Message"; + final Exception exception = new IllegalArgumentException(message); + + //when + final ResponseEntity actual = underTest.handleArgumentException(exception); + + //then + Assertions.assertEquals(status, actual.getStatusCode()); + final ErrorModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertNotNull(actualBody.error()); + Assertions.assertEquals(message, actualBody.error().getMessage()); + Assertions.assertEquals(exception.getClass().getName(), actualBody.error().getCode()); + final ErrorMessage actualInnerError = actualBody.error().getInnerError(); + Assertions.assertNull(actualInnerError); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNull( + final KeyConverterRegistry registry, + final VaultService vaultService) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new KeyController(registry, vaultService)); + + //then + exception + } + + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testCreateShouldUseInputParametersWhenCalled( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + when(vaultFake.getRecoveryLevel()).thenReturn(RecoveryLevel.PURGEABLE); + when(vaultFake.getRecoverableDays()).thenReturn(null); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final KeyCreateDetailedInput input = KeyCreateDetailedInput.builder() + .key(request.toKeyCreationInput()) + .keyOperations(operations) + .expiresOn(expiry) + .notBefore(notBefore) + .managed(false) + .enabled(true) + .tags(request.getTags()) + .build(); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + when(keyVaultFake.createKeyVersion(eq(KEY_NAME_1), eq(input))) + .thenReturn(VERSIONED_KEY_ENTITY_ID_1_VERSION_1); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_1))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.create(KEY_NAME_1, HTTPS_LOCALHOST_8443, request); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(keyVaultFake).createKeyVersion(eq(KEY_NAME_1), eq(input)); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @Test + void testVersionsShouldThrowExceptionWhenKeyIsNotFound() { + //given + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getVersions(eq(new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null)))) + .thenThrow(new NotFoundException("not found")); + + //when + Assertions.assertThrows(NotFoundException.class, + () -> underTest.versions(KEY_NAME_1, HTTPS_LOCALHOST_8443, 0, 0)); + + //then + exception + } + + @SuppressWarnings("checkstyle:MagicNumber") + @Test + void testVersionsShouldFilterTheListReturnedWhenKeyIsFoundAndHasMoreVersionsThanNeeded() { + //given + final int index = 30; + final LinkedList fullList = IntStream.range(0, 42) + .mapToObj(i -> UUID.randomUUID().toString().replaceAll("-", "")) + .sorted() + .collect(Collectors.toCollection(LinkedList::new)); + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + final String expectedNextUri = baseUri.asUri(HTTPS_LOCALHOST_8443, "versions?api-version=7.5&$skiptoken=31&maxresults=1") + .toString(); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getVersions(eq(baseUri))).thenReturn(fullList); + when(entities.getReadOnlyEntity(any())).thenAnswer(invocation -> { + final VersionedKeyEntityId keyEntityId = invocation.getArgument(0, VersionedKeyEntityId.class); + return createEntity(keyEntityId, createRequest(null, null, null)); + }); + when(keyEntityToV72KeyVersionItemModelConverter.convert(any(), any())).thenAnswer(invocation -> { + final KeyVaultKeyEntity entity = invocation.getArgument(0, KeyVaultKeyEntity.class); + return keyVaultKeyItemModel(entity.getId().asUri(HTTPS_LOCALHOST_8443), Map.of()); + }); + final URI expected = new VersionedKeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, fullList.get(index)).asUri(HTTPS_LOCALHOST_8443); + + //when + final ResponseEntity> actual = + underTest.versions(KEY_NAME_1, HTTPS_LOCALHOST_8443, 1, index); + + //then + Assertions.assertNotNull(actual); + final KeyVaultItemListModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertEquals(expected.toString(), actualBody.getValue().get(0).getKeyId()); + Assertions.assertNotNull(actualBody.getNextLink()); + Assertions.assertEquals(expectedNextUri, actualBody.getNextLink()); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @Test + void testVersionsShouldNotContainNextUriWhenLastPageIsReturnedFully() { + //given + final LinkedList fullList = IntStream.range(0, 25) + .mapToObj(i -> UUID.randomUUID().toString().replaceAll("-", "")) + .sorted() + .collect(Collectors.toCollection(LinkedList::new)); + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getVersions(eq(baseUri))).thenReturn(fullList); + when(entities.getReadOnlyEntity(any())).thenAnswer(invocation -> { + final VersionedKeyEntityId keyEntityId = invocation.getArgument(0, VersionedKeyEntityId.class); + return createEntity(keyEntityId, createRequest(null, null, null)); + }); + when(keyEntityToV72KeyVersionItemModelConverter.convert(any(), any())).thenAnswer(invocation -> { + final KeyVaultKeyEntity entity = invocation.getArgument(0, KeyVaultKeyEntity.class); + return keyVaultKeyItemModel(entity.getId().asUri(HTTPS_LOCALHOST_8443), Map.of()); + }); + final List expected = fullList.stream() + .map(e -> new VersionedKeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, e).asUri(HTTPS_LOCALHOST_8443)) + .collect(Collectors.toList()); + + //when + final ResponseEntity> actual = + underTest.versions(KEY_NAME_1, HTTPS_LOCALHOST_8443, 25, 0); + + //then + Assertions.assertNotNull(actual); + final KeyVaultItemListModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + final List actualList = actualBody.getValue().stream() + .map(KeyVaultKeyItemModel::getKeyId) + .map(URI::create) + .collect(Collectors.toList()); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertIterableEquals(expected, actualList); + Assertions.assertNull(actualBody.getNextLink()); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testDeleteKeyShouldReturnEntryWhenKeyIsFound( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(keyVaultFake.getDeletedEntities()) + .thenReturn(deletedEntities); + doNothing().when(keyVaultFake).delete(eq(baseUri)); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(deletedEntities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_KEY_ENTITY_ID_1_VERSION_3); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(deletedEntities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(DELETED_RESPONSE); + + //when + final ResponseEntity actual = underTest.delete(KEY_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(DELETED_RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + final InOrder inOrder = inOrder(keyVaultFake); + inOrder.verify(keyVaultFake).delete(eq(baseUri)); + inOrder.verify(keyVaultFake, atLeastOnce()).getDeletedEntities(); + verify(keyVaultFake, never()).getEntities(); + verify(deletedEntities).getLatestVersionOfEntity(eq(baseUri)); + verify(deletedEntities).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + verify(keyEntityToV72ModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testRecoverDeletedKeyShouldReturnEntryWhenKeyIsFound( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(keyVaultFake.getDeletedEntities()) + .thenReturn(deletedEntities); + doNothing().when(keyVaultFake).delete(eq(baseUri)); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_KEY_ENTITY_ID_1_VERSION_3); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.recoverDeletedKey(KEY_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + final InOrder inOrder = inOrder(keyVaultFake); + inOrder.verify(keyVaultFake).recover(eq(baseUri)); + inOrder.verify(keyVaultFake, atLeastOnce()).getEntities(); + verify(keyVaultFake, never()).getDeletedEntities(); + verify(entities).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + verify(keyEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testGetDeletedKeyShouldReturnEntryWhenKeyIsFound( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_KEY_ENTITY_ID_1_VERSION_3); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(DELETED_RESPONSE); + + //when + final ResponseEntity actual = underTest.getDeletedKey(KEY_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(DELETED_RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake, never()).getEntities(); + verify(keyVaultFake, atLeastOnce()).getDeletedEntities(); + verify(entities).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + verify(keyEntityToV72ModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testGetShouldReturnEntryWhenKeyIsFound( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_KEY_ENTITY_ID_1_VERSION_3); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.get(KEY_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake, atLeastOnce()).getEntities(); + verify(entities).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + verify(keyEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testGetKeysShouldReturnEntryWhenKeyIsFound( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity)); + final KeyVaultKeyItemModel keyItemModel = keyVaultKeyItemModel(baseUri.asUri(HTTPS_LOCALHOST_8443), Map.of()); + when(keyEntityToV72KeyItemModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(keyItemModel); + + //when + final ResponseEntity> actual = + underTest.listKeys(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(keyItemModel, actual.getBody().getValue().get(0)); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake, atLeastOnce()).getEntities(); + verify(keyVaultFake, never()).getDeletedEntities(); + verify(entities).listLatestEntities(); + verify(keyEntityToV72KeyItemModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testGetKeysShouldReturnNextLinkWhenNotOnLastPage( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity, entity, entity)); + final KeyVaultKeyItemModel keyItemModel = keyVaultKeyItemModel(baseUri.asUri(HTTPS_LOCALHOST_8443), Map.of()); + when(keyEntityToV72KeyItemModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(keyItemModel); + + //when + final ResponseEntity> actual = + underTest.listKeys(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(keyItemModel, actual.getBody().getValue().get(0)); + final String expectedNextLink = HTTPS_LOCALHOST_8443 + "/keys?api-version=7.5&$skiptoken=1&maxresults=1"; + Assertions.assertEquals(expectedNextLink, actual.getBody().getNextLink()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake, atLeastOnce()).getEntities(); + verify(keyVaultFake, never()).getDeletedEntities(); + verify(entities).listLatestEntities(); + verify(keyEntityToV72KeyItemModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testGetDeletedKeysShouldReturnEntryWhenKeyIsFound( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity)); + final DeletedKeyVaultKeyItemModel keyItemModel = deletedKeyVaultKeyItemModel(baseUri, Map.of()); + when(keyEntityToV72KeyItemModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(keyItemModel); + + //when + final ResponseEntity> actual = + underTest.listDeletedKeys(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(keyItemModel, actual.getBody().getValue().get(0)); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake, atLeastOnce()).getDeletedEntities(); + verify(keyVaultFake, never()).getEntities(); + verify(entities).listLatestEntities(); + verify(keyEntityToV72KeyItemModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testPurgeDeletedShouldRemoveEntryWhenDeletedKeyIsPurgeable( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + when(keyVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + final RecoveryLevel nonNullRecoveryLevel = Optional.ofNullable(recoveryLevel).orElse(RecoveryLevel.PURGEABLE); + if (!nonNullRecoveryLevel.isPurgeable()) { + doThrow(IllegalStateException.class).when(keyVaultFake).purge(eq(UNVERSIONED_KEY_ENTITY_ID_1)); + } + + //when + if (nonNullRecoveryLevel.isPurgeable()) { + final ResponseEntity response = underTest.purgeDeleted(KEY_NAME_1, HTTPS_LOCALHOST_8443); + Assertions.assertNotNull(response); + Assertions.assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + } else { + Assertions.assertThrows(IllegalStateException.class, () -> underTest.purgeDeleted(KEY_NAME_1, HTTPS_LOCALHOST_8443)); + } + + //then + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake, never()).getDeletedEntities(); + verify(keyVaultFake, atLeastOnce()).purge(eq(UNVERSIONED_KEY_ENTITY_ID_1)); + verify(keyVaultFake, never()).getEntities(); + verify(entities, never()).listLatestNonManagedEntities(); + verify(keyEntityToV72KeyItemModelConverter, never()).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testGetDeletedKeysShouldReturnNextLinkWhenNotOnLastPage( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + when(keyVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity, entity, entity)); + final DeletedKeyVaultKeyItemModel keyItemModel = deletedKeyVaultKeyItemModel(baseUri, Map.of()); + when(keyEntityToV72KeyItemModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(keyItemModel); + + //when + final ResponseEntity> actual = + underTest.listDeletedKeys(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(keyItemModel, actual.getBody().getValue().get(0)); + final String expectedNextLink = HTTPS_LOCALHOST_8443 + "/deletedkeys?api-version=7.5&$skiptoken=1&maxresults=1"; + Assertions.assertEquals(expectedNextLink, actual.getBody().getNextLink()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake, atLeastOnce()).getDeletedEntities(); + verify(keyVaultFake, never()).getEntities(); + verify(entities).listLatestEntities(); + verify(keyEntityToV72KeyItemModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("keyAttributeProvider") + void testGetWithVersionShouldReturnEntryWhenKeyAndVersionIsFound( + final List operations, final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + final CreateKeyRequest request = createRequest(operations, expiry, notBefore); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, request); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.getWithVersion(KEY_NAME_1, KEY_VERSION_3, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake).getEntities(); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + verify(keyEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("updateAttributeProvider") + void testUpdateVersionShouldReturnEntryWhenKeyAndVersionIsFound( + final List operations, final OffsetDateTime expiry, final OffsetDateTime notBefore, + final Boolean enabled, final Map tags) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + final CreateKeyRequest createKeyRequest = createRequest(null, null, null); + final UpdateKeyRequest updateKeyRequest = new UpdateKeyRequest(); + if (operations != null) { + updateKeyRequest.setKeyOperations(operations); + } + if (tags != null) { + updateKeyRequest.setTags(tags); + } + if (enabled != null || expiry != null || notBefore != null) { + final BasePropertiesUpdateModel properties = new BasePropertiesUpdateModel(); + properties.setEnabled(enabled); + properties.setExpiresOn(expiry); + properties.setNotBefore(notBefore); + updateKeyRequest.setProperties(properties); + } + final ReadOnlyKeyVaultKeyEntity entity = createEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, createKeyRequest); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(RecoveryLevel.PURGEABLE); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest + .updateVersion(KEY_NAME_1, KEY_VERSION_3, HTTPS_LOCALHOST_8443, updateKeyRequest); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).keyVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(keyVaultFake).getEntities(); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + final InOrder inOrder = inOrder(keyVaultFake, entities); + if (operations != null) { + inOrder.verify(keyVaultFake) + .setKeyOperations(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), same(updateKeyRequest.getKeyOperations())); + } else { + inOrder.verify(keyVaultFake, never()) + .setKeyOperations(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), anyList()); + } + if (enabled != null) { + inOrder.verify(keyVaultFake) + .setEnabled(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), eq(enabled)); + } else { + inOrder.verify(keyVaultFake, never()) + .setEnabled(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), anyBoolean()); + } + if (expiry != null || notBefore != null) { + inOrder.verify(keyVaultFake) + .setExpiry(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), eq(notBefore), eq(expiry)); + } else { + inOrder.verify(keyVaultFake, never()) + .setExpiry(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), any(), any()); + } + if (tags != null) { + inOrder.verify(keyVaultFake) + .clearTags(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + inOrder.verify(keyVaultFake) + .addTags(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), same(updateKeyRequest.getTags())); + } else { + inOrder.verify(keyVaultFake, never()) + .clearTags(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + inOrder.verify(keyVaultFake, never()) + .addTags(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3), anyMap()); + } + inOrder.verify(entities).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + verify(keyEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @Test + void testRotateKeyShouldCallTheKeyVaultFakeForDoingTheRotationWhenCalled() { + //given + final KeyEntityId entityId = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + final VersionedKeyEntityId newKeyId = new VersionedKeyEntityId(entityId.vault(), entityId.id()); + final CreateKeyRequest request = createRequest(List.of(), null, null); + final ReadOnlyKeyVaultKeyEntity entity = createEntity(newKeyId, request); + when(keyVaultFake.rotateKey(eq(entityId))) + .thenReturn(newKeyId); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getReadOnlyEntity(eq(newKeyId))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(newKeyId.vault()))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.rotateKey(entityId.id(), entityId.vault()); + + //then + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertEquals(RESPONSE, actual.getBody()); + final InOrder inOrder = inOrder(keyVaultFake, entities, keyEntityToV72ModelConverter); + inOrder.verify(keyVaultFake) + .rotateKey(eq(entityId)); + inOrder.verify(keyVaultFake) + .getEntities(); + inOrder.verify(entities) + .getReadOnlyEntity(eq(newKeyId)); + inOrder.verify(keyEntityToV72ModelConverter) + .convert(same(entity), eq(newKeyId.vault())); + } + + @NonNull + private CreateKeyRequest createRequest( + final List operations, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + final CreateKeyRequest keyRequest = new CreateKeyRequest(); + keyRequest.setKeyType(KeyType.RSA); + keyRequest.setKeyOperations(operations); + final KeyPropertiesModel properties = new KeyPropertiesModel(); + properties.setExpiresOn(expiry); + properties.setNotBefore(notBefore); + properties.setEnabled(true); + keyRequest.setProperties(properties); + keyRequest.setTags(TAGS_TWO_KEYS); + return keyRequest; + } + + private KeyCreateDetailedInput detailedInput( + final List operations, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + final RsaKeyCreationInput keyRequest = new RsaKeyCreationInput(KeyType.RSA, + KeyType.RSA.getValidKeyParameters(Integer.class).first(), null); + return KeyCreateDetailedInput.builder() + .key(keyRequest) + .managed(false) + .keyOperations(operations) + .expiresOn(expiry) + .notBefore(notBefore) + .enabled(true) + .tags(TAGS_TWO_KEYS) + .build(); + } + + @NonNull + private KeyVaultKeyEntity createEntity(final VersionedKeyEntityId keyEntityId, final CreateKeyRequest createKeyRequest) { + return new RsaKeyVaultKeyEntity(keyEntityId, vaultFake, createKeyRequest.getKeySize(), null, false); + } + + private KeyVaultKeyItemModel keyVaultKeyItemModel(final URI asUriNoVersion, final Map tags) { + final KeyVaultKeyItemModel model = new KeyVaultKeyItemModel(); + model.setAttributes(new KeyPropertiesModel()); + model.setKeyId(asUriNoVersion.toString()); + model.setTags(tags); + return model; + } + + private DeletedKeyVaultKeyItemModel deletedKeyVaultKeyItemModel(final KeyEntityId id, final Map tags) { + final DeletedKeyVaultKeyItemModel model = new DeletedKeyVaultKeyItemModel(); + model.setAttributes(new KeyPropertiesModel()); + model.setKeyId(id.asUriNoVersion(id.vault()).toString()); + model.setTags(tags); + model.setDeletedDate(TIME_10_MINUTES_AGO); + model.setScheduledPurgeDate(TIME_IN_10_MINUTES); + model.setRecoveryId(id.asRecoveryUri(id.vault()).toString()); + return model; + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyCryptoControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyCryptoControllerTest.java new file mode 100644 index 00000000..12e85cdf --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyCryptoControllerTest.java @@ -0,0 +1,264 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.HashUtil; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72KeyItemModelConverter; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72KeyVersionItemModelConverter; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72ModelConverter; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.*; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.*; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.CreateKeyRequest; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.KeyOperationsParameters; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.KeySignParameters; +import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.KeyVerifyParameters; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.RandomBytesRequest; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.RandomBytesResponse; +import com.github.nagyesta.lowkeyvault.service.common.ReadOnlyVersionedEntityMultiMap; +import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake; +import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.impl.KeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.key.impl.RsaKeyVaultKeyEntity; +import com.github.nagyesta.lowkeyvault.service.vault.VaultFake; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstants.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsKeys.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.HTTPS_LOCALHOST_8443; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.*; + +class KeyCryptoControllerTest { + + private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding(); + private static final Base64.Decoder DECODER = Base64.getUrlDecoder(); + private static final KeyVaultKeyModel RESPONSE = createResponse(); + @Mock + private KeyEntityToV72ModelConverter keyEntityToV72ModelConverter; + @Mock + private KeyEntityToV72KeyItemModelConverter keyEntityToV72KeyItemModelConverter; + @Mock + private KeyEntityToV72KeyVersionItemModelConverter keyEntityToV72KeyVersionItemModelConverter; + @Mock + private VaultService vaultService; + @Mock + private VaultFake vaultFake; + @Mock + private KeyVaultFake keyVaultFake; + @Mock + private KeyConverterRegistry registry; + @Mock + private ReadOnlyVersionedEntityMultiMap entities; + private com.github.nagyesta.lowkeyvault.controller.v7_5.KeyCryptoController underTest; + private AutoCloseable openMocks; + + private static KeyVaultKeyModel createResponse() { + final KeyVaultKeyModel model = new KeyVaultKeyModel(); + model.setKey(new JsonWebKeyModel()); + model.setAttributes(new KeyPropertiesModel()); + model.setTags(Map.of()); + return model; + } + + public static Stream nullProvider() { + final KeyConverterRegistry registry = mock(KeyConverterRegistry.class); + return Stream.builder() + .add(Arguments.of(null, null)) + .add(Arguments.of(registry, null)) + .add(Arguments.of(null, mock(VaultService.class))) + .build(); + } + + @BeforeEach + void setUp() { + openMocks = MockitoAnnotations.openMocks(this); + when(registry.modelConverter(eq(ApiConstants.V_7_5))).thenReturn(keyEntityToV72ModelConverter); + when(registry.itemConverter(eq(ApiConstants.V_7_5))).thenReturn(keyEntityToV72KeyItemModelConverter); + when(registry.versionedItemConverter(eq(ApiConstants.V_7_5))).thenReturn(keyEntityToV72KeyVersionItemModelConverter); + when(registry.versionedEntityId(any(URI.class), anyString(), anyString())).thenCallRealMethod(); + when(registry.entityId(any(URI.class), anyString())).thenCallRealMethod(); + underTest = new com.github.nagyesta.lowkeyvault.controller.v7_5.KeyCryptoController(registry, vaultService); + when(vaultService.findByUri(eq(HTTPS_LOCALHOST_8443))).thenReturn(vaultFake); + when(vaultFake.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + when(vaultFake.keyVaultFake()).thenReturn(keyVaultFake); + } + + @AfterEach + void tearDown() throws Exception { + openMocks.close(); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNull( + final KeyConverterRegistry registry, + final VaultService vaultService) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new KeyCryptoController(registry, vaultService)); + + //then + exception + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @ValueSource(strings = {BLANK, DEFAULT_VAULT, LOCALHOST, LOWKEY_VAULT}) + void testEncryptAndDecryptShouldGetBackOriginalInputWhenKeyAndVersionIsFound(final String clearText) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + final List operations = List.of( + KeyOperation.ENCRYPT, KeyOperation.DECRYPT, KeyOperation.WRAP_KEY, KeyOperation.UNWRAP_KEY); + final CreateKeyRequest request = createRequest(operations); + final RsaKeyVaultKeyEntity entity = (RsaKeyVaultKeyEntity) createEntity(request); + entity.setEnabled(true); + entity.setOperations(operations); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + final KeyOperationsParameters encryptParameters = new KeyOperationsParameters(); + encryptParameters.setAlgorithm(EncryptionAlgorithm.RSA_OAEP_256); + encryptParameters.setValue(ENCODER.encodeToString(clearText.getBytes(StandardCharsets.UTF_8))); + + //when + final ResponseEntity encrypted = underTest + .encrypt(KEY_NAME_1, KEY_VERSION_3, HTTPS_LOCALHOST_8443, encryptParameters); + Assertions.assertNotNull(encrypted); + Assertions.assertEquals(HttpStatus.OK, encrypted.getStatusCode()); + Assertions.assertNotNull(encrypted.getBody()); + Assertions.assertNotEquals(clearText, encrypted.getBody().getValue()); + + final KeyOperationsParameters decryptParameters = new KeyOperationsParameters(); + decryptParameters.setAlgorithm(EncryptionAlgorithm.RSA_OAEP_256); + decryptParameters.setValue(encrypted.getBody().getValue()); + final ResponseEntity actual = underTest + .decrypt(KEY_NAME_1, KEY_VERSION_3, HTTPS_LOCALHOST_8443, decryptParameters); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + final String decoded = new String(DECODER.decode(actual.getBody().getValue()), StandardCharsets.UTF_8); + Assertions.assertEquals(clearText, decoded); + + verify(vaultService, times(2)).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake, times(2)).keyVaultFake(); + verify(keyVaultFake, times(2)).getEntities(); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + verify(entities, times(2)).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @ValueSource(strings = {BLANK, DEFAULT_VAULT, LOCALHOST, LOWKEY_VAULT}) + void testSignAndVerifyShouldReturnTrueWhenKeyAndVersionIsFoundAndCalledInSequence(final String clearText) { + //given + final KeyEntityId baseUri = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null); + final List operations = List.of(KeyOperation.SIGN, KeyOperation.VERIFY); + final CreateKeyRequest request = createRequest(operations); + final RsaKeyVaultKeyEntity entity = (RsaKeyVaultKeyEntity) createEntity(request); + entity.setEnabled(true); + entity.setOperations(operations); + when(keyVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(keyEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + final KeySignParameters keySignParameters = new KeySignParameters(); + keySignParameters.setAlgorithm(SignatureAlgorithm.PS256); + keySignParameters.setValue(ENCODER.encodeToString(HashUtil.hash(clearText.getBytes(StandardCharsets.UTF_8), HashAlgorithm.SHA256))); + + //when + final ResponseEntity signature = underTest + .sign(KEY_NAME_1, KEY_VERSION_3, HTTPS_LOCALHOST_8443, keySignParameters); + Assertions.assertNotNull(signature); + Assertions.assertEquals(HttpStatus.OK, signature.getStatusCode()); + Assertions.assertNotNull(signature.getBody()); + Assertions.assertNotEquals(clearText, signature.getBody().getValue()); + + final KeyVerifyParameters verifyParameters = new KeyVerifyParameters(); + verifyParameters.setAlgorithm(SignatureAlgorithm.PS256); + verifyParameters.setDigest(ENCODER.encodeToString(HashUtil.hash(clearText.getBytes(StandardCharsets.UTF_8), HashAlgorithm.SHA256))); + verifyParameters.setValue(signature.getBody().getValue()); + final ResponseEntity actual = underTest + .verify(KEY_NAME_1, KEY_VERSION_3, HTTPS_LOCALHOST_8443, verifyParameters); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertTrue(actual.getBody().isValue()); + + verify(vaultService, times(2)).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake, times(2)).keyVaultFake(); + verify(keyVaultFake, times(2)).getEntities(); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + verify(entities, times(2)).getReadOnlyEntity(eq(VERSIONED_KEY_ENTITY_ID_1_VERSION_3)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @Test + void testGetRandomBytesShouldReturnRandomBytesWhenCalledWithValidData() { + //given + final RandomBytesRequest request = new RandomBytesRequest(); + request.setCount(128); + + //when + final ResponseEntity response = underTest.getRandomBytes(request); + + //then + final RandomBytesResponse body = response.getBody(); + Assertions.assertNotNull(body); + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assertions.assertEquals(128, body.getValue().length); + } + + @NonNull + private CreateKeyRequest createRequest( + final List operations) { + final CreateKeyRequest keyRequest = new CreateKeyRequest(); + keyRequest.setKeyType(KeyType.RSA); + keyRequest.setKeyOperations(operations); + final KeyPropertiesModel properties = new KeyPropertiesModel(); + properties.setExpiresOn(null); + properties.setNotBefore(null); + properties.setEnabled(true); + keyRequest.setProperties(properties); + keyRequest.setTags(TAGS_TWO_KEYS); + return keyRequest; + } + + @NonNull + private KeyVaultKeyEntity createEntity(final CreateKeyRequest createKeyRequest) { + return new RsaKeyVaultKeyEntity(VERSIONED_KEY_ENTITY_ID_1_VERSION_1, + vaultFake, createKeyRequest.getKeySize(), null, false); + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyPolicyControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyPolicyControllerTest.java new file mode 100644 index 00000000..66074b4c --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/KeyPolicyControllerTest.java @@ -0,0 +1,139 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.mapper.common.registry.KeyConverterRegistry; +import com.github.nagyesta.lowkeyvault.mapper.v7_3.key.KeyRotationPolicyToV73ModelConverter; +import com.github.nagyesta.lowkeyvault.mapper.v7_3.key.KeyRotationPolicyV73ModelToEntityConverter; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyRotationPolicyModel; +import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake; +import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId; +import com.github.nagyesta.lowkeyvault.service.key.impl.KeyRotationPolicy; +import com.github.nagyesta.lowkeyvault.service.vault.VaultFake; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.net.URI; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstantsKeys.KEY_NAME_1; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.HTTPS_LOCALHOST_8443; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.*; + +class KeyPolicyControllerTest { + + @Mock + private VaultService vaultService; + @Mock + private VaultFake vaultFake; + @Mock + private KeyVaultFake keyVaultFake; + @Mock + private KeyConverterRegistry registry; + @Mock + private KeyRotationPolicyToV73ModelConverter keyRotationPolicyToV73ModelConverter; + @Mock + private KeyRotationPolicyV73ModelToEntityConverter rotationV73ModelToEntityConverter; + private com.github.nagyesta.lowkeyvault.controller.v7_5.KeyPolicyController underTest; + private AutoCloseable openMocks; + + public static Stream nullProvider() { + final VaultService service = mock(VaultService.class); + final KeyConverterRegistry registry = mock(KeyConverterRegistry.class); + return Stream.builder() + .add(Arguments.of(service, null)) + .add(Arguments.of(null, registry)) + .build(); + } + + @BeforeEach + void setUp() { + openMocks = MockitoAnnotations.openMocks(this); + when(registry.rotationPolicyModelConverter(eq(ApiConstants.V_7_5))).thenReturn(keyRotationPolicyToV73ModelConverter); + when(registry.rotationPolicyEntityConverter(eq(ApiConstants.V_7_5))).thenReturn(rotationV73ModelToEntityConverter); + when(registry.versionedEntityId(any(URI.class), anyString(), anyString())).thenCallRealMethod(); + when(registry.entityId(any(URI.class), anyString())).thenCallRealMethod(); + underTest = new com.github.nagyesta.lowkeyvault.controller.v7_5.KeyPolicyController(registry, vaultService); + when(vaultService.findByUri(eq(HTTPS_LOCALHOST_8443))).thenReturn(vaultFake); + when(vaultFake.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + when(vaultFake.keyVaultFake()).thenReturn(keyVaultFake); + } + + @AfterEach + void tearDown() throws Exception { + openMocks.close(); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNull(final VaultService service, final KeyConverterRegistry registry) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new KeyPolicyController(registry, service)); + + //then + exception + } + + @Test + void testGetRotationPolicyShouldReturnTheRotationPolicyWhenItIsAlreadySet() { + //given + final KeyEntityId entityId = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1); + final KeyRotationPolicy rotationPolicy = mock(KeyRotationPolicy.class); + final KeyRotationPolicyModel model = mock(KeyRotationPolicyModel.class); + when(keyVaultFake.rotationPolicy(eq(entityId))) + .thenReturn(rotationPolicy); + when(keyRotationPolicyToV73ModelConverter.convert(same(rotationPolicy), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(model); + + //when + final ResponseEntity actual = underTest.getRotationPolicy(KEY_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(model, actual.getBody()); + final InOrder inOrder = inOrder(keyVaultFake, keyRotationPolicyToV73ModelConverter); + inOrder.verify(keyVaultFake).rotationPolicy(eq(entityId)); + inOrder.verify(keyRotationPolicyToV73ModelConverter).convert(same(rotationPolicy), eq(HTTPS_LOCALHOST_8443)); + } + + @Test + void testUpdateRotationPolicyShouldReturnTheRotationPolicyWhenItIsAlreadySet() { + //given + final KeyEntityId entityId = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1); + final KeyRotationPolicy rotationPolicy = mock(KeyRotationPolicy.class); + final KeyRotationPolicyModel input = mock(KeyRotationPolicyModel.class); + final KeyRotationPolicyModel output = mock(KeyRotationPolicyModel.class); + when(keyVaultFake.rotationPolicy(eq(entityId))) + .thenReturn(rotationPolicy); + when(rotationV73ModelToEntityConverter.convert(same(input))) + .thenReturn(rotationPolicy); + when(keyRotationPolicyToV73ModelConverter.convert(same(rotationPolicy), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(output); + + //when + final ResponseEntity actual = underTest.updateRotationPolicy(KEY_NAME_1, HTTPS_LOCALHOST_8443, input); + + //then + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(output, actual.getBody()); + final InOrder inOrder = inOrder(keyVaultFake, rotationV73ModelToEntityConverter, keyRotationPolicyToV73ModelConverter); + inOrder.verify(rotationV73ModelToEntityConverter).convert(same(input)); + inOrder.verify(keyVaultFake).setRotationPolicy(same(rotationPolicy)); + inOrder.verify(keyVaultFake).rotationPolicy(eq(entityId)); + inOrder.verify(keyRotationPolicyToV73ModelConverter).convert(same(rotationPolicy), eq(HTTPS_LOCALHOST_8443)); + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretBackupRestoreControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretBackupRestoreControllerIntegrationTest.java new file mode 100644 index 00000000..c175d77b --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretBackupRestoreControllerIntegrationTest.java @@ -0,0 +1,260 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.abortmission.booster.jupiter.annotation.LaunchAbortArmed; +import com.github.nagyesta.lowkeyvault.TestConstantsUri; +import com.github.nagyesta.lowkeyvault.mapper.common.registry.SecretConverterRegistry; +import com.github.nagyesta.lowkeyvault.model.common.backup.SecretBackupList; +import com.github.nagyesta.lowkeyvault.model.common.backup.SecretBackupListItem; +import com.github.nagyesta.lowkeyvault.model.common.backup.SecretBackupModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.KeyVaultSecretModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.SecretPropertiesModel; +import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException; +import com.github.nagyesta.lowkeyvault.service.secret.SecretVaultFake; +import com.github.nagyesta.lowkeyvault.service.secret.id.VersionedSecretEntityId; +import com.github.nagyesta.lowkeyvault.service.secret.impl.SecretCreateInput; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MimeTypeUtils; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstants.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsSecrets.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.getRandomVaultUri; +import static org.mockito.Mockito.mock; + +@LaunchAbortArmed +@SpringBootTest +class SecretBackupRestoreControllerIntegrationTest { + + @Autowired + @Qualifier("SecretBackupRestoreControllerV75") + private com.github.nagyesta.lowkeyvault.controller.v7_5.SecretBackupRestoreController underTest; + @Autowired + private VaultService vaultService; + private URI uri; + + public static Stream nullProvider() { + return Stream.builder() + .add(Arguments.of(null, null)) + .add(Arguments.of(mock(SecretConverterRegistry.class), null)) + .add(Arguments.of(null, mock(VaultService.class))) + .build(); + } + + @BeforeEach + void setUp() { + final String name = UUID.randomUUID().toString(); + uri = getRandomVaultUri(); + vaultService.create(uri, RecoveryLevel.RECOVERABLE_AND_PURGEABLE, RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE, null); + } + + @AfterEach + void tearDown() { + vaultService.delete(uri); + vaultService.purge(uri); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNulls( + final SecretConverterRegistry registry, + final VaultService vaultService) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new SecretBackupRestoreController(registry, vaultService)); + + //then + exception + } + + @Test + void testRestoreEntityShouldRestoreASingleSecretWhenCalledWithValidInput() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_1, backupModel, TAGS_THREE_KEYS); + + //when + final ResponseEntity actual = underTest.restore(uri, backupModel); + + //then + Assertions.assertNotNull(actual); + final KeyVaultSecretModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + assertRestoredSecretMatchesExpectations(actualBody, SECRET_VERSION_1, TAGS_THREE_KEYS); + } + + @Test + void testRestoreEntityShouldRestoreAThreeSecretsWhenCalledWithValidInput() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_1, backupModel, null); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_2, backupModel, TAGS_THREE_KEYS); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_3, backupModel, TAGS_EMPTY); + + //when + final ResponseEntity actual = underTest.restore(uri, backupModel); + + //then + Assertions.assertNotNull(actual); + final KeyVaultSecretModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + assertRestoredSecretMatchesExpectations(actualBody, SECRET_VERSION_3, TAGS_EMPTY); + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenCalledWithMoreThanOneUris() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_1, backupModel, null); + addVersionToList(TestConstantsUri.HTTPS_DEFAULT_LOWKEY_VAULT, SECRET_NAME_1, SECRET_VERSION_2, backupModel, TAGS_THREE_KEYS); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenCalledWithMoreThanOneNames() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_1, backupModel, null); + addVersionToList(uri, SECRET_NAME_2, SECRET_VERSION_2, backupModel, TAGS_THREE_KEYS); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenCalledWithUnknownUri() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(URI.create("https://uknknown.uri"), SECRET_NAME_1, SECRET_VERSION_1, backupModel, null); + + //when + Assertions.assertThrows(NotFoundException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenNameMatchesActiveSecret() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_1, backupModel, TAGS_EMPTY); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_2, backupModel, TAGS_ONE_KEY); + vaultService.findByUri(uri).secretVaultFake().createSecretVersion(SECRET_NAME_1, SecretCreateInput.builder() + .value(LOWKEY_VAULT) + .build()); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testRestoreEntityShouldThrowExceptionWhenNameMatchesDeletedSecret() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_1, backupModel, TAGS_EMPTY); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_2, backupModel, TAGS_ONE_KEY); + final SecretVaultFake vaultFake = vaultService.findByUri(uri).secretVaultFake(); + final VersionedSecretEntityId secretVersion = vaultFake.createSecretVersion(SECRET_NAME_1, SecretCreateInput.builder() + .value(LOWKEY_VAULT) + .build()); + vaultFake.delete(secretVersion); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.restore(uri, backupModel)); + + //then + exception + } + + @Test + void testBackupEntityShouldReturnTheOriginalBackupModelWhenCalledAfterRestoreEntity() { + //given + final SecretBackupModel backupModel = new SecretBackupModel(); + backupModel.setValue(new SecretBackupList()); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_1, backupModel, TAGS_EMPTY); + addVersionToList(uri, SECRET_NAME_1, SECRET_VERSION_2, backupModel, TAGS_ONE_KEY); + underTest.restore(uri, backupModel); + + //when + final ResponseEntity actual = underTest.backup(SECRET_NAME_1, uri); + + //then + Assertions.assertNotNull(actual); + final SecretBackupModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(backupModel, actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + } + + private void assertRestoredSecretMatchesExpectations( + final KeyVaultSecretModel actualBody, final String version, final Map expectedTags) { + Assertions.assertEquals(LOWKEY_VAULT, actualBody.getValue()); + Assertions.assertEquals(MimeTypeUtils.TEXT_PLAIN_VALUE, actualBody.getContentType()); + Assertions.assertEquals(new VersionedSecretEntityId(uri, SECRET_NAME_1, version).asUri(uri).toString(), actualBody.getId()); + Assertions.assertEquals(TIME_10_MINUTES_AGO, actualBody.getAttributes().getCreatedOn()); + Assertions.assertEquals(NOW, actualBody.getAttributes().getUpdatedOn()); + Assertions.assertEquals(TIME_IN_10_MINUTES, actualBody.getAttributes().getNotBefore()); + Assertions.assertEquals(TIME_IN_10_MINUTES.plusDays(1), actualBody.getAttributes().getExpiresOn()); + Assertions.assertEquals(RecoveryLevel.RECOVERABLE_AND_PURGEABLE, actualBody.getAttributes().getRecoveryLevel()); + Assertions.assertEquals(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE, actualBody.getAttributes().getRecoverableDays()); + Assertions.assertTrue(actualBody.getAttributes().isEnabled()); + Assertions.assertEquals(expectedTags, actualBody.getTags()); + } + + private void addVersionToList(final URI baseUri, final String name, final String version, + final SecretBackupModel backupModel, final Map tags) { + final SecretBackupListItem listItem = new SecretBackupListItem(); + listItem.setValue(LOWKEY_VAULT); + listItem.setContentType(MimeTypeUtils.TEXT_PLAIN_VALUE); + listItem.setVaultBaseUri(baseUri); + listItem.setId(name); + listItem.setVersion(version); + final SecretPropertiesModel propertiesModel = new SecretPropertiesModel(); + propertiesModel.setCreatedOn(TIME_10_MINUTES_AGO); + propertiesModel.setUpdatedOn(NOW); + propertiesModel.setNotBefore(TIME_IN_10_MINUTES); + propertiesModel.setExpiresOn(TIME_IN_10_MINUTES.plusDays(1)); + propertiesModel.setRecoveryLevel(RecoveryLevel.RECOVERABLE_AND_PURGEABLE); + propertiesModel.setRecoverableDays(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE); + listItem.setAttributes(propertiesModel); + listItem.setTags(tags); + final List list = new ArrayList<>(backupModel.getValue().getVersions()); + list.add(listItem); + backupModel.getValue().setVersions(list); + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretControllerTest.java new file mode 100644 index 00000000..f1649116 --- /dev/null +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_5/SecretControllerTest.java @@ -0,0 +1,940 @@ +package com.github.nagyesta.lowkeyvault.controller.v7_5; + +import com.github.nagyesta.lowkeyvault.mapper.common.registry.SecretConverterRegistry; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.secret.SecretEntityToV72ModelConverter; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.secret.SecretEntityToV72SecretItemModelConverter; +import com.github.nagyesta.lowkeyvault.mapper.v7_2.secret.SecretEntityToV72SecretVersionItemModelConverter; +import com.github.nagyesta.lowkeyvault.model.common.ApiConstants; +import com.github.nagyesta.lowkeyvault.model.common.KeyVaultItemListModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.BasePropertiesUpdateModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.*; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.request.CreateSecretRequest; +import com.github.nagyesta.lowkeyvault.model.v7_2.secret.request.UpdateSecretRequest; +import com.github.nagyesta.lowkeyvault.service.common.ReadOnlyVersionedEntityMultiMap; +import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException; +import com.github.nagyesta.lowkeyvault.service.secret.ReadOnlyKeyVaultSecretEntity; +import com.github.nagyesta.lowkeyvault.service.secret.SecretVaultFake; +import com.github.nagyesta.lowkeyvault.service.secret.id.SecretEntityId; +import com.github.nagyesta.lowkeyvault.service.secret.id.VersionedSecretEntityId; +import com.github.nagyesta.lowkeyvault.service.secret.impl.KeyVaultSecretEntity; +import com.github.nagyesta.lowkeyvault.service.secret.impl.SecretCreateInput; +import com.github.nagyesta.lowkeyvault.service.vault.VaultFake; +import com.github.nagyesta.lowkeyvault.service.vault.VaultService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static com.github.nagyesta.lowkeyvault.TestConstants.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsSecrets.*; +import static com.github.nagyesta.lowkeyvault.TestConstantsUri.HTTPS_LOCALHOST_8443; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.*; + +class SecretControllerTest { + + private static final KeyVaultSecretModel RESPONSE = createResponse(); + private static final DeletedKeyVaultSecretModel DELETED_RESPONSE = createDeletedResponse(); + @Mock + private SecretEntityToV72ModelConverter secretEntityToV72ModelConverter; + @Mock + private SecretEntityToV72SecretItemModelConverter secretEntityToV72SecretItemModelConverter; + @Mock + private SecretEntityToV72SecretVersionItemModelConverter secretEntityToV72SecretVersionItemModelConverter; + @Mock + private VaultService vaultService; + @Mock + private VaultFake vaultFake; + @Mock + private SecretVaultFake secretVaultFake; + @Mock + private SecretConverterRegistry registry; + @Mock + private ReadOnlyVersionedEntityMultiMap entities; + @Mock + private ReadOnlyVersionedEntityMultiMap deletedEntities; + private com.github.nagyesta.lowkeyvault.controller.v7_5.SecretController underTest; + private AutoCloseable openMocks; + + private static KeyVaultSecretModel createResponse() { + final KeyVaultSecretModel model = new KeyVaultSecretModel(); + model.setValue(LOWKEY_VAULT); + model.setAttributes(new SecretPropertiesModel()); + model.setTags(Map.of()); + return model; + } + + private static DeletedKeyVaultSecretModel createDeletedResponse() { + final DeletedKeyVaultSecretModel model = new DeletedKeyVaultSecretModel(); + model.setValue(LOWKEY_VAULT); + model.setAttributes(new SecretPropertiesModel()); + model.setTags(Map.of()); + model.setDeletedDate(TIME_10_MINUTES_AGO); + model.setScheduledPurgeDate(TIME_IN_10_MINUTES); + model.setRecoveryId(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1.asRecoveryUri(HTTPS_LOCALHOST_8443).toString()); + return model; + } + + @SuppressWarnings("checkstyle:MagicNumber") + public static Stream secretAttributeProvider() { + return Stream.builder() + .add(Arguments.of(null, null, null, null)) + .add(Arguments.of(null, null, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(RecoveryLevel.RECOVERABLE, 90, null, null)) + .add(Arguments.of(RecoveryLevel.RECOVERABLE, 90, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(RecoveryLevel.RECOVERABLE_AND_PURGEABLE, 90, null, null)) + .add(Arguments.of(RecoveryLevel.RECOVERABLE_AND_PURGEABLE, 90, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(RecoveryLevel.CUSTOMIZED_RECOVERABLE, 42, null, null)) + .add(Arguments.of(RecoveryLevel.CUSTOMIZED_RECOVERABLE, 42, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .add(Arguments.of(RecoveryLevel.PURGEABLE, null, null, null)) + .add(Arguments.of(RecoveryLevel.PURGEABLE, null, TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES)) + .build(); + } + + public static Stream nullProvider() { + return Stream.builder() + .add(Arguments.of(null, null)) + .add(Arguments.of(mock(SecretConverterRegistry.class), null)) + .add(Arguments.of(null, mock(VaultService.class))) + .build(); + } + + public static Stream updateAttributeProvider() { + return Stream.builder() + .add(Arguments.of(null, null, null, null)) + .add(Arguments.of(TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES, null, TAGS_EMPTY)) + .add(Arguments.of(null, TIME_IN_10_MINUTES, null, null)) + .add(Arguments.of(null, null, true, TAGS_THREE_KEYS)) + .add(Arguments.of(TIME_10_MINUTES_AGO, null, false, TAGS_TWO_KEYS)) + .add(Arguments.of(TIME_IN_10_MINUTES, null, null, TAGS_TWO_KEYS)) + .add(Arguments.of(TIME_10_MINUTES_AGO, TIME_IN_10_MINUTES, false, TAGS_TWO_KEYS)) + .build(); + } + + @BeforeEach + void setUp() { + openMocks = MockitoAnnotations.openMocks(this); + when(registry.modelConverter(eq(ApiConstants.V_7_5))).thenReturn(secretEntityToV72ModelConverter); + when(registry.itemConverter(eq(ApiConstants.V_7_5))).thenReturn(secretEntityToV72SecretItemModelConverter); + when(registry.versionedItemConverter(eq(ApiConstants.V_7_5))) + .thenReturn(secretEntityToV72SecretVersionItemModelConverter); + when(registry.versionedEntityId(any(URI.class), anyString(), anyString())).thenCallRealMethod(); + when(registry.entityId(any(URI.class), anyString())).thenCallRealMethod(); + underTest = new com.github.nagyesta.lowkeyvault.controller.v7_5.SecretController(registry, vaultService); + when(vaultService.findByUri(eq(HTTPS_LOCALHOST_8443))).thenReturn(vaultFake); + when(vaultFake.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + when(vaultFake.secretVaultFake()).thenReturn(secretVaultFake); + } + + @AfterEach + void tearDown() throws Exception { + openMocks.close(); + } + + @ParameterizedTest + @MethodSource("nullProvider") + void testConstructorShouldThrowExceptionWhenCalledWithNull( + final SecretConverterRegistry registry, + final VaultService vaultService) { + //given + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> new SecretController(registry, vaultService)); + + //then + exception + } + + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testCreateShouldUseInputParametersWhenCalled( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + when(vaultFake.getRecoveryLevel()).thenReturn(RecoveryLevel.PURGEABLE); + when(vaultFake.getRecoverableDays()).thenReturn(null); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + final ArgumentCaptor input = ArgumentCaptor.forClass(SecretCreateInput.class); + when(secretVaultFake.createSecretVersion(eq(SECRET_NAME_1), input.capture())) + .thenReturn(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.create(SECRET_NAME_1, HTTPS_LOCALHOST_8443, request); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(secretVaultFake).createSecretVersion(eq(SECRET_NAME_1), any(SecretCreateInput.class)); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + final SecretCreateInput captured = input.getValue(); + Assertions.assertEquals(request.getValue(), captured.getValue()); + Assertions.assertEquals(request.getContentType(), captured.getContentType()); + Assertions.assertEquals(request.getProperties().getExpiresOn(), captured.getExpiresOn()); + Assertions.assertEquals(request.getProperties().getNotBefore(), captured.getNotBefore()); + } + + @Test + void testVersionsShouldThrowExceptionWhenSecretIsNotFound() { + //given + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getVersions(eq(new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null)))) + .thenThrow(new NotFoundException("not found")); + + //when + Assertions.assertThrows(NotFoundException.class, + () -> underTest.versions(SECRET_NAME_1, HTTPS_LOCALHOST_8443, 0, 0)); + + //then + exception + } + + @SuppressWarnings("checkstyle:MagicNumber") + @Test + void testVersionsShouldFilterTheListReturnedWhenSecretIsFoundAndHasMoreVersionsThanNeeded() { + //given + final int index = 30; + final LinkedList fullList = IntStream.range(0, 42) + .mapToObj(i -> UUID.randomUUID().toString().replaceAll("-", "")) + .sorted() + .collect(Collectors.toCollection(LinkedList::new)); + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + final String expectedNextUri = baseUri.asUri(HTTPS_LOCALHOST_8443, "versions?api-version=7.5&$skiptoken=31&maxresults=1") + .toString(); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getVersions(eq(baseUri))).thenReturn(fullList); + when(entities.getReadOnlyEntity(any())).thenAnswer(invocation -> { + final VersionedSecretEntityId secretEntityId = invocation.getArgument(0, VersionedSecretEntityId.class); + return createEntity(secretEntityId, createRequest(null, null)); + }); + when(secretEntityToV72SecretVersionItemModelConverter.convert(any(), any())).thenAnswer(invocation -> { + final KeyVaultSecretEntity entity = invocation.getArgument(0, KeyVaultSecretEntity.class); + return keyVaultSecretItemModel(entity.getId().asUri(HTTPS_LOCALHOST_8443), Map.of()); + }); + final URI expected = new VersionedSecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, + fullList.get(index)).asUri(HTTPS_LOCALHOST_8443); + + //when + final ResponseEntity> actual = + underTest.versions(SECRET_NAME_1, HTTPS_LOCALHOST_8443, 1, index); + + //then + Assertions.assertNotNull(actual); + final KeyVaultItemListModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertEquals(expected.toString(), actualBody.getValue().get(0).getId()); + Assertions.assertNotNull(actualBody.getNextLink()); + Assertions.assertEquals(expectedNextUri, actualBody.getNextLink()); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @Test + void testVersionsShouldNotContainNextUriWhenLastPageIsReturnedFully() { + //given + final LinkedList fullList = IntStream.range(0, 25) + .mapToObj(i -> UUID.randomUUID().toString().replaceAll("-", "")) + .sorted() + .collect(Collectors.toCollection(LinkedList::new)); + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(entities.getVersions(eq(baseUri))).thenReturn(fullList); + when(entities.getReadOnlyEntity(any())).thenAnswer(invocation -> { + final VersionedSecretEntityId secretEntityId = invocation.getArgument(0, VersionedSecretEntityId.class); + return createEntity(secretEntityId, createRequest(null, null)); + }); + when(secretEntityToV72SecretVersionItemModelConverter.convert(any(), any())).thenAnswer(invocation -> { + final KeyVaultSecretEntity entity = invocation.getArgument(0, KeyVaultSecretEntity.class); + return keyVaultSecretItemModel(entity.getId().asUri(HTTPS_LOCALHOST_8443), Map.of()); + }); + final List expected = fullList.stream() + .map(e -> new VersionedSecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, e).asUri(HTTPS_LOCALHOST_8443)) + .collect(Collectors.toList()); + + //when + final ResponseEntity> actual = + underTest.versions(SECRET_NAME_1, HTTPS_LOCALHOST_8443, 25, 0); + + //then + Assertions.assertNotNull(actual); + final KeyVaultItemListModel actualBody = actual.getBody(); + Assertions.assertNotNull(actualBody); + final List actualList = actualBody.getValue().stream() + .map(KeyVaultSecretItemModel::getId) + .map(URI::create) + .collect(Collectors.toList()); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertIterableEquals(expected, actualList); + Assertions.assertNull(actualBody.getNextLink()); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testDeleteSecretShouldReturnEntryWhenSecretIsFound( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(secretVaultFake.getDeletedEntities()) + .thenReturn(deletedEntities); + doNothing().when(secretVaultFake).delete(eq(baseUri)); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(deletedEntities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(deletedEntities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(DELETED_RESPONSE); + + //when + final ResponseEntity actual = underTest.delete(SECRET_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(DELETED_RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + final InOrder inOrder = inOrder(secretVaultFake); + inOrder.verify(secretVaultFake).delete(eq(baseUri)); + inOrder.verify(secretVaultFake, atLeastOnce()).getDeletedEntities(); + verify(secretVaultFake, never()).getEntities(); + verify(deletedEntities).getLatestVersionOfEntity(eq(baseUri)); + verify(deletedEntities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testRecoverDeletedSecretShouldReturnEntryWhenSecretIsFound( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(secretVaultFake.getDeletedEntities()) + .thenReturn(deletedEntities); + doNothing().when(secretVaultFake).delete(eq(baseUri)); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.recoverDeletedSecret(SECRET_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + final InOrder inOrder = inOrder(secretVaultFake); + inOrder.verify(secretVaultFake).recover(eq(baseUri)); + inOrder.verify(secretVaultFake, atLeastOnce()).getEntities(); + verify(secretVaultFake, never()).getDeletedEntities(); + verify(entities).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetDeletedSecretShouldReturnEntryWhenSecretIsFound( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(DELETED_RESPONSE); + + //when + final ResponseEntity actual = underTest.getDeletedSecret(SECRET_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(DELETED_RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, never()).getEntities(); + verify(secretVaultFake, atLeastOnce()).getDeletedEntities(); + verify(entities).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetDeletedSecretShouldThrowExceptionWhenSecretIsDisabled( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setEnabled(false); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(DELETED_RESPONSE); + + //when + Assertions.assertThrows(NotFoundException.class, () -> underTest.getDeletedSecret(SECRET_NAME_1, HTTPS_LOCALHOST_8443)); + + //then + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, never()).getEntities(); + verify(secretVaultFake, atLeastOnce()).getDeletedEntities(); + verify(entities).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter, never()).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testPurgeDeletedShouldSucceedWhenDeletedSecretIsPurgeable( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + final RecoveryLevel nonNullRecoveryLevel = Optional.ofNullable(recoveryLevel).orElse(RecoveryLevel.PURGEABLE); + if (!nonNullRecoveryLevel.isPurgeable()) { + doThrow(IllegalStateException.class).when(secretVaultFake).purge(eq(UNVERSIONED_SECRET_ENTITY_ID_1)); + } + + //when + if (nonNullRecoveryLevel.isPurgeable()) { + final ResponseEntity response = underTest.purgeDeleted(SECRET_NAME_1, HTTPS_LOCALHOST_8443); + Assertions.assertNotNull(response); + Assertions.assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + } else { + Assertions.assertThrows(IllegalStateException.class, () -> underTest.purgeDeleted(SECRET_NAME_1, HTTPS_LOCALHOST_8443)); + } + + //then + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, never()).getEntities(); + verify(secretVaultFake, never()).getDeletedEntities(); + verify(secretVaultFake, atLeastOnce()).purge(eq(UNVERSIONED_SECRET_ENTITY_ID_1)); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + verify(entities, never()).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter, never()).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetShouldReturnEntryWhenSecretIsFound( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getLatestVersionOfEntity((eq(baseUri)))) + .thenReturn(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.get(SECRET_NAME_1, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, atLeastOnce()).getEntities(); + verify(entities).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetSecretsShouldReturnEntryWhenSecretIsFound( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity)); + final KeyVaultSecretItemModel secretItemModel = keyVaultSecretItemModel(baseUri.asUri(HTTPS_LOCALHOST_8443), Map.of()); + when(secretEntityToV72SecretItemModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(secretItemModel); + + //when + final ResponseEntity> actual = + underTest.listSecrets(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(secretItemModel, actual.getBody().getValue().get(0)); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, atLeastOnce()).getEntities(); + verify(secretVaultFake, never()).getDeletedEntities(); + verify(entities).listLatestEntities(); + verify(secretEntityToV72SecretItemModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetSecretsShouldReturnNextLinkWhenNotOnLastPage( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity, entity, entity)); + final KeyVaultSecretItemModel secretItemModel = keyVaultSecretItemModel(baseUri.asUri(HTTPS_LOCALHOST_8443), Map.of()); + when(secretEntityToV72SecretItemModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(secretItemModel); + + //when + final ResponseEntity> actual = + underTest.listSecrets(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(secretItemModel, actual.getBody().getValue().get(0)); + final String expectedNextLink = HTTPS_LOCALHOST_8443 + "/secrets?api-version=7.5&$skiptoken=1&maxresults=1"; + Assertions.assertEquals(expectedNextLink, actual.getBody().getNextLink()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, atLeastOnce()).getEntities(); + verify(secretVaultFake, never()).getDeletedEntities(); + verify(entities).listLatestEntities(); + verify(secretEntityToV72SecretItemModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetDeletedSecretsShouldReturnEntryWhenSecretIsFound( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity)); + final DeletedKeyVaultSecretItemModel secretItemModel = deletedKeyVaultSecretItemModel(baseUri, Map.of()); + when(secretEntityToV72SecretItemModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(secretItemModel); + + //when + final ResponseEntity> actual = + underTest.listDeletedSecrets(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(secretItemModel, actual.getBody().getValue().get(0)); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, atLeastOnce()).getDeletedEntities(); + verify(secretVaultFake, never()).getEntities(); + verify(entities).listLatestEntities(); + verify(secretEntityToV72SecretItemModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetDeletedSecretsShouldReturnNextLinkWhenNotOnLastPage( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + when(secretVaultFake.getDeletedEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setDeletedDate(TIME_10_MINUTES_AGO); + entity.setScheduledPurgeDate(TIME_IN_10_MINUTES); + when(entities.listLatestEntities()) + .thenReturn(List.of(entity, entity, entity)); + final DeletedKeyVaultSecretItemModel secretItemModel = deletedKeyVaultSecretItemModel(baseUri, Map.of()); + when(secretEntityToV72SecretItemModelConverter.convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(secretItemModel); + + //when + final ResponseEntity> actual = + underTest.listDeletedSecrets(HTTPS_LOCALHOST_8443, 1, 0); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertNotNull(actual.getBody()); + Assertions.assertNotNull(actual.getBody().getValue()); + Assertions.assertEquals(1, actual.getBody().getValue().size()); + Assertions.assertSame(secretItemModel, actual.getBody().getValue().get(0)); + final String expectedNextLink = HTTPS_LOCALHOST_8443 + "/deletedsecrets?api-version=7.5&$skiptoken=1&maxresults=1"; + Assertions.assertEquals(expectedNextLink, actual.getBody().getNextLink()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake, atLeastOnce()).getDeletedEntities(); + verify(secretVaultFake, never()).getEntities(); + verify(entities).listLatestEntities(); + verify(secretEntityToV72SecretItemModelConverter).convertDeleted(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetWithVersionShouldReturnEntryWhenSecretAndVersionIsFound( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest.getWithVersion(SECRET_NAME_1, SECRET_VERSION_3, HTTPS_LOCALHOST_8443); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake).getEntities(); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("secretAttributeProvider") + void testGetWithVersionShouldThrowExceptionWhenSecretAndVersionIsFoundButSecretVersionIsDisabled( + final RecoveryLevel recoveryLevel, final Integer recoverableDays, + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + final CreateSecretRequest request = createRequest(expiry, notBefore); + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, request); + entity.setEnabled(false); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(recoveryLevel); + when(vaultFake.getRecoverableDays()) + .thenReturn(recoverableDays); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + Assertions.assertThrows(NotFoundException.class, () -> + underTest.getWithVersion(SECRET_NAME_1, SECRET_VERSION_3, HTTPS_LOCALHOST_8443)); + + //then + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake).getEntities(); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + verify(entities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter, never()).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @ParameterizedTest + @MethodSource("updateAttributeProvider") + void testUpdateVersionShouldReturnEntryWhenSecretAndVersionIsFound( + final OffsetDateTime expiry, final OffsetDateTime notBefore, + final Boolean enabled, final Map tags) { + //given + final SecretEntityId baseUri = new SecretEntityId(HTTPS_LOCALHOST_8443, SECRET_NAME_1, null); + final CreateSecretRequest createSecretRequest = createRequest(null, null); + final UpdateSecretRequest updateSecretRequest = new UpdateSecretRequest(); + if (tags != null) { + updateSecretRequest.setTags(tags); + } + if (enabled != null || expiry != null || notBefore != null) { + final BasePropertiesUpdateModel properties = new BasePropertiesUpdateModel(); + properties.setEnabled(enabled); + properties.setExpiresOn(expiry); + properties.setNotBefore(notBefore); + updateSecretRequest.setProperties(properties); + } + final ReadOnlyKeyVaultSecretEntity entity = createEntity(VERSIONED_SECRET_ENTITY_ID_1_VERSION_1, createSecretRequest); + when(secretVaultFake.getEntities()) + .thenReturn(entities); + when(vaultFake.getRecoveryLevel()) + .thenReturn(RecoveryLevel.PURGEABLE); + when(entities.getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3))) + .thenReturn(entity); + when(secretEntityToV72ModelConverter.convert(same(entity), eq(HTTPS_LOCALHOST_8443))) + .thenReturn(RESPONSE); + + //when + final ResponseEntity actual = underTest + .updateVersion(SECRET_NAME_1, SECRET_VERSION_3, HTTPS_LOCALHOST_8443, updateSecretRequest); + + //then + Assertions.assertNotNull(actual); + Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode()); + Assertions.assertSame(RESPONSE, actual.getBody()); + verify(vaultService).findByUri(eq(HTTPS_LOCALHOST_8443)); + verify(vaultFake).secretVaultFake(); + verify(vaultFake).getRecoveryLevel(); + verify(vaultFake).getRecoverableDays(); + verify(secretVaultFake).getEntities(); + verify(entities, never()).getLatestVersionOfEntity(eq(baseUri)); + final InOrder inOrder = inOrder(secretVaultFake, entities); + if (enabled != null) { + inOrder.verify(secretVaultFake) + .setEnabled(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3), eq(enabled)); + } else { + inOrder.verify(secretVaultFake, never()) + .setEnabled(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3), anyBoolean()); + } + if (expiry != null || notBefore != null) { + inOrder.verify(secretVaultFake) + .setExpiry(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3), eq(notBefore), eq(expiry)); + } else { + inOrder.verify(secretVaultFake, never()) + .setExpiry(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3), any(), any()); + } + if (tags != null) { + inOrder.verify(secretVaultFake) + .clearTags(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + inOrder.verify(secretVaultFake) + .addTags(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3), same(updateSecretRequest.getTags())); + } else { + inOrder.verify(secretVaultFake, never()) + .clearTags(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + inOrder.verify(secretVaultFake, never()) + .addTags(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3), anyMap()); + } + inOrder.verify(entities).getReadOnlyEntity(eq(VERSIONED_SECRET_ENTITY_ID_1_VERSION_3)); + verify(secretEntityToV72ModelConverter).convert(same(entity), eq(HTTPS_LOCALHOST_8443)); + } + + @NonNull + private CreateSecretRequest createRequest( + final OffsetDateTime expiry, final OffsetDateTime notBefore) { + final CreateSecretRequest secretRequest = new CreateSecretRequest(); + secretRequest.setValue(LOWKEY_VAULT); + final SecretPropertiesModel properties = new SecretPropertiesModel(); + properties.setExpiresOn(expiry); + properties.setNotBefore(notBefore); + properties.setEnabled(true); + secretRequest.setProperties(properties); + secretRequest.setTags(TAGS_TWO_KEYS); + return secretRequest; + } + + @NonNull + private KeyVaultSecretEntity createEntity(final VersionedSecretEntityId secretEntityId, final CreateSecretRequest createSecretRequest) { + return new KeyVaultSecretEntity(secretEntityId, vaultFake, createSecretRequest.getValue(), createSecretRequest.getContentType()); + } + + private KeyVaultSecretItemModel keyVaultSecretItemModel(final URI asUriNoVersion, final Map tags) { + final KeyVaultSecretItemModel model = new KeyVaultSecretItemModel(); + model.setAttributes(new SecretPropertiesModel()); + model.setId(asUriNoVersion.toString()); + model.setTags(tags); + return model; + } + + private DeletedKeyVaultSecretItemModel deletedKeyVaultSecretItemModel(final SecretEntityId id, final Map tags) { + final DeletedKeyVaultSecretItemModel model = new DeletedKeyVaultSecretItemModel(); + model.setAttributes(new SecretPropertiesModel()); + model.setId(id.asUriNoVersion(id.vault()).toString()); + model.setTags(tags); + model.setDeletedDate(TIME_10_MINUTES_AGO); + model.setScheduledPurgeDate(TIME_IN_10_MINUTES); + model.setRecoveryId(id.asRecoveryUri(id.vault()).toString()); + return model; + } +} diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/certificate/CertificateEntityToV73CertificateItemModelConverterTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/certificate/CertificateEntityToV73CertificateItemModelConverterTest.java index 989f5b11..f0d10009 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/certificate/CertificateEntityToV73CertificateItemModelConverterTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/certificate/CertificateEntityToV73CertificateItemModelConverterTest.java @@ -57,7 +57,7 @@ void testConvertShouldMapThumbprintWhenCalledWithValidData() { //given final CertificateEntityToV73PropertiesModelConverter properties = mock(CertificateEntityToV73PropertiesModelConverter.class); final CertificateConverterRegistry registry = mock(CertificateConverterRegistry.class); - when(registry.propertiesConverter(eq(ApiConstants.V_7_4))).thenReturn(properties); + when(registry.propertiesConverter(eq(ApiConstants.V_7_5))).thenReturn(properties); final CertificateEntityToV73CertificateItemModelConverter underTest = new CertificateEntityToV73CertificateItemModelConverter(registry); final byte[] expectedThumbprint = THUMBPRINT; diff --git a/lowkey-vault-client/README.md b/lowkey-vault-client/README.md index 595419e6..23d65821 100644 --- a/lowkey-vault-client/README.md +++ b/lowkey-vault-client/README.md @@ -18,7 +18,7 @@ Visit the [Readme](../README.md) in the repo root for more information about the use [Maven Central](https://search.maven.org/search?q=com.github.nagyesta.lowkey-vault). 2. Create a [ApacheHttpClientProvider](src/main/java/com/github/nagyesta/lowkeyvault/http/ApacheHttpClientProvider.java) instance using the constructor supporting host overrides: `ApacheHttpClientProvider(final String vaultUrl, final Function authorityOverrideFunction)`. - [Example](../lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/SecretsStepDefs.javaL32-35) + [Example](../lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/SecretsStepDefs.java#L32-35) 3. Use your `ApacheHttpClientProvider` to obtain your key/secret/crypto clients. 4. Done. diff --git a/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/ApacheHttpResponse.java b/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/ApacheHttpResponse.java index 09a6a259..a2afc30c 100644 --- a/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/ApacheHttpResponse.java +++ b/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/ApacheHttpResponse.java @@ -1,5 +1,6 @@ package com.github.nagyesta.lowkeyvault.http; +import com.azure.core.http.HttpHeaderName; import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; @@ -29,7 +30,7 @@ final class ApacheHttpResponse extends HttpResponse { this.statusCode = apacheResponse.getStatusLine().getStatusCode(); this.headers = new HttpHeaders(); Arrays.stream(apacheResponse.getAllHeaders()) - .forEach(header -> headers.set(header.getName(), header.getValue())); + .forEach(header -> headers.set(HttpHeaderName.fromString(header.getName()), header.getValue())); final HttpEntity responseEntity = Optional.ofNullable(apacheResponse.getEntity()).orElse(new StringEntity("")); this.entity = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); } @@ -41,7 +42,7 @@ public int getStatusCode() { @Override public String getHeaderValue(final String s) { - return getHeaders().getValue(s); + return getHeaders().getValue(HttpHeaderName.fromString(s)); } @Override diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/ParameterTypeDefs.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/ParameterTypeDefs.java index 05c89f81..b3a31cf9 100644 --- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/ParameterTypeDefs.java +++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/ParameterTypeDefs.java @@ -43,7 +43,7 @@ public List keyOperations(final String operations) { .orElse(Collections.emptyList()); } - @ParameterType("(7.2|7.3|7.4)") + @ParameterType("(7.2|7.3|7.4|7.5)") public String api(final String api) { return api; } diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/BackupAndRestoreCertificates.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/BackupAndRestoreCertificates.feature index 0a8de9f5..88293c48 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/BackupAndRestoreCertificates.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/BackupAndRestoreCertificates.feature @@ -27,6 +27,8 @@ Feature: Certificate backup and restore | 7.3 | 73-importRsaCert4096Pkcs | rsa-example-com.p12 | password | PKCS12 | CN=example.com | 2024-01-27 | | 7.4 | 74-importRsaCert2048Pem | rsa-localhost.pem | - | PEM | CN=localhost | 2052-08-28 | | 7.4 | 74-importRsaCert2048Pkcs | rsa-localhost.p12 | changeit | PKCS12 | CN=localhost | 2052-08-28 | + | 7.5 | 75-importRsaCert2048Pem | rsa-localhost.pem | - | PEM | CN=localhost | 2052-08-28 | + | 7.5 | 75-importRsaCert2048Pkcs | rsa-localhost.p12 | changeit | PKCS12 | CN=localhost | 2052-08-28 | @Certificate @CertificateImport @CertificateBackup @CertificateRestore @EC Scenario Outline: EC_CERT_BACKUP_01 Single versions of EC certificates can be backed up and restored with the certificate client @@ -53,3 +55,5 @@ Feature: Certificate backup and restore | 7.3 | 73-importEc521Pkcs | ec521-ec-localhost.p12 | changeit | PKCS12 | CN=ec.localhost | 2023-09-10 | | 7.4 | 74-importEc521Pem | ec521-ec-localhost.pem | - | PEM | CN=ec.localhost | 2023-09-10 | | 7.4 | 74-importEc521Pkcs | ec521-ec-localhost.p12 | changeit | PKCS12 | CN=ec.localhost | 2023-09-10 | + | 7.5 | 75-importEc521Pem | ec521-ec-localhost.pem | - | PEM | CN=ec.localhost | 2023-09-10 | + | 7.5 | 75-importEc521Pkcs | ec521-ec-localhost.p12 | changeit | PKCS12 | CN=ec.localhost | 2023-09-10 | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/CreateCertificates.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/CreateCertificates.feature index cfc159ee..68efc377 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/CreateCertificates.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/CreateCertificates.feature @@ -27,6 +27,8 @@ Feature: Certificate creation | 7.3 | with | 73-createRsaCert4096PkcsHsm | 4096 | enabled | PKCS12 | CN=example.com | | 7.4 | without | 74-createRsaCert2048Pem | 2048 | enabled | PEM | CN=localhost | | 7.4 | with | 74-createRsaCert2048PemHsm | 2048 | enabled | PEM | CN=localhost | + | 7.5 | without | 75-createRsaCert2048Pem | 2048 | enabled | PEM | CN=localhost | + | 7.5 | with | 75-createRsaCert2048PemHsm | 2048 | enabled | PEM | CN=localhost | @Certificate @CertificateCreate @EC Scenario Outline: EC_CERT_CREATE_01 Single versions of EC certificates can be created with the certificate client @@ -62,6 +64,8 @@ Feature: Certificate creation | 7.3 | with | 73-createEcCertP521PkcsHsm | P-521 | enabled | PKCS12 | CN=example.com | | 7.4 | without | 74-createEcCertP256Pem | P-256 | enabled | PEM | CN=localhost | | 7.4 | with | 74-createEcCertP256PemHsm | P-256 | enabled | PEM | CN=localhost | + | 7.5 | without | 75-createEcCertP256Pem | P-256 | enabled | PEM | CN=localhost | + | 7.5 | with | 75-createEcCertP256PemHsm | P-256 | enabled | PEM | CN=localhost | @Certificate @CertificateCreate @RSA Scenario Outline: RSA_CERT_CREATE_02 Single versions of RSA certificates can be created using lifetime actions @@ -84,6 +88,7 @@ Feature: Certificate creation | 7.3 | 73-createRsaCertPkcsAction | 75 | percent lifetime | AutoRenew | PKCS12 | | 7.3 | 73-createRsaCertPemRenewAction | 75 | percent lifetime | AutoRenew | PEM | | 7.4 | 74-createRsaCertPemAction | 20 | days before expiry | EmailContacts | PEM | + | 7.5 | 75-createRsaCertPemAction | 20 | days before expiry | EmailContacts | PEM | @Certificate @CertificateCreate @EC Scenario Outline: EC_CERT_CREATE_02 Single versions of EC certificates can be created using lifetime actions @@ -105,6 +110,7 @@ Feature: Certificate creation | 7.3 | 73-createEcCertPemAction | 10 | days before expiry | EmailContacts | PEM | | 7.3 | 73-createEcCertPkcsAction | 80 | percent lifetime | AutoRenew | PKCS12 | | 7.4 | 74-createEcCertPemAction | 10 | days before expiry | EmailContacts | PEM | + | 7.5 | 75-createEcCertPemAction | 10 | days before expiry | EmailContacts | PEM | @Certificate @CertificateCreate @RSA Scenario Outline: RSA_CERT_CREATE_03 Two versions of the same RSA certificates can be created using the same name @@ -126,6 +132,7 @@ Feature: Certificate creation | 7.3 | 73-createRsaCertPemDouble | PEM | | 7.3 | 73-createRsaCertPkcsDouble | PKCS12 | | 7.4 | 74-createRsaCertPemDouble | PEM | + | 7.5 | 75-createRsaCertPemDouble | PEM | @Certificate @CertificateCreate @EC Scenario Outline: EC_CERT_CREATE_03 Two versions of the same EC certificates can be created using the same name @@ -147,3 +154,4 @@ Feature: Certificate creation | 7.3 | 73-createEcCertPemDouble | PEM | | 7.3 | 73-createEcCertPkcsDouble | PKCS12 | | 7.4 | 74-createEcCertPemDouble | PEM | + | 7.5 | 75-createEcCertPemDouble | PEM | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/DeleteCertificates.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/DeleteCertificates.feature index 2ff0a43e..10accad8 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/DeleteCertificates.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/DeleteCertificates.feature @@ -14,6 +14,7 @@ Feature: Certificate delete/purge/recover | api | index | fileName | | 7.3 | 1 | rsa-localhost.pem | | 7.4 | 2 | rsa-localhost.pem | + | 7.5 | 3 | rsa-localhost.pem | @Certificate @CertificateImport @CertificateDelete @EC @CreateVault Scenario Outline: EC_CERT_DELETE_01 Single versions of multiple EC certificates imported and deleted then get as deleted @@ -29,6 +30,7 @@ Feature: Certificate delete/purge/recover | api | index | fileName | | 7.3 | 1 | ec521-ec-localhost.pem | | 7.4 | 2 | ec521-ec-localhost.pem | + | 7.5 | 3 | ec521-ec-localhost.pem | @Certificate @CertificateImport @CertificateDelete @RSA @CreateVault Scenario Outline: RSA_CERT_PURGE_01 Single versions of multiple RSA certificates imported and deleted then purged @@ -47,6 +49,7 @@ Feature: Certificate delete/purge/recover | api | index | fileName | | 7.3 | 1 | rsa-localhost.pem | | 7.4 | 2 | rsa-localhost.pem | + | 7.5 | 3 | rsa-localhost.pem | @Certificate @CertificateImport @CertificateDelete @EC @CreateVault Scenario Outline: EC_CERT_PURGE_01 Single versions of multiple EC certificates imported and deleted then purged @@ -65,6 +68,7 @@ Feature: Certificate delete/purge/recover | api | index | fileName | | 7.3 | 1 | ec521-ec-localhost.pem | | 7.4 | 2 | ec521-ec-localhost.pem | + | 7.5 | 3 | ec521-ec-localhost.pem | @Certificate @CertificateImport @CertificateDelete @RSA @CreateVault Scenario Outline: RSA_CERT_RECOVER_01 Single versions of multiple RSA certificates imported and deleted then recovered @@ -83,6 +87,7 @@ Feature: Certificate delete/purge/recover | api | index | fileName | | 7.3 | 1 | rsa-localhost.pem | | 7.4 | 2 | rsa-localhost.pem | + | 7.5 | 3 | rsa-localhost.pem | @Certificate @CertificateImport @CertificateDelete @EC @CreateVault Scenario Outline: EC_CERT_RECOVER_01 Single versions of multiple EC certificates imported and deleted then recovered @@ -101,3 +106,4 @@ Feature: Certificate delete/purge/recover | api | index | fileName | | 7.3 | 1 | ec521-ec-localhost.pem | | 7.4 | 2 | ec521-ec-localhost.pem | + | 7.5 | 3 | ec521-ec-localhost.pem | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/GetCertificatePolicies.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/GetCertificatePolicies.feature index cb1dcf49..36fe70d6 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/GetCertificatePolicies.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/GetCertificatePolicies.feature @@ -17,6 +17,7 @@ Feature: Get certificate policy | 7.3 | 73-policyRsaCert2048Pem | rsa-localhost.pem | CN=localhost | 360 | | 7.3 | 73-policyRsaCert4096Pem | rsa-example-com.pem | CN=example.com | 12 | | 7.4 | 74-policyRsaCert2048Pem | rsa-localhost.pem | CN=localhost | 360 | + | 7.5 | 75-policyRsaCert2048Pem | rsa-localhost.pem | CN=localhost | 360 | @Certificate @CertificateImport @CertificateGetPolicy @EC Scenario Outline: EC_CERT_GET_POLICY_01 Policy data of EC certificates can be accessed with the certificate client @@ -32,3 +33,4 @@ Feature: Get certificate policy | api | certName | fileName | subject | validity | | 7.3 | 73-policyEc521Pem | ec521-ec-localhost.pem | CN=ec.localhost | 12 | | 7.4 | 74-policyEc521Pem | ec521-ec-localhost.pem | CN=ec.localhost | 12 | + | 7.5 | 75-policyEc521Pem | ec521-ec-localhost.pem | CN=ec.localhost | 12 | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ImportCertificates.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ImportCertificates.feature index 719fccdf..c5f29036 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ImportCertificates.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ImportCertificates.feature @@ -20,6 +20,7 @@ Feature: Certificate import | 7.3 | 73-importRsaCert4096Pem | rsa-example-com.pem | - | PEM | CN=example.com | 2024-01-27 | | 7.3 | 73-importRsaCert4096Pkcs | rsa-example-com.p12 | password | PKCS12 | CN=example.com | 2024-01-27 | | 7.4 | 74-importRsaCert2048Pem | rsa-localhost.pem | - | PEM | CN=localhost | 2052-08-28 | + | 7.5 | 75-importRsaCert2048Pem | rsa-localhost.pem | - | PEM | CN=localhost | 2052-08-28 | @Certificate @CertificateImport @EC Scenario Outline: EC_CERT_IMPORT_01 Single versions of EC certificates can be imported with the certificate client @@ -39,6 +40,7 @@ Feature: Certificate import | 7.3 | 73-importEc521Pem | ec521-ec-localhost.pem | - | PEM | CN=ec.localhost | 2023-09-10 | | 7.3 | 73-importEc521Pkcs | ec521-ec-localhost.p12 | changeit | PKCS12 | CN=ec.localhost | 2023-09-10 | | 7.4 | 74-importEc521Pem | ec521-ec-localhost.pem | - | PEM | CN=ec.localhost | 2023-09-10 | + | 7.5 | 75-importEc521Pem | ec521-ec-localhost.pem | - | PEM | CN=ec.localhost | 2023-09-10 | @Certificate @CertificateImport @CertificateUpdate @RSA Scenario Outline: RSA_CERT_UPDATE_01 Imported RSA certificates can be updated with the certificate client @@ -63,6 +65,7 @@ Feature: Certificate import | 7.3 | 73-updateRsaCert4096Pem | rsa-example-com.pem | - | PEM | PEM | CN=example.com | CN=updated.local | | 7.3 | 73-updateRsaCert4096Pkcs | rsa-example-com.p12 | password | PKCS12 | PEM | CN=example.com | CN=example.com | | 7.4 | 74-updateRsaCert2048Pkcs | rsa-localhost.p12 | changeit | PKCS12 | PKCS12 | CN=localhost | CN=updated.local | + | 7.5 | 75-updateRsaCert2048Pkcs | rsa-localhost.p12 | changeit | PKCS12 | PKCS12 | CN=localhost | CN=updated.local | @Certificate @CertificateImport @CertificateUpdate @EC Scenario Outline: EC_CERT_UPDATE_01 Imported EC certificates can be updated with the certificate client @@ -85,3 +88,4 @@ Feature: Certificate import | 7.3 | 73-updateEc521Pem | ec521-ec-localhost.pem | - | PEM | PKCS12 | CN=ec.localhost | CN=updated.local | | 7.3 | 73-updateEc521Pkcs | ec521-ec-localhost.p12 | changeit | PKCS12 | PEM | CN=ec.localhost | CN=ec.localhost | | 7.4 | 74-updateEc521Pkcs | ec521-ec-localhost.p12 | changeit | PKCS12 | PEM | CN=ec.localhost | CN=ec.localhost | + | 7.5 | 75-updateEc521Pkcs | ec521-ec-localhost.p12 | changeit | PKCS12 | PEM | CN=ec.localhost | CN=ec.localhost | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListCertificates.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListCertificates.feature index 3cf3a158..0c515779 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListCertificates.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListCertificates.feature @@ -20,6 +20,7 @@ Feature: Certificate list | 7.3 | 3 | rsa-localhost.pem | 2 | | 7.3 | 4 | rsa-localhost.pem | 10 | | 7.4 | 5 | rsa-localhost.pem | 10 | + | 7.5 | 6 | rsa-localhost.pem | 10 | @Certificate @CertificateImport @CertificateList @EC @CreateVault Scenario Outline: EC_CERT_LIST_01 Single versions of multiple EC certificates imported then listed with the certificate client @@ -41,6 +42,7 @@ Feature: Certificate list | 7.3 | 3 | ec521-ec-localhost.pem | 2 | | 7.3 | 4 | ec521-ec-localhost.pem | 10 | | 7.4 | 5 | ec521-ec-localhost.pem | 2 | + | 7.5 | 6 | ec521-ec-localhost.pem | 2 | @Certificate @CertificateImport @CertificateList @RSA @CreateVault Scenario Outline: RSA_CERT_LIST_02 A single version of an RSA certificate is imported then versions listed with the certificate client @@ -55,6 +57,7 @@ Feature: Certificate list | api | index | fileName | | 7.3 | 1 | rsa-localhost.pem | | 7.4 | 2 | rsa-localhost.pem | + | 7.5 | 3 | rsa-localhost.pem | @Certificate @CertificateImport @CertificateList @EC @CreateVault Scenario Outline: EC_CERT_LIST_02 A single version of an EC certificate is imported then versions listed with the certificate client @@ -69,3 +72,4 @@ Feature: Certificate list | api | index | fileName | | 7.3 | 1 | ec521-ec-localhost.pem | | 7.4 | 2 | ec521-ec-localhost.pem | + | 7.5 | 3 | ec521-ec-localhost.pem | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListDeletedCertificates.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListDeletedCertificates.feature index 100b8ac7..dc9993d7 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListDeletedCertificates.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListDeletedCertificates.feature @@ -15,6 +15,7 @@ Feature: Certificate list deleted | 7.3 | 1 | rsa-localhost.pem | 1 | | 7.3 | 2 | rsa-localhost.pem | 5 | | 7.4 | 3 | rsa-localhost.pem | 5 | + | 7.5 | 4 | rsa-localhost.pem | 5 | @Certificate @CertificateImport @CertificateListDeleted @EC @CreateVault Scenario Outline: EC_CERT_LIST_DELETED_01 Single versions of multiple EC certificates imported and deleted then listed as deleted @@ -31,3 +32,4 @@ Feature: Certificate list deleted | 7.3 | 1 | ec521-ec-localhost.pem | 1 | | 7.3 | 2 | ec521-ec-localhost.pem | 5 | | 7.4 | 3 | ec521-ec-localhost.pem | 1 | + | 7.5 | 4 | ec521-ec-localhost.pem | 1 | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/RenewCertificates.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/RenewCertificates.feature index 5ab92e4f..09d10fbc 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/RenewCertificates.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/RenewCertificates.feature @@ -24,6 +24,7 @@ Feature: Certificate renewal/recreation | 7.3 | 1 | 73-recreateRsaCert | PEM | CN=localhost | 20 | 100 | | 7.3 | 2 | 73-renewRsaCert | PEM | CN=example.com | 5 | 360 | | 7.4 | 3 | 74-recreateRsaCert | PEM | CN=localhost | 20 | 100 | + | 7.5 | 4 | 75-recreateRsaCert | PEM | CN=localhost | 20 | 100 | @Certificate @CertificateCreate @CertificateTimeShift @EC Scenario Outline: EC_CERT_TIME_SHIFT_01 Single versions of EC certificates can be recreated or renewed with time shift @@ -49,3 +50,4 @@ Feature: Certificate renewal/recreation | 7.3 | 1 | 73-recreateEcCert | PEM | CN=localhost | 20 | 100 | | 7.3 | 2 | 73-renewEcCert | PEM | CN=example.com | 5 | 360 | | 7.4 | 3 | 74-renewEcCert | PEM | CN=example.com | 5 | 360 | + | 7.5 | 4 | 75-renewEcCert | PEM | CN=example.com | 5 | 360 | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/BackupAndRestoreKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/BackupAndRestoreKeys.feature index f2ab3e21..3118a805 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/BackupAndRestoreKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/BackupAndRestoreKeys.feature @@ -27,6 +27,7 @@ Feature: Key backup and restore | 7.3 | backupRsaKey-06 | 4096 | RS384 | | | 7.3 | backupRsaKey-07 | 4096 | RS512 | The quick brown fox jumps over the lazy dog. | | 7.4 | backupRsaKey-08 | 2048 | PS256 | The quick brown fox jumps over the lazy dog. | + | 7.5 | backupRsaKey-09 | 2048 | PS256 | The quick brown fox jumps over the lazy dog. | @Key @KeyImport @KeySign @KeyBackup @KeyRestore @EC Scenario Outline: EC_BACKUP_01 An EC key is imported, backed up, vault is recreated then after restore, the key is verified @@ -57,6 +58,7 @@ Feature: Key backup and restore | 7.3 | backupEc-08 | P-384 | ES384 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.3 | backupEc-09 | P-521 | ES512 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.4 | backupEc-10 | P-256K | ES256K | The quick brown fox jumps over the lazy dog. | + | 7.5 | backupEc-11 | P-256K | ES256K | The quick brown fox jumps over the lazy dog. | @Key @KeyImport @KeyEncrypt @KeyBackup @KeyRestore @OCT @@ -86,6 +88,7 @@ Feature: Key backup and restore | 7.3 | backupOct-06 | 192 | A192CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.3 | backupOct-07 | 256 | A256CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.4 | backupOct-08 | 256 | A256CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | + | 7.5 | backupOct-09 | 256 | A256CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | @Key @KeyImport @KeyEncrypt @KeyBackup @KeyRestore @RSA Scenario Outline: RSA_BACKUP_02 An RSA key is restored from json, backed up, then the backup content is compared to the source @@ -105,6 +108,7 @@ Feature: Key backup and restore | 7.3 | jsonBackupRsa-3072-73 | | 7.3 | jsonBackupRsa-4096-73 | | 7.4 | jsonBackupRsa-2048-74 | + | 7.5 | jsonBackupRsa-2048-75 | @Key @KeyImport @KeyEncrypt @KeyBackup @KeyRestore @EC Scenario Outline: EC_BACKUP_02 An EC key is restored from json, backed up, then the backup content is compared to the source @@ -126,6 +130,7 @@ Feature: Key backup and restore | 7.3 | jsonBackupEc-384-73 | | 7.3 | jsonBackupEc-521-73 | | 7.4 | jsonBackupEc-384-74 | + | 7.5 | jsonBackupEc-384-75 | @Key @KeyImport @KeyEncrypt @KeyBackup @KeyRestore @OCT Scenario Outline: OCT_BACKUP_02 An OCT key is restored from json, backed up, then the backup content is compared to the source @@ -145,3 +150,4 @@ Feature: Key backup and restore | 7.3 | jsonBackupOct-192-73 | | 7.3 | jsonBackupOct-256-73 | | 7.4 | jsonBackupOct-192-74 | + | 7.5 | jsonBackupOct-192-75 | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/CreateKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/CreateKeys.feature index 65e12d85..a99f9b71 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/CreateKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/CreateKeys.feature @@ -42,6 +42,7 @@ Feature: Key creation | 7.3 | without | 73-createRsaKeyDates | 2048 | 257 | enabled | null | 4321 | 1234 | null | | 7.3 | without | 73-createRsaKeyNotEnabled | 2048 | 257 | not enabled | null | null | null | null | | 7.4 | without | 74-createRsaKeyNotEnabled | 2048 | 257 | not enabled | null | null | null | null | + | 7.5 | without | 75-createRsaKeyNotEnabled | 2048 | 257 | not enabled | null | null | null | null | @Key @KeyCreate @EC Scenario Outline: EC_CREATE_01 Single versions of EC keys can be created with the key client @@ -85,6 +86,7 @@ Feature: Key creation | 7.3 | without | 73-createEcKeyDates | P-256 | 32 | enabled | null | 4321 | 1234 | null | | 7.3 | without | 73-createEcKeyNotEnabled | P-256 | 32 | not enabled | null | null | null | null | | 7.4 | without | 74-createEcKeyDates | P-256 | 32 | enabled | null | 4321 | 1234 | null | + | 7.5 | without | 75-createEcKeyDates | P-256 | 32 | enabled | null | 4321 | 1234 | null | @Key @KeyCreate @OCT Scenario Outline: OCT_CREATE_01 Single versions of OCT keys can be created with the key client @@ -125,3 +127,4 @@ Feature: Key creation | 7.3 | with | 73-createOctKeyDates | 128 | enabled | null | 4321 | 1234 | null | | 7.3 | with | 73-createOctKeyNotEnabled | 128 | not enabled | null | null | null | null | | 7.4 | with | 74-createOctKeyDates | 128 | enabled | null | 4321 | 1234 | null | + | 7.5 | with | 75-createOctKeyDates | 128 | enabled | null | 4321 | 1234 | null | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/DeleteKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/DeleteKeys.feature index 3b1451b5..a1fc62de 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/DeleteKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/DeleteKeys.feature @@ -18,6 +18,7 @@ Feature: Key delete and recover | 7.3 | keys-alias-delete | 5 | without | 73-deleteRsaKeyA | 2048 | | 7.3 | keys-alias-delete | 6 | without | 73-delete-rsa-key-nameA | 2048 | | 7.4 | keys-alias-delete | 6 | without | 74-delete-rsa-key-nameA | 2048 | + | 7.5 | keys-alias-delete | 6 | without | 75-delete-rsa-key-nameA | 2048 | @Key @KeyCreate @KeyDelete @EC @KeyAlias Scenario Outline: EC_DELETE_01 Multiple versions of EC keys are created with the key client then deleted @@ -37,6 +38,7 @@ Feature: Key delete and recover | 7.3 | keys-alias-delete | 5 | without | 73-deleteEcKey256A | P-256 | | 7.3 | keys-alias-delete | 6 | without | 73-deleteEcKey256kA | P-256K | | 7.4 | keys-delete | 6 | without | 74-deleteEcKey256k | P-256K | + | 7.5 | keys-delete | 6 | without | 75-deleteEcKey256k | P-256K | @Key @KeyCreate @KeyDelete @OCT @KeyAlias Scenario Outline: OCT_DELETE_01 Multiple versions of OCT keys are created with the key client then deleted @@ -56,6 +58,7 @@ Feature: Key delete and recover | 7.3 | keys-alias-delete | 5 | 73-deleteOctKeyA | 128 | | 7.3 | keys-alias-delete | 6 | 73-deleteOctKey192A | 192 | | 7.4 | keys-alias-delete | 6 | 74-deleteOctKey192A | 192 | + | 7.5 | keys-alias-delete | 6 | 75-deleteOctKey192A | 192 | @Key @KeyCreate @KeyDelete @KeyRecover @RSA @KeyAlias Scenario Outline: RSA_RECOVER_01 Multiple versions of RSA keys are created with the key client then deleted and recovered @@ -75,6 +78,7 @@ Feature: Key delete and recover | 7.3 | keys-alias-delete | 5 | without | 73-recoverRsaKeyA | 2048 | | 7.3 | keys-alias-delete | 6 | without | 73-recover-rsa-key-nameA | 2048 | | 7.4 | keys-delete | 5 | without | 74-recoverRsaKey | 2048 | + | 7.5 | keys-delete | 5 | without | 75-recoverRsaKey | 2048 | @Key @KeyCreate @KeyDelete @KeyRecover @EC @KeyAlias Scenario Outline: EC_RECOVER_01 Multiple versions of EC keys are created with the key client then deleted and recovered @@ -94,6 +98,7 @@ Feature: Key delete and recover | 7.3 | keys-alias-delete | 5 | without | 73-recoverEcKey256A | P-256 | | 7.3 | keys-alias-delete | 6 | without | 73-recoverEcKey256kA | P-256K | | 7.4 | keys-delete | 5 | without | 74-recoverEcKey256 | P-256 | + | 7.5 | keys-delete | 5 | without | 75-recoverEcKey256 | P-256 | @Key @KeyCreate @KeyDelete @KeyRecover @OCT @KeyAlias Scenario Outline: OCT_RECOVER_01 Multiple versions of OCT keys are created with the key client then deleted and recovered @@ -111,6 +116,7 @@ Feature: Key delete and recover | 7.3 | keys-delete | 5 | 73-recoverOctKey | 128 | | 7.3 | keys-delete | 6 | 73-recoverOctKey192 | 192 | | 7.4 | keys-delete | 6 | 74-recoverOctKey192 | 192 | + | 7.5 | keys-delete | 6 | 75-recoverOctKey192 | 192 | @Key @KeyCreate @KeyDelete @KeyPurge @RSA Scenario Outline: RSA_PURGE_01 Multiple versions of RSA keys are created with the key client then deleted and purged @@ -132,6 +138,7 @@ Feature: Key delete and recover | 7.3 | 5 | without | 73-purgeRsaKey | 2048 | | 7.3 | 6 | without | 73-purge-rsa-key-name | 2048 | | 7.4 | 5 | without | 74-purgeRsaKey | 2048 | + | 7.5 | 5 | without | 75-purgeRsaKey | 2048 | @Key @KeyCreate @KeyDelete @KeyPurge @EC Scenario Outline: EC_PURGE_01 Multiple versions of EC keys are created with the key client then deleted and purge @@ -153,6 +160,7 @@ Feature: Key delete and recover | 7.3 | 5 | without | 73-purgeEcKey256 | P-256 | | 7.3 | 6 | without | 73-purgeEcKey256k | P-256K | | 7.4 | 6 | without | 74-purgeEcKey256k | P-256K | + | 7.5 | 6 | without | 75-purgeEcKey256k | P-256K | @Key @KeyCreate @KeyDelete @KeyPurge @OCT Scenario Outline: OCT_PURGE_01 Multiple versions of OCT keys are created with the key client then deleted and purge @@ -174,3 +182,4 @@ Feature: Key delete and recover | 7.3 | 3 | 73-purgeOctKey | 128 | | 7.3 | 4 | 73-purgeOctKey192 | 192 | | 7.4 | 3 | 74-purgeOctKey | 128 | + | 7.5 | 3 | 75-purgeOctKey | 128 | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/EncryptWithKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/EncryptWithKeys.feature index b4daeef3..22029e49 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/EncryptWithKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/EncryptWithKeys.feature @@ -22,6 +22,7 @@ Feature: Key encrypt and decrypt | 7.3 | encryptRsaKey-06 | 2048 | RSA-OAEP-256 | | | 7.3 | encryptRsaKey-07 | 4096 | RSA-OAEP-256 | | | 7.4 | encryptRsaKey-08 | 2048 | RSA-OAEP-256 | | + | 7.5 | encryptRsaKey-09 | 2048 | RSA-OAEP-256 | | @Key @KeyCreate @KeyEncrypt @OCT Scenario Outline: OCT_ENCRYPT_01 An OCT key is created with the key client then used for encrypt and decrypt operations @@ -45,3 +46,4 @@ Feature: Key encrypt and decrypt | 7.3 | encryptOct-06 | 192 | A192CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.3 | encryptOct-07 | 256 | A256CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.4 | encryptOct-08 | 128 | A128CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | + | 7.5 | encryptOct-09 | 128 | A128CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/GetKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/GetKeys.feature index bc38ff97..fe2d15d2 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/GetKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/GetKeys.feature @@ -43,6 +43,7 @@ Feature: Key get | 7.3 | 3 | without | 73-get01RsaKeyOperations | 2048 | 257 | wrapKey,unwrapKey | null | null | null | | 7.3 | 4 | without | 73-get01RsaKeyDates | 2048 | 257 | null | 4321 | 1234 | null | | 7.4 | 4 | without | 74-get01RsaKeyDates | 2048 | 257 | null | 4321 | 1234 | null | + | 7.5 | 4 | without | 75-get01RsaKeyDates | 2048 | 257 | null | 4321 | 1234 | null | @Key @KeyCreate @KeyGet @EC Scenario Outline: EC_GET_01 Multiple versions of EC keys are created with the key client then the latest is fetched @@ -87,6 +88,7 @@ Feature: Key get | 7.3 | 3 | without | 73-get01EcKeyOperations | P-256 | 32 | sign,verify | null | null | null | | 7.3 | 4 | without | 73-get01EcKeyDates | P-256 | 32 | null | 4321 | 1234 | null | | 7.4 | 4 | without | 74-get01EcKeyDates | P-256 | 32 | null | 4321 | 1234 | null | + | 7.5 | 4 | without | 75-get01EcKeyDates | P-256 | 32 | null | 4321 | 1234 | null | @Key @KeyCreate @KeyGet @OCT Scenario Outline: OCT_GET_01 Multiple versions of OCT keys are created with the key client then the latest is fetched @@ -128,6 +130,7 @@ Feature: Key get | 7.3 | 3 | with | 73-get01OctKeyOperations | 128 | wrapKey,unwrapKey | null | null | null | | 7.3 | 4 | with | 73-get01OctKeyDates | 128 | null | 4321 | 1234 | null | | 7.4 | 4 | with | 74-get01OctKeyDates | 128 | null | 4321 | 1234 | null | + | 7.5 | 4 | with | 75-get01OctKeyDates | 128 | null | 4321 | 1234 | null | @Key @KeyCreate @KeyGet @RSA @@ -152,6 +155,7 @@ Feature: Key get | 7.3 | 2 | without | 73-get02RsaKey | 2048 | 257 | | 7.3 | 3 | without | 73-get02-rsa-key-name | 2048 | 257 | | 7.4 | 3 | without | 74-get02-rsa-key-name | 2048 | 257 | + | 7.5 | 3 | without | 75-get02-rsa-key-name | 2048 | 257 | @Key @KeyCreate @KeyGet @EC Scenario Outline: EC_GET_02 Multiple versions of EC keys are created with the key client then the first is fetched by version @@ -175,6 +179,7 @@ Feature: Key get | 7.3 | 5 | without | 73-get02EcKey256 | P-256 | 32 | | 7.3 | 6 | without | 73-get02EcKey256k | P-256K | 32 | | 7.4 | 5 | without | 74-get02EcKey256 | P-256 | 32 | + | 7.5 | 5 | without | 75-get02EcKey256 | P-256 | 32 | @Key @KeyCreate @KeyGet @OCT Scenario Outline: OCT_GET_02 Multiple versions of OCT keys are created with the key client then the first is fetched by version @@ -199,6 +204,7 @@ Feature: Key get | 7.3 | 5 | with | 73-get02OctKey | 128 | | 7.3 | 6 | with | 73-get02OctKey192 | 192 | | 7.4 | 6 | with | 74-get02OctKey192 | 192 | + | 7.5 | 6 | with | 75-get02OctKey192 | 192 | @Key @KeyCreate @KeyGet @RSA @@ -216,6 +222,7 @@ Feature: Key get | 7.2 | 5 | without | 72-get03RsaKeyNotEnabled | 2048 | | 7.3 | 2 | without | 73-get03RsaKeyNotEnabled | 2048 | | 7.4 | 2 | without | 74-get03RsaKeyNotEnabled | 2048 | + | 7.5 | 2 | without | 75-get03RsaKeyNotEnabled | 2048 | @Key @KeyCreate @KeyGet @EC Scenario Outline: EC_GET_03 Multiple versions of disabled EC keys are created with the key client then the latest is fetched @@ -232,6 +239,7 @@ Feature: Key get | 7.2 | 3 | without | 72-get03EcKeyNotEnabled | P-256 | | 7.3 | 4 | without | 73-get03EcKeyNotEnabled | P-256 | | 7.4 | 4 | without | 74-get03EcKeyNotEnabled | P-256 | + | 7.5 | 4 | without | 75-get03EcKeyNotEnabled | P-256 | @Key @KeyCreate @KeyGet @OCT Scenario Outline: OCT_GET_03 Multiple versions of disabled OCT keys are created with the key client then the latest is fetched @@ -248,6 +256,7 @@ Feature: Key get | 7.2 | 2 | 72-get03OctKeyNotEnabled | 128 | | 7.3 | 3 | 73-get03OctKeyNotEnabled | 128 | | 7.4 | 3 | 74-get03OctKeyNotEnabled | 128 | + | 7.5 | 3 | 75-get03OctKeyNotEnabled | 128 | @Key @KeyCreate @KeyGet @KeyUpdate @EC Scenario Outline: EC_UPDATE_01 Multiple versions of EC keys are created with the key client then the latest is updated and fetched @@ -293,3 +302,4 @@ Feature: Key get | 7.3 | 4 | without | 73-update01EcKeyDates | P-256 | 32 | enabled | null | 4321 | 1234 | null | | 7.3 | 3 | without | 73-update01EcKeyNotEnabled | P-256 | 32 | not enabled | null | null | null | null | | 7.4 | 4 | without | 74-update01EcKeyDates | P-256 | 32 | enabled | null | 4321 | 1234 | null | + | 7.5 | 4 | without | 75-update01EcKeyDates | P-256 | 32 | enabled | null | 4321 | 1234 | null | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ImportKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ImportKeys.feature index a4c7d723..e991c01e 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ImportKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ImportKeys.feature @@ -20,6 +20,7 @@ Feature: Key import | 7.3 | importRsaKey-06 | 4096 | RS384 | | | 7.3 | importRsaKey-07 | 4096 | RS512 | The quick brown fox jumps over the lazy dog. | | 7.4 | importRsaKey-08 | 2048 | PS256 | The quick brown fox jumps over the lazy dog. | + | 7.5 | importRsaKey-09 | 2048 | PS256 | The quick brown fox jumps over the lazy dog. | @Key @KeyImport @KeySign @EC Scenario Outline: EC_IMPORT_01 An EC key is imported with the key client then used for sign and verify operations @@ -43,6 +44,7 @@ Feature: Key import | 7.3 | importEc-08 | P-384 | ES384 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.3 | importEc-09 | P-521 | ES512 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.4 | importEc-10 | P-256 | ES256 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | + | 7.5 | importEc-11 | P-256 | ES256 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | @Key @KeyImport @KeyEncrypt @OCT @@ -65,3 +67,4 @@ Feature: Key import | 7.3 | importOct-06 | 192 | A192CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.3 | importOct-07 | 256 | A256CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.4 | importOct-08 | 256 | A256CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | + | 7.5 | importOct-09 | 256 | A256CBC | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListDeletedKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListDeletedKeys.feature index 8c698fbb..aa7b4fbb 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListDeletedKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListDeletedKeys.feature @@ -16,6 +16,7 @@ Feature: Key list deleted | 7.3 | 02 | 1 | listRsaKey | | 7.3 | 03 | 2 | list-rsa-key-name | | 7.4 | 04 | 2 | list-rsa-key-name | + | 7.5 | 05 | 2 | list-rsa-key-name | @Key @KeyCreate @KeyListDeleted @EC @CreateVault Scenario Outline: EC_LIST_DELETED_01 EC keys are created and deleted with the key client then all are listed as deleted keys @@ -37,6 +38,7 @@ Feature: Key list deleted | 7.3 | 06 | 25 | listEcKey | | 7.3 | 07 | 42 | list-ec-key-name | | 7.4 | 08 | 42 | list-ec-key-name | + | 7.5 | 09 | 42 | list-ec-key-name | @Key @KeyCreate @KeyListDeleted @OCT @CreateVault Scenario Outline: OCT_LIST_DELETED_01 OCT keys are created and deleted with the key client then all are listed as deleted keys @@ -58,3 +60,4 @@ Feature: Key list deleted | 7.3 | 06 | 25 | listOctKey | | 7.3 | 07 | 42 | list-oct-key-name | | 7.4 | 08 | 42 | list-oct-key-name | + | 7.5 | 09 | 42 | list-oct-key-name | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListKeys.feature index 233f93f3..35d63ef6 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/ListKeys.feature @@ -16,6 +16,7 @@ Feature: Key list | 7.3 | 02 | 1 | listRsaKey | | 7.3 | 03 | 2 | list-rsa-key-name | | 7.4 | 04 | 2 | list-rsa-key-name | + | 7.5 | 05 | 2 | list-rsa-key-name | @Key @KeyCreate @KeyList @EC @CreateVault Scenario Outline: EC_LIST_01 EC keys are created with the key client then all are listed @@ -37,6 +38,7 @@ Feature: Key list | 7.3 | 06 | 25 | listEcKey | | 7.3 | 07 | 42 | list-ec-key-name | | 7.4 | 08 | 2 | list-ec-key-name | + | 7.5 | 09 | 2 | list-ec-key-name | @Key @KeyCreate @KeyList @OCT @CreateVault Scenario Outline: OCT_LIST_01 OCT keys are created with the key client then all are listed @@ -58,3 +60,4 @@ Feature: Key list | 7.3 | 06 | 25 | listOctKey | | 7.3 | 07 | 42 | list-oct-key-name | | 7.4 | 08 | 2 | list-oct-key-name | + | 7.5 | 09 | 2 | list-oct-key-name | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RandomData.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RandomData.feature index 198a1505..bca01b8a 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RandomData.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RandomData.feature @@ -24,3 +24,5 @@ Feature: Random data | 7.3 | 24000 | | 7.4 | 1 | | 7.4 | 42 | + | 7.5 | 1 | + | 7.5 | 42 | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RotateKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RotateKeys.feature index 7dee6867..8eeb0477 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RotateKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RotateKeys.feature @@ -30,6 +30,7 @@ Feature: Key rotation | 7.3 | without | 73-rotateRsaKeyNotEnabled | 2048 | 257 | not enabled | null | 4321 | 1234 | null | | 7.3 | without | 73-rotateRsaKeyMap | 2048 | 257 | enabled | null | null | null | aKey:aValue,b1:b2 | | 7.4 | without | 74-rotateRsaKeyMap | 2048 | 257 | enabled | null | null | null | aKey:aValue,b1:b2 | + | 7.5 | without | 75-rotateRsaKeyMap | 2048 | 257 | enabled | null | null | null | aKey:aValue,b1:b2 | @Key @KeyCreate @KeyRotate @EC Scenario Outline: EC_ROTATE_01 Single versions of EC keys can be created with the key client, then rotated and result observed @@ -61,6 +62,7 @@ Feature: Key rotation | 7.3 | without | 73-rotateEcKeyAllOps | P-256 | 32 | enabled | sign,verify,import | null | null | null | | 7.3 | without | 73-rotateEcKeyNotEnabled | P-256 | 32 | not enabled | null | null | null | null | | 7.4 | without | 74-rotateEcKeyNotEnabled | P-256 | 32 | not enabled | null | null | null | null | + | 7.5 | without | 75-rotateEcKeyNotEnabled | P-256 | 32 | not enabled | null | null | null | null | @Key @KeyCreate @KeyRotate @OCT Scenario Outline: OCT_ROTATE_01 Single versions of OCT keys can be created with the key client, then rotated and result observed @@ -93,3 +95,4 @@ Feature: Key rotation | 7.3 | with | 73-rotateOctKeyAllOps | 128 | enabled | encrypt,decrypt,wrapKey,unwrapKey,import | null | null | null | | 7.3 | with | 73-rotateOctKeyNotEnabled | 128 | not enabled | null | null | null | null | | 7.4 | with | 74-rotateOctKey | 128 | enabled | null | null | null | null | + | 7.5 | with | 75-rotateOctKey | 128 | enabled | null | null | null | null | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/SignWithKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/SignWithKeys.feature index 15b06a57..e149ef72 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/SignWithKeys.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/SignWithKeys.feature @@ -22,6 +22,7 @@ Feature: Key sign and verify | 7.3 | signRsaKey-06 | 4096 | RS384 | | | 7.3 | signRsaKey-07 | 4096 | RS512 | The quick brown fox jumps over the lazy dog. | | 7.4 | signRsaKey-08 | 2048 | PS256 | The quick brown fox jumps over the lazy dog. | + | 7.5 | signRsaKey-09 | 2048 | PS256 | The quick brown fox jumps over the lazy dog. | @Key @KeyCreate @KeySign @EC Scenario Outline: EC_SIGN_01 An EC key is created with the key client then used for sign and verify operations @@ -47,3 +48,4 @@ Feature: Key sign and verify | 7.3 | signEc-08 | P-384 | ES384 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.3 | signEc-09 | P-521 | ES512 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | | 7.4 | signEc-10 | P-521 | ES512 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | + | 7.5 | signEc-11 | P-521 | ES512 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do. | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/BackupAndRestoreSecrets.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/BackupAndRestoreSecrets.feature index 03a694bb..c56df67a 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/BackupAndRestoreSecrets.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/BackupAndRestoreSecrets.feature @@ -24,3 +24,4 @@ Feature: Secret backup and restore | 7.3 | 06 | 25 | 73-backupSecret | | 7.3 | 07 | 42 | 73-backup-secret-name | | 7.4 | 08 | 42 | 73-backup-secret-name | + | 7.5 | 09 | 42 | 73-backup-secret-name | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/CreateSecrets.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/CreateSecrets.feature index 07cbc2c1..f96e6c5b 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/CreateSecrets.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/CreateSecrets.feature @@ -37,3 +37,4 @@ Feature: Secret creation | 7.3 | 73-createSecretDates | enabled | text/plain | Only sometimes. | 4321 | 1234 | null | | 7.3 | 73-createSecretNotEnabled | not enabled | text/plain | Not enabled | null | null | null | | 7.4 | 74-createSecretDates | enabled | text/plain | Only sometimes. | 4321 | 1234 | null | + | 7.5 | 75-createSecretDates | enabled | text/plain | Only sometimes. | 4321 | 1234 | null | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/DeleteSecrets.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/DeleteSecrets.feature index 8a82f1fd..5b613b55 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/DeleteSecrets.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/DeleteSecrets.feature @@ -21,6 +21,7 @@ Feature: Secret delete and recover | 7.3 | secrets-alias-delete | 5 | 73-deleteSecret2A | text/plain | The quick brown fox jumps over the lazy dog. | | 7.3 | secrets-alias-delete | 4 | 73-deleteSecretXmlA | application/xml | | | 7.4 | secrets-alias-delete | 4 | 74-deleteSecretXmlA | application/xml | | + | 7.5 | secrets-alias-delete | 4 | 75-deleteSecretXmlA | application/xml | | @Secret @SecretCreate @SecretDelete @SecretRecover @SecretAlias Scenario Outline: SECRET_RECOVER_01 Multiple versions of secrets are created with the secret client then deleted and recovered @@ -43,6 +44,7 @@ Feature: Secret delete and recover | 7.3 | secrets-alias-delete | 5 | 73-recoverSecret2A | text/plain | The quick brown fox jumps over the lazy dog. | | 7.3 | secrets-alias-delete | 4 | 73-recoverSecretXmlA | application/xml | | | 7.4 | secrets-delete | 5 | 74-recoverSecret2 | text/plain | The quick brown fox jumps over the lazy dog. | + | 7.5 | secrets-delete | 5 | 75-recoverSecret2 | text/plain | The quick brown fox jumps over the lazy dog. | @Secret @SecretCreate @SecretDelete @SecretPurge Scenario Outline: SECRET_PURGE_01 Multiple versions of secrets are created with the secret client then deleted and purged @@ -66,3 +68,4 @@ Feature: Secret delete and recover | 7.3 | 5 | 73-purgeSecret2 | text/plain | The quick brown fox jumps over the lazy dog. | | 7.3 | 4 | 73-purgeSecretXml | application/xml | | | 7.4 | 4 | 74-purgeSecretXml | application/xml | | + | 7.5 | 4 | 75-purgeSecretXml | application/xml | | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/GetSecrets.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/GetSecrets.feature index 34fbc898..92c5ff79 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/GetSecrets.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/GetSecrets.feature @@ -38,6 +38,7 @@ Feature: Secret get | 7.3 | 3 | 73-get01SecretJson | application/json | {"value":true} | null | null | null | | 7.3 | 4 | 73-get01SecretDates | text/plain | Only sometimes. | 4321 | 1234 | null | | 7.4 | 4 | 74-get01SecretDates | text/plain | Only sometimes. | 4321 | 1234 | null | + | 7.5 | 4 | 75-get01SecretDates | text/plain | Only sometimes. | 4321 | 1234 | null | @Secret @SecretCreate @SecretGet Scenario Outline: SECRET_GET_02 Multiple versions of secrets are created with the secret client then the first is fetched @@ -61,6 +62,7 @@ Feature: Secret get | 7.3 | 5 | 73-get02Secret2 | text/plain | The quick brown fox jumps over the lazy dog. | | 7.3 | 4 | 73-get02SecretXml | application/xml | | | 7.4 | 4 | 74-get02SecretXml | application/xml | | + | 7.5 | 4 | 75-get02SecretXml | application/xml | | @Secret @SecretCreate @SecretGet Scenario Outline: SECRET_GET_03 Multiple versions of disabled secrets are created with the secret client then the latest is fetched @@ -78,6 +80,7 @@ Feature: Secret get | 7.2 | 3 | 72-get03SecretNotEnabled | text/plain | Not enabled | | 7.3 | 3 | 73-get03SecretNotEnabled | text/plain | Not enabled | | 7.4 | 3 | 74-get03SecretNotEnabled | text/plain | Not enabled | + | 7.5 | 3 | 75-get03SecretNotEnabled | text/plain | Not enabled | @Secret @SecretCreate @SecretUpdate @SecretGet Scenario Outline: SECRET_UPDATE_01 Multiple versions of secrets are created with the secret client then the latest is updated and fetched @@ -118,3 +121,4 @@ Feature: Secret get | 7.3 | 4 | 73-update01SecretDates | enabled | text/plain | Only sometimes. | 4321 | 1234 | null | | 7.3 | 3 | 73-update01SecretNotEnabled | not enabled | text/plain | Not enabled | null | null | null | | 7.4 | 3 | 74-update01SecretJson | enabled | application/json | {"value":true} | null | null | null | + | 7.5 | 3 | 75-update01SecretJson | enabled | application/json | {"value":true} | null | null | null | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListDeletedSecrets.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListDeletedSecrets.feature index 42c0fb7a..cb57ef81 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListDeletedSecrets.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListDeletedSecrets.feature @@ -20,3 +20,4 @@ Feature: Secret list deleted | 7.3 | 06 | 25 | listSecret | | 7.3 | 07 | 42 | list-secret-name | | 7.4 | 08 | 25 | list-secret-name | + | 7.5 | 09 | 25 | list-secret-name | diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListSecrets.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListSecrets.feature index adfdb9fa..2b1fc8d7 100644 --- a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListSecrets.feature +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/secrets/ListSecrets.feature @@ -20,3 +20,4 @@ Feature: Secret list | 7.3 | 06 | 25 | listSecret | | 7.3 | 07 | 42 | list-secret-name | | 7.4 | 08 | 42 | list-secret-name | + | 7.5 | 09 | 42 | list-secret-name | diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-75.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-75.json new file mode 100644 index 00000000..ab346d0c --- /dev/null +++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-75.json @@ -0,0 +1,56 @@ +{ + "versions": [ + { + "vaultBaseUri": "https://keys-backup-jsonBackupEc-384-75.localhost:8443", + "entityId": "jsonBackupEc-384-75", + "entityVersion": "7f4c0a2ef5454e07a533e597434984a8", + "attributes": { + "enabled": true, + "created": 1649503879, + "updated": 1649503879, + "recoveryLevel": "Recoverable+Purgeable", + "exp": 1659871879, + "recoverableDays": 90 + }, + "tags": {}, + "managed": false, + "keyMaterial": { + "crv": "P-384", + "d": "bd2taaXwxvA_DRUZ1wMT28l8TnaMDz1mn2Z2x_pJT_nkZ11BNS1FFxJvjYHIvoU4", + "key_ops": [ + "sign" + ], + "kid": "https://keys-backup-jsonBackupEc-384-75.localhost:8443/keys/jsonBackupEc-384-75/7f4c0a2ef5454e07a533e597434984a8", + "kty": "EC", + "x": "KzD2vTm-aSjXN_RFlY7P78R6hpfdJcSHTC9WM7QXmf0VJro3cXdFOZk6vrx5WDjE", + "y": "AKYy8hXwjc0O8mVBXOolUvHklqEV2POLIN6c3EpZIJ-Sz2H_Vce0EoAF320bZhxrfw" + } + } + ], + "rotationPolicy": { + "id": "https://keys-backup-jsonBackupEc-384-75.localhost:8443/keys/jsonBackupEc-384-75/rotationpolicy", + "lifetimeActions": [ + { + "trigger": { + "timeBeforeExpiry": "P30D" + }, + "action": { + "type": "notify" + } + }, + { + "trigger": { + "timeAfterCreate": "P100D" + }, + "action": { + "type": "rotate" + } + } + ], + "attributes": { + "expiryTime": "P120D", + "created": 1649503879, + "updated": 1649503879 + } + } +} diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-75.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-75.json new file mode 100644 index 00000000..416bea5b --- /dev/null +++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-75.json @@ -0,0 +1,55 @@ +{ + "versions": [ + { + "vaultBaseUri": "https://keys-backup-jsonBackupOct-192-75.localhost:8443", + "entityId": "jsonBackupOct-192-75", + "entityVersion": "55ccab8f94b244ab97d82899340c22dd", + "attributes": { + "enabled": true, + "created": 1649503106, + "updated": 1649503106, + "recoveryLevel": "Recoverable+Purgeable", + "exp": 1659871879, + "recoverableDays": 90 + }, + "tags": {}, + "managed": false, + "keyMaterial": { + "k": "fp2J-nnMUBZVxCDFdKDxjDJX0F_BIM8P", + "key_ops": [ + "encrypt", + "wrapKey" + ], + "kid": "https://keys-backup-jsonBackupOct-192-75.localhost:8443/keys/jsonBackupOct-192-75/55ccab8f94b244ab97d82899340c22dd", + "kty": "oct-HSM" + } + } + ], + + "rotationPolicy": { + "id": "https://keys-backup-jsonBackupOct-192-75.localhost:8443/keys/jsonBackupOct-192-75/rotationpolicy", + "lifetimeActions": [ + { + "trigger": { + "timeBeforeExpiry": "P30D" + }, + "action": { + "type": "notify" + } + }, + { + "trigger": { + "timeAfterCreate": "P100D" + }, + "action": { + "type": "rotate" + } + } + ], + "attributes": { + "expiryTime": "P120D", + "created": 1649503106, + "updated": 1649503106 + } + } +} diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-75.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-75.json new file mode 100644 index 00000000..9277d9c3 --- /dev/null +++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-75.json @@ -0,0 +1,62 @@ +{ + "versions": [ + { + "vaultBaseUri": "https://keys-backup-jsonBackupRsa-2048-75.localhost:8443", + "entityId": "jsonBackupRsa-2048-75", + "entityVersion": "2d93f37afada4679b00b528f7238ad5c", + "attributes": { + "enabled": true, + "created": 1649504957, + "updated": 1649504957, + "recoveryLevel": "Recoverable+Purgeable", + "exp": 1659871879, + "recoverableDays": 90 + }, + "tags": {}, + "managed": false, + "keyMaterial": { + "d": "BOSCOBcA_oaZAMPp3APYWbQvyPuw0uFwblDxFSN8YGsSaMqNZMc4b01hC7oMRJzTC7k5dFkCcvikKJU3xWiqNY-FP9HEPJkHezEOQ9rk4nKuJuex157gf1SUxqzDttjQxty027P0ZVL-EmB6OdET2CKVpEyTo7RVZ9UBvQnb1S7-ZBU7UTBotWA7MRsfItvTX_EHX9atPJegpSWmJaJ05-_-dXkRnffrk43bLJ5cJEF4G6X7c4rUFjxSTJ3OwTjmpZPAJT1q85H3-NXrhXiabkFicR3cIj4OpA4egUpRwVze0VAbNOlIsZYIUocLuBelKYLLmr5hTnP_UKSKwznayQ", + "dp": "AN9pfqyKw-uT9FWr8GqsyhqJYR9KLgTtX6uqxJk_Y-4NZEMooCeq2olaVpFic0tfaOMJnDufYsUBL_i1TxoD9w3s0YjVwBiURFKs-0Ghs2RGxk22WGbKrnS4Zh8k2RKkNvFPnRciXva1nfMjeWEahb6O8ReA7AZNECKAqJtE5UTB", + "dq": "B7sIKKG4bo4Gvm4xj7KBnlP4X9UQH8FLnisGVe9HqDJH4Zu7d-dj5yHT-HaX07MU5p0r2H26owT829SxZGZB2qR_njnuyt8KnmAdEJPttNkW6L25csAoEzqdexi08wmWH0fAEMVDgeobAg1YtByts-VmajO0TWxYyO1oiJFc6ok", + "e": "AQAB", + "key_ops": [ + "sign", + "encrypt", + "wrapKey" + ], + "kid": "https://keys-backup-jsonBackupRsa-2048-75.localhost:8443/keys/jsonBackupRsa-2048-75/2d93f37afada4679b00b528f7238ad5c", + "kty": "RSA-HSM", + "n": "AMfXpr8tkLe86czuJnDBbzcNFNk5paAm0hcI6zD-P1OYvinL2zJG3-8O0GQNuHeExZykhpfOqo6nOv1tbcqesUwkgcT385z2Cmf0G0TfK7EcvDRO8APxqMIvuQpglzFOXAJ4aqVD56IWFMzzkkaR73DKmDF-6cmWtrZu0dEv7LN5cZAk2wd421k5JTbcHoo5kAZBv_CJNt_M0n5VMK5jACV_d53mmaumTHffu-BLOyfBlr0cQMr63aBYh8q5KlcL7U5QJC01umOl4Wwl7yrbD4jZo5f3kWwYTDfeRQlz40h68XUssPc9lG7Gsj9hYxalyCwP9ix5dcs4x-OsYXLX-nM", + "p": "AOqsdAb_YNN9OdgFRuVNUmMlG2DpgQfCs8kHUi0_s4IGLjgu4yX1LUhVNnBMLCxTEGO7U-N9e3jHRDexQLHbXzt2IkrN-pwh3MZVEry6yxQILxbMp4aPqZpWTs9ufSiX0iI68lb7uZhsiaMcpEYQX_N67Vlj1Z6-rCGM1K_-86op", + "q": "ANoA3nnRaVah2bdmqdjK1Hh20wj4gTEQrKnt_XZDlxMo_HZ77saX_hMjJpYX008w-ZPV-LFNc_clcHK7ZedJZB6pcmuumPvLuvqSqifD4wEwcQAR3a8aTs2gBeqmjyxXXtQk8KK8LNpIzclm98n0vmk1kc0fHobmr_KVFyFBYAs7", + "qi": "AOfgFCIEztUVLY7nA5-vMDPRYsf8oKSVeeXDqueXogic75BzYL4mfWevgfkyatP0aa8y8E38annoMN-E1J-cu2gBneLQCd9rf0cIqRIooTwZ-RMLPEKtpBNcT3sBirfeviITpDqOirbke92XbMNGkAEPoC5JPiCYU1FASGDaJFaF" + } + } + ], + "rotationPolicy": { + "id": "https://keys-backup-jsonBackupRsa-2048-75.localhost:8443/keys/jsonBackupRsa-2048-75/rotationpolicy", + "lifetimeActions": [ + { + "trigger": { + "timeBeforeExpiry": "P30D" + }, + "action": { + "type": "notify" + } + }, + { + "trigger": { + "timeAfterCreate": "P100D" + }, + "action": { + "type": "rotate" + } + } + ], + "attributes": { + "expiryTime": "P120D", + "created": 1649504966, + "updated": 1649504966 + } + } +}