diff --git a/README.md b/README.md index 70e69fac..b35501ea 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ Lowkey Vault is far from supporting all Azure Key Vault features. The list suppo - Password used for PKCS12 stores: `lowkey-vault` - Get certificate operation - Get pending create operation results + - Get pending delete operation results - Get available certificate versions - Get certificate - Latest version of a single certificate @@ -164,6 +165,12 @@ Lowkey Vault is far from supporting all Azure Key Vault features. The list suppo - Import certificate - Self-signed only - The downloadable certificate is protected using `lowkey-vault` as password for PKCS12 stores +- Get deleted certificate + - Latest version of a single certificate + - List of all certificate +- Delete certificate +- Recover deleted certificate +- Purge deleted certificate #### Warning! diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonCertificateController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonCertificateController.java index e9b5df40..3ff013ba 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonCertificateController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonCertificateController.java @@ -77,8 +77,23 @@ public ResponseEntity pendingCreate( log.info("Received request to {} get pending create certificate: {} using API version: {}", baseUri.toString(), certificateName, apiVersion()); final CertificateVaultFake vaultFake = getVaultByUri(baseUri); - final VersionedCertificateEntityId entityId = vaultFake.getEntities().getLatestVersionOfEntity(entityId(baseUri, certificateName)); - final ReadOnlyKeyVaultCertificateEntity readOnlyEntity = vaultFake.getEntities().getReadOnlyEntity(entityId); + final VersionedCertificateEntityId entityId = vaultFake + .getEntities().getLatestVersionOfEntity(entityId(baseUri, certificateName)); + final ReadOnlyKeyVaultCertificateEntity readOnlyEntity = vaultFake + .getEntities().getReadOnlyEntity(entityId); + return ResponseEntity.ok(pendingModelConverter.convert(readOnlyEntity, baseUri)); + } + + public ResponseEntity pendingDelete( + @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + final URI baseUri) { + log.info("Received request to {} get pending delete certificate: {} using API version: {}", + baseUri.toString(), certificateName, apiVersion()); + final CertificateVaultFake vaultFake = getVaultByUri(baseUri); + final VersionedCertificateEntityId entityId = vaultFake.getDeletedEntities() + .getLatestVersionOfEntity(entityId(baseUri, certificateName)); + final ReadOnlyKeyVaultCertificateEntity readOnlyEntity = vaultFake + .getDeletedEntities().getReadOnlyEntity(entityId); return ResponseEntity.ok(pendingModelConverter.convert(readOnlyEntity, baseUri)); } @@ -90,6 +105,7 @@ public ResponseEntity get( return ResponseEntity.ok(getLatestEntityModel(baseUri, certificateName)); } + public ResponseEntity getPolicy( @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, final URI baseUri) { @@ -122,6 +138,56 @@ public ResponseEntity importCertificate( return ResponseEntity.ok().body(convertDetails(readOnlyEntity, baseUri)); } + public ResponseEntity delete( + @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + final URI baseUri) { + log.info("Received request to {} delete certificate: {} using API version: {}", + baseUri.toString(), certificateName, apiVersion()); + + final CertificateVaultFake vaultFake = getVaultByUri(baseUri); + final CertificateEntityId entityId = new CertificateEntityId(baseUri, certificateName); + vaultFake.delete(entityId); + final VersionedCertificateEntityId latestVersion = vaultFake.getDeletedEntities().getLatestVersionOfEntity(entityId); + return ResponseEntity.ok(getDeletedModelById(vaultFake, latestVersion, baseUri, true)); + } + + public ResponseEntity getDeletedCertificate( + @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + final URI baseUri) { + log.info("Received request to {} get deleted certificate: {} using API version: {}", + baseUri.toString(), certificateName, apiVersion()); + + final CertificateVaultFake vaultFake = getVaultByUri(baseUri); + final CertificateEntityId entityId = new CertificateEntityId(baseUri, certificateName); + final VersionedCertificateEntityId latestVersion = vaultFake.getDeletedEntities().getLatestVersionOfEntity(entityId); + return ResponseEntity.ok(getDeletedModelById(vaultFake, latestVersion, baseUri, false)); + } + + public ResponseEntity recoverDeletedCertificate( + @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + final URI baseUri) { + log.info("Received request to {} recover deleted certificate: {} using API version: {}", + baseUri.toString(), certificateName, apiVersion()); + + final CertificateVaultFake vaultFake = getVaultByUri(baseUri); + final CertificateEntityId entityId = new CertificateEntityId(baseUri, certificateName); + vaultFake.recover(entityId); + final VersionedCertificateEntityId latestVersion = vaultFake.getEntities().getLatestVersionOfEntity(entityId); + return ResponseEntity.ok(getModelById(vaultFake, latestVersion, baseUri, true)); + } + + public ResponseEntity purgeDeleted( + @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, + final URI baseUri) { + log.info("Received request to {} purge deleted certificate: {} using API version: {}", + baseUri.toString(), certificateName, apiVersion()); + + final CertificateVaultFake vaultFake = getVaultByUri(baseUri); + final CertificateEntityId entityId = new CertificateEntityId(baseUri, certificateName); + vaultFake.purge(entityId); + return ResponseEntity.noContent().build(); + } + public ResponseEntity> versions( @Valid @Pattern(regexp = NAME_PATTERN) final String certificateName, final URI baseUri, @@ -145,14 +211,29 @@ public ResponseEntity> listC return ResponseEntity.ok(getPageOfItems(baseUri, maxResults, skipToken, includePending)); } + public ResponseEntity> listDeletedCertificates( + final URI baseUri, + final int maxResults, + final int skipToken, + final boolean includePending) { + log.info("Received request to {} list deleted certificates, (max results: {}, skip: {}, includePending: {}) using API version: {}", + baseUri.toString(), maxResults, skipToken, includePending, apiVersion()); + + return ResponseEntity.ok(getPageOfDeletedItems(baseUri, maxResults, skipToken, includePending)); + } + private KeyVaultItemListModel getPageOfItems( final URI baseUri, final int limit, final int offset, final boolean includePending) { - final KeyVaultItemListModel page = super.getPageOfItems(baseUri, limit, offset, "/certificates"); - final String nextLink = Optional.ofNullable(page.getNextLink()) - .map(next -> next + "&" + INCLUDE_PENDING_PARAM + "=" + includePending) - .orElse(null); - page.setNextLink(nextLink); - return page; + final KeyVaultItemListModel page = + super.getPageOfItems(baseUri, limit, offset, "/certificates"); + return fixNextLink(page, includePending); + } + + private KeyVaultItemListModel getPageOfDeletedItems( + final URI baseUri, final int limit, final int offset, final boolean includePending) { + final KeyVaultItemListModel page = + super.getPageOfDeletedItems(baseUri, limit, offset, "/deletedcertificates"); + return fixNextLink(page, includePending); } @Override @@ -165,6 +246,16 @@ protected CertificateEntityId entityId(final URI baseUri, final String name) { return new CertificateEntityId(baseUri, name); } + private
  • KeyVaultItemListModel
  • fixNextLink( + final KeyVaultItemListModel
  • page, + final boolean includePending) { + final String nextLink = Optional.ofNullable(page.getNextLink()) + .map(next -> next + "&" + INCLUDE_PENDING_PARAM + "=" + includePending) + .orElse(null); + page.setNextLink(nextLink); + return page; + } + private VersionedCertificateEntityId createCertificateWithAttributes( final CertificateVaultFake certificateVaultFake, final String certificateName, final CreateCertificateRequest request) { final CertificatePropertiesModel properties = Objects.requireNonNullElse(request.getProperties(), new CertificatePropertiesModel()); diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyController.java index 647a57c0..8baf04d2 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyController.java @@ -99,7 +99,7 @@ public ResponseEntity> listKeys( return ResponseEntity.ok(getPageOfItems(baseUri, maxResults, skipToken, "/keys")); } - public ResponseEntity> listDeletedKeys( + public ResponseEntity> listDeletedKeys( final URI baseUri, final int maxResults, final int skipToken) { @@ -145,7 +145,7 @@ public ResponseEntity updateVersion( return ResponseEntity.ok(getModelById(keyVaultFake, entityId, baseUri, true)); } - public ResponseEntity getDeletedKey( + public ResponseEntity getDeletedKey( @Valid @Pattern(regexp = NAME_PATTERN) final String keyName, final URI baseUri) { log.info("Received request to {} get deleted key: {} using API version: {}", diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretController.java index 45f243a3..6ab19257 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretController.java @@ -49,7 +49,7 @@ public ResponseEntity create( return ResponseEntity.ok(getModelById(secretVaultFake, secretEntityId, baseUri, true)); } - public ResponseEntity delete( + public ResponseEntity delete( @Valid @Pattern(regexp = NAME_PATTERN) final String secretName, final URI baseUri) { log.info("Received request to {} delete secret: {} using API version: {}", @@ -84,7 +84,7 @@ public ResponseEntity> listSecret return ResponseEntity.ok(getPageOfItems(baseUri, maxResults, skipToken, "/secrets")); } - public ResponseEntity> listDeletedSecrets( + public ResponseEntity> listDeletedSecrets( final URI baseUri, final int maxResults, final int skipToken) { diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/GenericEntityController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/GenericEntityController.java index 0426e51f..6d061139 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/GenericEntityController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/GenericEntityController.java @@ -117,10 +117,10 @@ protected KeyVaultItemListModel getPageOfItems(final URI baseUri, final int l } @SuppressWarnings("SameParameterValue") - protected KeyVaultItemListModel getPageOfDeletedItems(final URI baseUri, final int limit, final int offset, final String uriPath) { + protected KeyVaultItemListModel getPageOfDeletedItems(final URI baseUri, final int limit, final int offset, final String uriPath) { final S entityVaultFake = getVaultByUri(baseUri); final List allItems = entityVaultFake.getDeletedEntities().listLatestNonManagedEntities(); - final List items = filterList(limit, offset, allItems, source -> itemConverter.convertDeleted(source, baseUri)); + final List items = filterList(limit, offset, allItems, source -> itemConverter.convertDeleted(source, baseUri)); final URI nextUri = getNextUri(baseUri + uriPath, allItems, items, limit, offset); return listModel(items, nextUri); } @@ -156,7 +156,7 @@ protected void updateTags(final BaseVaultFake vaultFake, final V entity }); } - protected KeyVaultItemListModel listModel(final List items, final URI nextUri) { + protected
  • KeyVaultItemListModel
  • listModel(final List
  • items, final URI nextUri) { return new KeyVaultItemListModel<>(items, nextUri); } @@ -169,8 +169,8 @@ private URI getNextUri(final String prefix, final Collection allItems, return nextUri; } - private List filterList( - final int limit, final int offset, final Collection allItems, final Function mapper) { + private List
  • filterList( + final int limit, final int offset, final Collection allItems, final Function mapper) { return allItems.stream() .skip(offset) .limit(limit) diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyController.java index 5e085871..049a2fbf 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyController.java @@ -6,6 +6,8 @@ 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.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; @@ -48,9 +50,10 @@ public KeyController(@NonNull final KeyEntityToV72ModelConverter keyEntityToV72M params = API_VERSION_7_2, 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) { + 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); } @@ -59,9 +62,10 @@ public ResponseEntity create(@PathVariable @Valid @Pattern(reg params = API_VERSION_7_2, 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) { + 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); } @@ -69,8 +73,9 @@ public ResponseEntity importKey(@PathVariable @Valid @Pattern( @DeleteMapping(value = "/keys/{keyName}", params = API_VERSION_7_2, 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) { + 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); } @@ -101,7 +106,7 @@ public ResponseEntity> listKeys( @GetMapping(value = "/deletedkeys", params = API_VERSION_7_2, produces = APPLICATION_JSON_VALUE) - public ResponseEntity> listDeletedKeys( + 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) { @@ -146,8 +151,9 @@ public ResponseEntity updateVersion( @GetMapping(value = "/deletedkeys/{keyName}", params = API_VERSION_7_2, 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) { + 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); } @@ -155,8 +161,9 @@ public ResponseEntity getDeletedKey(@PathVariable @Valid @Patt @PostMapping(value = "/deletedkeys/{keyName}/recover", params = API_VERSION_7_2, 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) { + 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); } @@ -164,8 +171,9 @@ public ResponseEntity recoverDeletedKey(@PathVariable @Valid @ @DeleteMapping(value = "/deletedkeys/{keyName}", params = API_VERSION_7_2, 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) { + 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); } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretController.java index 2738055a..a79d9f6c 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretController.java @@ -6,6 +6,8 @@ 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.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; @@ -48,9 +50,10 @@ public SecretController( params = API_VERSION_7_2, 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) { + 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); } @@ -58,8 +61,9 @@ public ResponseEntity create(@PathVariable @Valid @Pattern( @DeleteMapping(value = "/secrets/{secretName}", params = API_VERSION_7_2, 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) { + 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); } @@ -90,7 +94,7 @@ public ResponseEntity> listSecret @GetMapping(value = "/deletedsecrets", params = API_VERSION_7_2, produces = APPLICATION_JSON_VALUE) - public ResponseEntity> listDeletedSecrets( + 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) { diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateController.java index dcc02355..a2024620 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateController.java @@ -64,6 +64,16 @@ public ResponseEntity pendingCreate( return super.pendingCreate(certificateName, baseUri); } + @Override + @DeleteMapping(value = "/certificates/{certificateName}/pending", + params = API_VERSION_7_3, + 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 @PostMapping(value = "/certificates/{certificateName}/import", params = API_VERSION_7_3, @@ -107,6 +117,46 @@ public ResponseEntity getWithVersion( return super.getWithVersion(certificateName, certificateVersion, baseUri); } + @Override + @DeleteMapping(value = "/certificates/{certificateName}", + params = API_VERSION_7_3, + 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}", + params = API_VERSION_7_3, + 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", + params = API_VERSION_7_3, + 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}", + params = API_VERSION_7_3, + 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", params = API_VERSION_7_3, @@ -131,6 +181,18 @@ public ResponseEntity> listC return super.listCertificates(baseUri, maxResults, skipToken, includePending); } + @Override + @GetMapping(value = "/deletedcertificates", + params = API_VERSION_7_3, + 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 protected String apiVersion() { return V_7_3; diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java index 1feb3cdd..1551f016 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java @@ -6,6 +6,8 @@ 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.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; @@ -106,7 +108,7 @@ public ResponseEntity> listKeys( @GetMapping(value = "/deletedkeys", params = API_VERSION_7_3, produces = APPLICATION_JSON_VALUE) - public ResponseEntity> listDeletedKeys( + 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) { @@ -165,7 +167,7 @@ public ResponseEntity rotateKey( @GetMapping(value = "/deletedkeys/{keyName}", params = API_VERSION_7_3, produces = APPLICATION_JSON_VALUE) - public ResponseEntity getDeletedKey( + 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); diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretController.java index 6d002823..5d8adb4f 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretController.java @@ -6,6 +6,8 @@ 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.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; @@ -48,9 +50,10 @@ public SecretController( params = API_VERSION_7_3, 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) { + 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); } @@ -58,8 +61,9 @@ public ResponseEntity create(@PathVariable @Valid @Pattern( @DeleteMapping(value = "/secrets/{secretName}", params = API_VERSION_7_3, 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) { + 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); } @@ -90,7 +94,7 @@ public ResponseEntity> listSecret @GetMapping(value = "/deletedsecrets", params = API_VERSION_7_3, produces = APPLICATION_JSON_VALUE) - public ResponseEntity> listDeletedSecrets( + 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) { diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/certificate/CertificatePropertiesModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/certificate/CertificatePropertiesModel.java index 96cc1dc7..fdc930d6 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/certificate/CertificatePropertiesModel.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/certificate/CertificatePropertiesModel.java @@ -4,4 +4,8 @@ public final class CertificatePropertiesModel extends BasePropertiesModel { + public CertificatePropertiesModel() { + this.setRecoverableDays(null); + this.setRecoveryLevel(null); + } } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImpl.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImpl.java index b8bb08dd..ac447712 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImpl.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImpl.java @@ -6,6 +6,8 @@ import com.github.nagyesta.lowkeyvault.service.certificate.id.CertificateEntityId; import com.github.nagyesta.lowkeyvault.service.certificate.id.VersionedCertificateEntityId; import com.github.nagyesta.lowkeyvault.service.common.impl.BaseVaultFakeImpl; +import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId; +import com.github.nagyesta.lowkeyvault.service.secret.id.SecretEntityId; import com.github.nagyesta.lowkeyvault.service.vault.VaultFake; import lombok.NonNull; @@ -39,4 +41,33 @@ public VersionedCertificateEntityId importCertificateVersion( name, input, vaultFake()); return addVersion(entity.getId(), entity); } + + @Override + public void delete(@NonNull final CertificateEntityId entityId) { + super.delete(entityId); + vaultFake().keyVaultFake().delete(toKeyEntityId(entityId)); + vaultFake().secretVaultFake().delete(toSecretEntityId(entityId)); + } + + @Override + public void recover(@NonNull final CertificateEntityId entityId) { + super.recover(entityId); + vaultFake().keyVaultFake().recover(toKeyEntityId(entityId)); + vaultFake().secretVaultFake().recover(toSecretEntityId(entityId)); + } + + @Override + public void purge(@NonNull final CertificateEntityId entityId) { + super.purge(entityId); + vaultFake().keyVaultFake().purge(toKeyEntityId(entityId)); + vaultFake().secretVaultFake().purge(toSecretEntityId(entityId)); + } + + private KeyEntityId toKeyEntityId(final CertificateEntityId entityId) { + return new KeyEntityId(entityId.vault(), entityId.id()); + } + + private SecretEntityId toSecretEntityId(final CertificateEntityId entityId) { + return new SecretEntityId(entityId.vault(), entityId.id()); + } } diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyControllerTest.java index 3a8bfd31..edc9d152 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyControllerTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyControllerTest.java @@ -506,7 +506,7 @@ void testGetDeletedKeyShouldReturnEntryWhenKeyIsFound( .thenReturn(DELETED_RESPONSE); //when - final ResponseEntity actual = underTest.getDeletedKey(KEY_NAME_1, HTTPS_LOCALHOST_8443); + final ResponseEntity actual = underTest.getDeletedKey(KEY_NAME_1, HTTPS_LOCALHOST_8443); //then Assertions.assertNotNull(actual); @@ -676,7 +676,7 @@ void testGetDeletedKeysShouldReturnEntryWhenKeyIsFound( .thenReturn(keyItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedKeys(HTTPS_LOCALHOST_8443, 1, 0); //then @@ -764,7 +764,7 @@ void testGetDeletedKeysShouldReturnNextLinkWhenNotOnLastPage( .thenReturn(keyItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedKeys(HTTPS_LOCALHOST_8443, 1, 0); //then diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretControllerTest.java index 9c6f6697..4be38d15 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretControllerTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretControllerTest.java @@ -331,7 +331,7 @@ void testDeleteSecretShouldReturnEntryWhenSecretIsFound( .thenReturn(DELETED_RESPONSE); //when - final ResponseEntity actual = underTest.delete(SECRET_NAME_1, HTTPS_LOCALHOST_8443); + final ResponseEntity actual = underTest.delete(SECRET_NAME_1, HTTPS_LOCALHOST_8443); //then Assertions.assertNotNull(actual); @@ -642,7 +642,7 @@ void testGetDeletedSecretsShouldReturnEntryWhenSecretIsFound( .thenReturn(secretItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedSecrets(HTTPS_LOCALHOST_8443, 1, 0); //then @@ -687,7 +687,7 @@ void testGetDeletedSecretsShouldReturnNextLinkWhenNotOnLastPage( .thenReturn(secretItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedSecrets(HTTPS_LOCALHOST_8443, 1, 0); //then diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateControllerIntegrationTest.java index 5dd9fb73..68137911 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateControllerIntegrationTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/CertificateControllerIntegrationTest.java @@ -2,17 +2,22 @@ import com.github.nagyesta.abortmission.booster.jupiter.annotation.LaunchAbortArmed; import com.github.nagyesta.lowkeyvault.ResourceUtils; +import com.github.nagyesta.lowkeyvault.model.common.DeletedModel; import com.github.nagyesta.lowkeyvault.model.common.KeyVaultItemListModel; +import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel; 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_3.certificate.*; +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.CertAuthorityType; import com.github.nagyesta.lowkeyvault.service.certificate.impl.CertContentType; import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException; +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.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -32,8 +37,7 @@ import static com.github.nagyesta.lowkeyvault.TestConstantsCertificates.*; import static com.github.nagyesta.lowkeyvault.TestConstantsUri.*; import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_3; -import static org.springframework.http.HttpStatus.ACCEPTED; -import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.*; @LaunchAbortArmed @SpringBootTest @@ -46,6 +50,18 @@ class CertificateControllerIntegrationTest { @Autowired private VaultService vaultService; + @BeforeEach + void setUp() { + prepareVaultIfNotExists(HTTPS_DEFAULT_LOWKEY_VAULT_80); + prepareVaultIfNotExists(HTTPS_LOOP_BACK_IP_80); + } + + private void prepareVaultIfNotExists(final URI baseUri) { + if (vaultService.list().stream().map(VaultFake::baseUri).noneMatch(baseUri::equals)) { + vaultService.create(baseUri, RecoveryLevel.RECOVERABLE_AND_PURGEABLE, RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE, Set.of()); + } + } + @SuppressWarnings("checkstyle:MagicNumber") public static Stream certificateListProvider() { return Stream.builder() @@ -65,7 +81,7 @@ void testCreateShouldReturnPendingCertificateWhenCalled() { //when final ResponseEntity actual = underTest - .create("create-" + CERT_NAME_1, HTTPS_LOCALHOST_8443, request); + .create("create-" + CERT_NAME_1, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); //then Assertions.assertEquals(ACCEPTED, actual.getStatusCode()); @@ -77,11 +93,11 @@ void testCreateShouldReturnPendingCertificateWhenCalled() { void testPendingCreateShouldReturnPendingCertificateWhenExists() { //given final CreateCertificateRequest request = getCreateCertificateRequest(); - underTest.create("pending-" + CERT_NAME_2, HTTPS_LOCALHOST_8443, request); + underTest.create("pending-" + CERT_NAME_2, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); //when final ResponseEntity actual = underTest - .pendingCreate("pending-" + CERT_NAME_2, HTTPS_LOCALHOST_8443); + .pendingCreate("pending-" + CERT_NAME_2, HTTPS_DEFAULT_LOWKEY_VAULT_80); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -95,7 +111,36 @@ void testPendingCreateShouldThrowExceptionWhenNotFound() { //when Assertions.assertThrows(NotFoundException.class, () -> underTest - .pendingCreate("pending-" + CERT_NAME_3, HTTPS_LOCALHOST_8443)); + .pendingCreate("pending-" + CERT_NAME_3, HTTPS_DEFAULT_LOWKEY_VAULT_80)); + + //then + exception + } + + @Test + void testPendingDeleteShouldReturnPendingCertificateWhenExists() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = "pending-del-" + CERT_NAME_2; + underTest.create(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + underTest.delete(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80); + + //when + final ResponseEntity actual = underTest + .pendingDelete(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80); + + //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, HTTPS_DEFAULT_LOWKEY_VAULT_80)); //then + exception } @@ -104,14 +149,14 @@ void testPendingCreateShouldThrowExceptionWhenNotFound() { void testGetShouldReturnModelWhenCalledWithValidData() { //given final CreateCertificateRequest request = getCreateCertificateRequest(); - underTest.create(CERT_NAME_2, HTTPS_LOCALHOST_8443, request); - final Deque versions = vaultService.findByUri(HTTPS_LOCALHOST_8443) + underTest.create(CERT_NAME_2, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + final Deque versions = vaultService.findByUri(HTTPS_DEFAULT_LOWKEY_VAULT_80) .certificateVaultFake() .getEntities() - .getVersions(new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_2)); + .getVersions(new CertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, CERT_NAME_2)); //when - final ResponseEntity actual = underTest.get(CERT_NAME_2, HTTPS_LOCALHOST_8443); + final ResponseEntity actual = underTest.get(CERT_NAME_2, HTTPS_DEFAULT_LOWKEY_VAULT_80); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -120,8 +165,8 @@ void testGetShouldReturnModelWhenCalledWithValidData() { Assertions.assertNotNull(body.getCertificate()); Assertions.assertNotNull(body.getThumbprint()); Assertions.assertEquals(Collections.emptyMap(), body.getTags()); - final URI id = new VersionedCertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_2, versions.getFirst()) - .asUri(HTTPS_LOCALHOST_8443); + final URI id = new VersionedCertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, CERT_NAME_2, versions.getFirst()) + .asUri(HTTPS_DEFAULT_LOWKEY_VAULT_80); Assertions.assertEquals(id.toString(), body.getId()); } @@ -129,15 +174,15 @@ void testGetShouldReturnModelWhenCalledWithValidData() { void testGetWithVersionShouldReturnModelWhenCalledWithValidData() { //given final CreateCertificateRequest request = getCreateCertificateRequest(); - underTest.create(CERT_NAME_3, HTTPS_LOCALHOST_8443, request); - final Deque versions = vaultService.findByUri(HTTPS_LOCALHOST_8443) + underTest.create(CERT_NAME_3, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + final Deque versions = vaultService.findByUri(HTTPS_DEFAULT_LOWKEY_VAULT_80) .certificateVaultFake() .getEntities() - .getVersions(new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_3)); + .getVersions(new CertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, CERT_NAME_3)); //when final ResponseEntity actual = underTest - .getWithVersion(CERT_NAME_3, versions.getFirst(), HTTPS_LOCALHOST_8443); + .getWithVersion(CERT_NAME_3, versions.getFirst(), HTTPS_DEFAULT_LOWKEY_VAULT_80); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -146,8 +191,8 @@ void testGetWithVersionShouldReturnModelWhenCalledWithValidData() { Assertions.assertNotNull(body.getCertificate()); Assertions.assertNotNull(body.getThumbprint()); Assertions.assertEquals(Collections.emptyMap(), body.getTags()); - final URI id = new VersionedCertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_3, versions.getFirst()) - .asUri(HTTPS_LOCALHOST_8443); + final URI id = new VersionedCertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, CERT_NAME_3, versions.getFirst()) + .asUri(HTTPS_DEFAULT_LOWKEY_VAULT_80); Assertions.assertEquals(id.toString(), body.getId()); } @@ -170,7 +215,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPemData() { //when final ResponseEntity actual = underTest - .importCertificate(name, HTTPS_LOCALHOST_8443, request); + .importCertificate(name, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -179,7 +224,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPemData() { Assertions.assertNotNull(body.getCertificate()); Assertions.assertNotNull(body.getThumbprint()); Assertions.assertEquals(Collections.emptyMap(), body.getTags()); - Assertions.assertTrue(body.getId().startsWith(HTTPS_LOCALHOST_8443.toString())); + Assertions.assertTrue(body.getId().startsWith(HTTPS_DEFAULT_LOWKEY_VAULT_80.toString())); Assertions.assertTrue(body.getId().contains(name)); } @@ -191,7 +236,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPkcs12Data() { //when final ResponseEntity actual = underTest - .importCertificate(name, HTTPS_LOCALHOST_8443, request); + .importCertificate(name, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -200,7 +245,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPkcs12Data() { Assertions.assertNotNull(body.getCertificate()); Assertions.assertNotNull(body.getThumbprint()); Assertions.assertEquals(Collections.emptyMap(), body.getTags()); - Assertions.assertTrue(body.getId().startsWith(HTTPS_LOCALHOST_8443.toString())); + Assertions.assertTrue(body.getId().startsWith(HTTPS_DEFAULT_LOWKEY_VAULT_80.toString())); Assertions.assertTrue(body.getId().contains(name)); } @@ -212,7 +257,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPemDataAndNoType() //when final ResponseEntity actual = underTest - .importCertificate(name, HTTPS_LOCALHOST_8443, request); + .importCertificate(name, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -221,7 +266,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPemDataAndNoType() Assertions.assertNotNull(body.getCertificate()); Assertions.assertNotNull(body.getThumbprint()); Assertions.assertEquals(Collections.emptyMap(), body.getTags()); - Assertions.assertTrue(body.getId().startsWith(HTTPS_LOCALHOST_8443.toString())); + Assertions.assertTrue(body.getId().startsWith(HTTPS_DEFAULT_LOWKEY_VAULT_80.toString())); Assertions.assertTrue(body.getId().contains(name)); } @@ -233,7 +278,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPkcs12DataAndNoTyp //when final ResponseEntity actual = underTest - .importCertificate(name, HTTPS_LOCALHOST_8443, request); + .importCertificate(name, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -242,7 +287,7 @@ void testImportCertificateShouldReturnModelWhenCalledWithValidPkcs12DataAndNoTyp Assertions.assertNotNull(body.getCertificate()); Assertions.assertNotNull(body.getThumbprint()); Assertions.assertEquals(Collections.emptyMap(), body.getTags()); - Assertions.assertTrue(body.getId().startsWith(HTTPS_LOCALHOST_8443.toString())); + Assertions.assertTrue(body.getId().startsWith(HTTPS_DEFAULT_LOWKEY_VAULT_80.toString())); Assertions.assertTrue(body.getId().contains(name)); } @@ -254,7 +299,7 @@ void testImportCertificateShouldThrowExceptionWhenCalledWithNotMatchingCertTypes //when Assertions.assertThrows(IllegalArgumentException.class, - () -> underTest.importCertificate(name, HTTPS_LOCALHOST_8443, request)); + () -> underTest.importCertificate(name, HTTPS_DEFAULT_LOWKEY_VAULT_80, request)); //then + exception } @@ -265,11 +310,11 @@ void testVersionsShouldReturnAPageOfVersionsWhenTheCertificateExists() { final CertificateImportRequest request = getCreateImportRequest("/cert/ec.pem", CertContentType.PEM); final String name = CERT_NAME_1 + "-versions"; final KeyVaultCertificateModel imported = Objects - .requireNonNull(underTest.importCertificate(name, HTTPS_LOCALHOST_8443, request).getBody()); + .requireNonNull(underTest.importCertificate(name, HTTPS_DEFAULT_LOWKEY_VAULT_80, request).getBody()); //when final ResponseEntity> actual = underTest - .versions(name, HTTPS_LOCALHOST_8443, 1, 0); + .versions(name, HTTPS_DEFAULT_LOWKEY_VAULT_80, 1, 0); //then Assertions.assertEquals(OK, actual.getStatusCode()); @@ -322,29 +367,163 @@ void testGetPolicyShouldReturnModelWhenCalledWithValidData() { //given final CreateCertificateRequest request = getCreateCertificateRequest(); final String certificateName = CERT_NAME_2 + "policy"; - underTest.create(certificateName, HTTPS_LOCALHOST_8443, request); - final Deque versions = vaultService.findByUri(HTTPS_LOCALHOST_8443) + underTest.create(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + final Deque versions = vaultService.findByUri(HTTPS_DEFAULT_LOWKEY_VAULT_80) .certificateVaultFake() .getEntities() - .getVersions(new CertificateEntityId(HTTPS_LOCALHOST_8443, certificateName)); + .getVersions(new CertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, certificateName)); //when - final ResponseEntity actual = underTest.getPolicy(certificateName, HTTPS_LOCALHOST_8443); + final ResponseEntity actual = underTest.getPolicy(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80); //then Assertions.assertEquals(OK, actual.getStatusCode()); final CertificatePolicyModel body = actual.getBody(); Assertions.assertNotNull(body); - final URI id = new VersionedCertificateEntityId(HTTPS_LOCALHOST_8443, certificateName, versions.getFirst()) - .asPolicyUri(HTTPS_LOCALHOST_8443); + final URI id = new VersionedCertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, certificateName, versions.getFirst()) + .asPolicyUri(HTTPS_DEFAULT_LOWKEY_VAULT_80); 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.assertTrue(body.getAttributes().getRecoveryLevel().isPurgeable()); + Assertions.assertNull(body.getAttributes().getRecoveryLevel()); Assertions.assertEquals(id.toString(), body.getId()); } + @Test + void testDeleteShouldReturnDeleteModelWhenCalledWithValidData() { + //given + final CreateCertificateRequest request = getCreateCertificateRequest(); + final String certificateName = CERT_NAME_2 + "-delete"; + underTest.create(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + final CertificateVaultFake certificateVaultFake = vaultService.findByUri(HTTPS_DEFAULT_LOWKEY_VAULT_80) + .certificateVaultFake(); + final Deque versions = certificateVaultFake + .getEntities() + .getVersions(new CertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, certificateName)); + + //when + final ResponseEntity actual = underTest.delete(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final DeletedKeyVaultCertificateModel body = actual.getBody(); + final VersionedCertificateEntityId expectedId = + new VersionedCertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, 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, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + final CertificateVaultFake certificateVaultFake = vaultService.findByUri(HTTPS_DEFAULT_LOWKEY_VAULT_80) + .certificateVaultFake(); + certificateVaultFake.delete(new CertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, certificateName)); + + //when + final ResponseEntity actual = underTest.purgeDeleted(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80); + + //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, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + final CertificateVaultFake certificateVaultFake = vaultService.findByUri(HTTPS_DEFAULT_LOWKEY_VAULT_80) + .certificateVaultFake(); + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, certificateName); + final Deque versions = certificateVaultFake + .getEntities() + .getVersions(entityId); + certificateVaultFake.delete(entityId); + + //when + final ResponseEntity actual = underTest + .recoverDeletedCertificate(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final KeyVaultCertificateModel body = actual.getBody(); + final VersionedCertificateEntityId expectedId = + new VersionedCertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, 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, HTTPS_DEFAULT_LOWKEY_VAULT_80, request); + final CertificateVaultFake certificateVaultFake = vaultService.findByUri(HTTPS_DEFAULT_LOWKEY_VAULT_80) + .certificateVaultFake(); + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, certificateName); + final Deque versions = certificateVaultFake + .getEntities() + .getVersions(entityId); + certificateVaultFake.delete(entityId); + + //when + final ResponseEntity actual = underTest + .getDeletedCertificate(certificateName, HTTPS_DEFAULT_LOWKEY_VAULT_80); + + //then + Assertions.assertEquals(OK, actual.getStatusCode()); + final DeletedKeyVaultCertificateModel body = actual.getBody(); + final VersionedCertificateEntityId expectedId = + new VersionedCertificateEntityId(HTTPS_DEFAULT_LOWKEY_VAULT_80, 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, HTTPS_LOOP_BACK_IP_80, request); + final CertificateVaultFake certificateVaultFake = vaultService.findByUri(HTTPS_LOOP_BACK_IP_80) + .certificateVaultFake(); + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_LOOP_BACK_IP_80, certificateName); + final Deque versions = certificateVaultFake + .getEntities() + .getVersions(entityId); + certificateVaultFake.delete(entityId); + + //when + final ResponseEntity> actual = + underTest.listDeletedCertificates(HTTPS_LOOP_BACK_IP_80, 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(HTTPS_LOOP_BACK_IP_80).toString(), itemModel.getCertificateId()); + assertIsDeletedModel(itemModel, entityId); + } + private CertificateImportRequest getCreateImportRequest(final String resource, final CertContentType type) { final CertificateImportRequest request = new CertificateImportRequest(); request.setCertificate(ResourceUtils.loadResourceAsByteArray(resource)); @@ -389,11 +568,34 @@ private CreateCertificateRequest getCreateCertificateRequest() { return request; } - private static void assertPendingCreateResponseIsAsExpected(final KeyVaultPendingCertificateModel body) { + private void assertPendingCreateResponseIsAsExpected(final KeyVaultPendingCertificateModel body) { Assertions.assertNotNull(body); Assertions.assertNotNull(body.getCsr()); Assertions.assertNotNull(body.getRequestId()); Assertions.assertNull(body.getStatusDetails()); Assertions.assertEquals("completed", body.getStatus()); } + + private void assertExpectedCertificateModel( + final CreateCertificateRequest request, + final VersionedCertificateEntityId expectedId, + final KeyVaultCertificateModel body) { + Assertions.assertNotNull(body); + final URI id = expectedId.asUri(HTTPS_DEFAULT_LOWKEY_VAULT_80); + 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()); + } } diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java index 1bf3a00d..88c20ea7 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java @@ -506,7 +506,7 @@ void testGetDeletedKeyShouldReturnEntryWhenKeyIsFound( .thenReturn(DELETED_RESPONSE); //when - final ResponseEntity actual = underTest.getDeletedKey(KEY_NAME_1, HTTPS_LOCALHOST_8443); + final ResponseEntity actual = underTest.getDeletedKey(KEY_NAME_1, HTTPS_LOCALHOST_8443); //then Assertions.assertNotNull(actual); @@ -676,7 +676,7 @@ void testGetDeletedKeysShouldReturnEntryWhenKeyIsFound( .thenReturn(keyItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedKeys(HTTPS_LOCALHOST_8443, 1, 0); //then @@ -764,7 +764,7 @@ void testGetDeletedKeysShouldReturnNextLinkWhenNotOnLastPage( .thenReturn(keyItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedKeys(HTTPS_LOCALHOST_8443, 1, 0); //then diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretControllerTest.java index 250c009d..ef62628f 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretControllerTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretControllerTest.java @@ -331,7 +331,7 @@ void testDeleteSecretShouldReturnEntryWhenSecretIsFound( .thenReturn(DELETED_RESPONSE); //when - final ResponseEntity actual = underTest.delete(SECRET_NAME_1, HTTPS_LOCALHOST_8443); + final ResponseEntity actual = underTest.delete(SECRET_NAME_1, HTTPS_LOCALHOST_8443); //then Assertions.assertNotNull(actual); @@ -683,7 +683,7 @@ void testGetDeletedSecretsShouldReturnEntryWhenSecretIsFound( .thenReturn(secretItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedSecrets(HTTPS_LOCALHOST_8443, 1, 0); //then @@ -728,7 +728,7 @@ void testGetDeletedSecretsShouldReturnNextLinkWhenNotOnLastPage( .thenReturn(secretItemModel); //when - final ResponseEntity> actual = + final ResponseEntity> actual = underTest.listDeletedSecrets(HTTPS_LOCALHOST_8443, 1, 0); //then diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImplTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImplTest.java index e63f3fd4..81496d08 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImplTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/certificate/impl/CertificateVaultFakeImplTest.java @@ -7,7 +7,10 @@ import com.github.nagyesta.lowkeyvault.model.v7_3.certificate.CertificatePolicyModel; import com.github.nagyesta.lowkeyvault.service.certificate.CertificateVaultFake; import com.github.nagyesta.lowkeyvault.service.certificate.ReadOnlyKeyVaultCertificateEntity; +import com.github.nagyesta.lowkeyvault.service.certificate.id.CertificateEntityId; import com.github.nagyesta.lowkeyvault.service.certificate.id.VersionedCertificateEntityId; +import com.github.nagyesta.lowkeyvault.service.key.impl.KeyVaultFakeImpl; +import com.github.nagyesta.lowkeyvault.service.secret.impl.SecretVaultFakeImpl; import com.github.nagyesta.lowkeyvault.service.vault.VaultFake; import com.github.nagyesta.lowkeyvault.service.vault.impl.VaultFakeImpl; import org.junit.jupiter.api.Assertions; @@ -24,6 +27,8 @@ import static com.github.nagyesta.lowkeyvault.TestConstantsCertificateKeys.EMPTY_PASSWORD; import static com.github.nagyesta.lowkeyvault.TestConstantsCertificates.*; import static com.github.nagyesta.lowkeyvault.TestConstantsUri.HTTPS_LOCALHOST_8443; +import static com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE; +import static com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel.RECOVERABLE_AND_PURGEABLE; import static com.github.nagyesta.lowkeyvault.service.certificate.impl.CertAuthorityType.UNKNOWN; import static com.github.nagyesta.lowkeyvault.service.certificate.impl.KeyVaultCertificateEntityTest.VALIDITY_MONTHS; import static org.mockito.Mockito.*; @@ -168,4 +173,147 @@ void testImportCertificateVersionShouldThrowExceptionWhenCalledWithNull(final St //then + exception } + + @Test + void testDeleteShouldPropagateDeletionToTheManagedKeyAndSecretWithTheSameNameWhenCalledWithValidInput() { + //given + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_1); + final VaultFake vault = mock(VaultFake.class); + when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + final CertificateVaultFakeImpl underTest = prepareWithCertificateCreated(vault); + + //when + underTest.delete(entityId); + + //then + verify(vault, atLeastOnce()).baseUri(); + verify(vault, atLeastOnce()).secretVaultFake(); + verify(vault, atLeastOnce()).keyVaultFake(); + Assertions.assertTrue(vault.certificateVaultFake().getEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.certificateVaultFake().getDeletedEntities().containsName(CERT_NAME_1)); + Assertions.assertTrue(vault.keyVaultFake().getEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.keyVaultFake().getDeletedEntities().containsName(CERT_NAME_1)); + Assertions.assertTrue(vault.secretVaultFake().getEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.secretVaultFake().getDeletedEntities().containsName(CERT_NAME_1)); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void testDeleteShouldShouldThrowExceptionWhenCalledWithNull() { + //given + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_1); + final VaultFake vault = mock(VaultFake.class); + when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + final CertificateVaultFakeImpl underTest = prepareWithCertificateCreated(vault); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.delete(null)); + + //then + exception + } + + @Test + void testPurgeShouldPropagateDeletionToTheManagedKeyAndSecretWithTheSameNameWhenCalledWithValidInput() { + //given + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_1); + final VaultFake vault = mock(VaultFake.class); + when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + final CertificateVaultFakeImpl underTest = prepareWithCertificateCreated(vault); + underTest.delete(entityId); + + //when + underTest.purge(entityId); + + //then + verify(vault, atLeastOnce()).baseUri(); + verify(vault, atLeastOnce()).secretVaultFake(); + verify(vault, atLeastOnce()).keyVaultFake(); + Assertions.assertTrue(vault.certificateVaultFake().getEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.certificateVaultFake().getDeletedEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.keyVaultFake().getEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.keyVaultFake().getDeletedEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.secretVaultFake().getEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.secretVaultFake().getDeletedEntities().listLatestEntities().isEmpty()); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void testPurgeShouldShouldThrowExceptionWhenCalledWithNull() { + //given + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_1); + final VaultFake vault = mock(VaultFake.class); + when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + final CertificateVaultFakeImpl underTest = prepareWithCertificateCreated(vault); + underTest.delete(entityId); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.purge(null)); + + //then + exception + } + + @Test + void testRecoverShouldPropagateDeletionToTheManagedKeyAndSecretWithTheSameNameWhenCalledWithValidInput() { + //given + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_1); + final VaultFake vault = mock(VaultFake.class); + when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + final CertificateVaultFakeImpl underTest = prepareWithCertificateCreated(vault); + underTest.delete(entityId); + + //when + underTest.recover(entityId); + + //then + verify(vault, atLeastOnce()).baseUri(); + verify(vault, atLeastOnce()).secretVaultFake(); + verify(vault, atLeastOnce()).keyVaultFake(); + Assertions.assertTrue(vault.certificateVaultFake().getEntities().containsName(CERT_NAME_1)); + Assertions.assertTrue(vault.certificateVaultFake().getDeletedEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.keyVaultFake().getEntities().containsName(CERT_NAME_1)); + Assertions.assertTrue(vault.keyVaultFake().getDeletedEntities().listLatestEntities().isEmpty()); + Assertions.assertTrue(vault.secretVaultFake().getEntities().containsName(CERT_NAME_1)); + Assertions.assertTrue(vault.secretVaultFake().getDeletedEntities().listLatestEntities().isEmpty()); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void testRecoverShouldShouldThrowExceptionWhenCalledWithNull() { + //given + final CertificateEntityId entityId = new CertificateEntityId(HTTPS_LOCALHOST_8443, CERT_NAME_1); + final VaultFake vault = mock(VaultFake.class); + when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST_8443); + final CertificateVaultFakeImpl underTest = prepareWithCertificateCreated(vault); + underTest.delete(entityId); + + //when + Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.recover(null)); + + //then + exception + } + + private CertificateVaultFakeImpl prepareWithCertificateCreated(final VaultFake vault) { + final CertificateCreationInput cert = CertificateCreationInput.builder() + .subject("CN=" + LOCALHOST) + .name(CERT_NAME_1) + .keyType(KeyType.EC) + .keyCurveName(KeyCurveName.P_256) + .validityStart(NOW) + .validityMonths(VALIDITY_MONTHS) + .contentType(CertContentType.PEM) + .build(); + when(vault.getRecoveryLevel()).thenReturn(RECOVERABLE_AND_PURGEABLE); + when(vault.getRecoverableDays()).thenReturn(MAX_RECOVERABLE_DAYS_INCLUSIVE); + final KeyVaultFakeImpl keyVaultFake = new KeyVaultFakeImpl( + vault, RECOVERABLE_AND_PURGEABLE, MAX_RECOVERABLE_DAYS_INCLUSIVE); + when(vault.keyVaultFake()).thenReturn(keyVaultFake); + final SecretVaultFakeImpl secretVaultFake = new SecretVaultFakeImpl( + vault, RECOVERABLE_AND_PURGEABLE, MAX_RECOVERABLE_DAYS_INCLUSIVE); + when(vault.secretVaultFake()).thenReturn(secretVaultFake); + final CertificateVaultFakeImpl underTest = new CertificateVaultFakeImpl( + vault, RECOVERABLE_AND_PURGEABLE, MAX_RECOVERABLE_DAYS_INCLUSIVE); + when(vault.certificateVaultFake()).thenReturn(underTest); + underTest.createCertificateVersion(CERT_NAME_1, cert); + return underTest; + } } diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/context/CommonTestContext.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/context/CommonTestContext.java index 766adbc2..ad24f725 100644 --- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/context/CommonTestContext.java +++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/context/CommonTestContext.java @@ -94,8 +94,8 @@ public List getListedIds() { return listedIds; } - public void setListedIds(final List listedKeyNames) { - this.listedIds = listedKeyNames; + public void setListedIds(final List listedIds) { + this.listedIds = listedIds; } public List getDeletedRecoveryIds() { diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificateStepDefAssertion.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificateStepDefAssertion.java index 074457cb..e4851eac 100644 --- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificateStepDefAssertion.java +++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificateStepDefAssertion.java @@ -1,6 +1,8 @@ package com.github.nagyesta.lowkeyvault.steps; +import com.azure.security.keyvault.certificates.CertificateClient; import com.azure.security.keyvault.certificates.models.CertificateContentType; +import com.azure.security.keyvault.certificates.models.DeletedCertificate; import com.azure.security.keyvault.keys.models.KeyVaultKey; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; import com.github.nagyesta.lowkeyvault.KeyGenUtil; @@ -110,6 +112,12 @@ public void theListShouldContainCountItems(final int count) { assertEquals(count, ids.size()); } + @Then("the deleted list should contain {int} items") + public void theDeletedListShouldContainCountItems(final int count) { + final List ids = context.getDeletedRecoveryIds(); + assertEquals(count, ids.size()); + } + @And("the downloaded certificate policy has {int} months validity") public void theDownloadedCertificatePolicyHasMonthsValidity(final int validity) { final Integer actual = context.getDownloadedPolicy().getValidityInMonths(); @@ -122,6 +130,14 @@ public void theDownloadedCertificatePolicyHasSubjectAsSubject(final String subje assertEquals(subject, actual); } + @And("the deleted certificate policy named {name} is downloaded") + public void theDeletedCertificatePolicyNamedMultiImportIsDownloaded(final String name) { + final CertificateClient client = context.getClient(context.getCertificateServiceVersion()); + final DeletedCertificate deletedCertificate = client.getDeletedCertificate(name); + context.setLastDeleted(deletedCertificate); + assertNotNull(deletedCertificate); + } + private X509Certificate getX509Certificate(final CertificateContentType contentType, final String value) throws Exception { final X509Certificate certificate; if (contentType == CertificateContentType.PEM) { diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificatesStepDefs.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificatesStepDefs.java index fbfb48df..e4f90b77 100644 --- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificatesStepDefs.java +++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CertificatesStepDefs.java @@ -175,4 +175,40 @@ public void theCertificateVersionsAreListed() { .stream().collect(Collectors.toList()); context.setListedIds(actual); } + + @And("{int} certificates with {name} prefix are deleted") + public void certificatesWithMultiImportPrefixAreDeleted(final int count, final String prefix) { + final CertificateClient client = context.getClient(context.getCertificateServiceVersion()); + IntStream.range(0, count).forEach(i -> { + final DeletedCertificate deletedCertificate = client.beginDeleteCertificate(prefix + i) + .waitForCompletion().getValue(); + context.setLastDeleted(deletedCertificate); + }); + } + + @And("{int} certificates with {name} prefix are purged") + public void certificatesWithMultiImportPrefixArePurged(final int count, final String prefix) { + final CertificateClient client = context.getClient(context.getCertificateServiceVersion()); + IntStream.range(0, count).forEach(i -> { + client.purgeDeletedCertificate(prefix + i); + }); + } + + @And("{int} certificates with {name} prefix are recovered") + public void certificatesWithMultiImportPrefixAreRecovered(final int count, final String prefix) { + final CertificateClient client = context.getClient(context.getCertificateServiceVersion()); + IntStream.range(0, count).forEach(i -> { + client.beginRecoverDeletedCertificate(prefix + i).waitForCompletion(); + }); + } + + @When("the deleted certificates are listed") + public void theDeletedCertificatesAreListed() { + final List recoveryIds = context.getClient(context.getCertificateServiceVersion()) + .listDeletedCertificates() + .stream() + .map(DeletedCertificate::getRecoveryId) + .collect(Collectors.toList()); + context.setDeletedRecoveryIds(recoveryIds); + } } 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 new file mode 100644 index 00000000..c8ba7c7a --- /dev/null +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/DeleteCertificates.feature @@ -0,0 +1,95 @@ +Feature: Certificate delete/purge/recover + + @Certificate @CertificateImport @CertificateDelete @RSA @CreateVault + Scenario Outline: RSA_CERT_DELETE_01 Single versions of multiple RSA certificates imported and deleted then get as deleted + Given certificate API version is used + And a vault is created with name cert-del-rsa- + And a certificate client is created with the vault named cert-del-rsa- + And 1 certificates are imported from the resource named using - as password + When 1 certificates with multi-import- prefix are deleted + Then the deleted certificate policy named multi-import-0 is downloaded + + Examples: + | api | index | fileName | + | 7.3 | 1 | 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 + Given certificate API version is used + And a vault is created with name cert-del-ec- + And a certificate client is created with the vault named cert-del-ec- + And 1 certificates are imported from the resource named using - as password + When 1 certificates with multi-import- prefix are deleted + Then the deleted certificate policy named multi-import-0 is downloaded + + Examples: + | api | index | fileName | + | 7.3 | 1 | 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 + Given certificate API version is used + And a vault is created with name cert-purge-rsa- + And a certificate client is created with the vault named cert-purge-rsa- + And 1 certificates are imported from the resource named using - as password + When 1 certificates with multi-import- prefix are deleted + And 1 certificates with multi-import- prefix are purged + Then the deleted certificates are listed + And the deleted list should contain 0 items + And the certificates are listed + And the list should contain 0 items + + Examples: + | api | index | fileName | + | 7.3 | 1 | 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 + Given certificate API version is used + And a vault is created with name cert-purge-ec- + And a certificate client is created with the vault named cert-purge-ec- + And 1 certificates are imported from the resource named using - as password + When 1 certificates with multi-import- prefix are deleted + And 1 certificates with multi-import- prefix are purged + Then the deleted certificates are listed + And the deleted list should contain 0 items + And the certificates are listed + And the list should contain 0 items + + Examples: + | api | index | fileName | + | 7.3 | 1 | 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 + Given certificate API version is used + And a vault is created with name cert-recover-rsa- + And a certificate client is created with the vault named cert-recover-rsa- + And 1 certificates are imported from the resource named using - as password + When 1 certificates with multi-import- prefix are deleted + And 1 certificates with multi-import- prefix are recovered + Then the deleted certificates are listed + And the deleted list should contain 0 items + And the certificates are listed + And the list should contain 1 items + + Examples: + | api | index | fileName | + | 7.3 | 1 | 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 + Given certificate API version is used + And a vault is created with name cert-recover-ec- + And a certificate client is created with the vault named cert-recover-ec- + And 1 certificates are imported from the resource named using - as password + When 1 certificates with multi-import- prefix are deleted + And 1 certificates with multi-import- prefix are recovered + Then the deleted certificates are listed + And the deleted list should contain 0 items + And the certificates are listed + And the list should contain 1 items + + Examples: + | api | index | fileName | + | 7.3 | 1 | 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 new file mode 100644 index 00000000..8c013d03 --- /dev/null +++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/certificates/ListDeletedCertificates.feature @@ -0,0 +1,31 @@ +Feature: Certificate list deleted + + @Certificate @CertificateImport @CertificateListDeleted @RSA @CreateVault + Scenario Outline: RSA_CERT_LIST_DELETED_01 Single versions of multiple RSA certificates imported and deleted then listed as deleted + Given certificate API version is used + And a vault is created with name cert-list-deleted-rsa- + And a certificate client is created with the vault named cert-list-deleted-rsa- + And certificates are imported from the resource named using - as password + And certificates with multi-import- prefix are deleted + When the deleted certificates are listed + Then the deleted list should contain items + + Examples: + | api | index | fileName | count | + | 7.3 | 1 | rsa-localhost.pem | 1 | + | 7.3 | 2 | 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 + Given certificate API version is used + And a vault is created with name cert-list-deleted-ec- + And a certificate client is created with the vault named cert-list-deleted-ec- + And certificates are imported from the resource named using - as password + And certificates with multi-import- prefix are deleted + When the deleted certificates are listed + Then the deleted list should contain items + + Examples: + | api | index | fileName | count | + | 7.3 | 1 | ec521-ec-localhost.pem | 1 | + | 7.3 | 2 | ec521-ec-localhost.pem | 5 |