From 628fae8eca748b8a04fd291f585c11f53fa26745 Mon Sep 17 00:00:00 2001 From: Esta Nagy Date: Fri, 28 Jun 2024 22:57:50 +0200 Subject: [PATCH] Bug: Management endpoints require Content-Type header even if no request body needed (#1042) - Removes Content-Type header requirement from GET and DELETE endpoints - Updates tests Updates #1040 {patch} Signed-off-by: Esta Nagy --- .../VaultBackupManagementController.java | 10 ++---- .../controller/VaultManagementController.java | 36 +++++++++---------- .../impl/LowkeyVaultManagementClientImpl.java | 29 ++++++--------- .../LowkeyVaultManagementClientImplTest.java | 10 +++--- 4 files changed, 34 insertions(+), 51 deletions(-) diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultBackupManagementController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultBackupManagementController.java index b94399b0..b6158c39 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultBackupManagementController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultBackupManagementController.java @@ -10,7 +10,6 @@ import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -26,9 +25,7 @@ @Slf4j @RestController -@RequestMapping(value = "/management/vault", - consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, - produces = MimeTypeUtils.APPLICATION_JSON_VALUE) +@RequestMapping(value = "/management/vault", produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public class VaultBackupManagementController extends ErrorHandlingAwareController implements InitializingBean { private final VaultImporter vaultImporter; @@ -60,9 +57,8 @@ public void afterPropertiesSet() { content = @Content( mediaType = MimeTypeUtils.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class) - ))}, - requestBody = @RequestBody(content = @Content(mediaType = MimeTypeUtils.APPLICATION_JSON_VALUE))) - @GetMapping(value = {"/export", "/export/"}) + ))}) + @GetMapping(value = {"/export", "/export/"}, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public ResponseEntity export() { log.info("Received request to export active vaults."); final List backupModels = vaultImportExportExecutor.backupVaultList(vaultService); diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultManagementController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultManagementController.java index 628a9c77..9afb81ee 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultManagementController.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/VaultManagementController.java @@ -27,9 +27,7 @@ @Slf4j @RestController -@RequestMapping(value = "/management/vault", - consumes = APPLICATION_JSON_VALUE, - produces = APPLICATION_JSON_VALUE) +@RequestMapping(value = "/management/vault", produces = APPLICATION_JSON_VALUE) public class VaultManagementController extends ErrorHandlingAwareController { private final VaultService vaultService; @@ -56,7 +54,7 @@ public VaultManagementController(@NonNull final VaultService vaultService, content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class)))}, requestBody = @RequestBody( content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = VaultModel.class)))) - @PostMapping(value = {"", "/"}) + @PostMapping(value = {"", "/"}, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public ResponseEntity createVault(@Valid @org.springframework.web.bind.annotation.RequestBody final VaultModel model) { log.info("Received request to create vault with uri: {}, recovery level: {}, recoverable days: {}", model.getBaseUri(), model.getRecoveryLevel(), model.getRecoverableDays()); @@ -72,9 +70,8 @@ public ResponseEntity createVault(@Valid @org.springframework.web.bi content = @Content(mediaType = APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = VaultModel.class)))), @ApiResponse(responseCode = "500", description = "Internal error", - content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class)))}, - requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @GetMapping(value = {"", "/"}) + content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class)))}) + @GetMapping(value = {"", "/"}, produces = APPLICATION_JSON_VALUE) public ResponseEntity> listVaults() { log.info("Received request to list vaults."); final List vaultFake = vaultService.list().stream() @@ -91,9 +88,8 @@ public ResponseEntity> listVaults() { content = @Content(mediaType = APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = VaultModel.class)))), @ApiResponse(responseCode = "500", description = "Internal error", - content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class)))}, - requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @GetMapping(value = {"/deleted", "/deleted/"}) + content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class)))}) + @GetMapping(value = {"/deleted", "/deleted/"}, produces = APPLICATION_JSON_VALUE) public ResponseEntity> listDeletedVaults() { log.info("Received request to list deleted vaults."); final List vaultFake = vaultService.listDeleted().stream() @@ -117,9 +113,8 @@ public ResponseEntity> listDeletedVaults() { content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class)))}, parameters = { @Parameter(name = "baseUri", - example = BASE_URI, description = "The base URI of the vault we want delete.", required = true)}, - requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @DeleteMapping(value = {"", "/"}) + example = BASE_URI, description = "The base URI of the vault we want delete.", required = true)}) + @DeleteMapping(value = {"", "/"}, produces = APPLICATION_JSON_VALUE) public ResponseEntity deleteVault(@RequestParam final URI baseUri) { log.info("Received request to delete vault with uri: {}", baseUri); return ResponseEntity.ok(vaultService.delete(baseUri)); @@ -140,7 +135,7 @@ public ResponseEntity deleteVault(@RequestParam final URI baseUri) { @Parameter(name = "baseUri", example = BASE_URI, description = "The base URI of the vault we want to recover.", required = true)}, requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @PutMapping(value = {"/recover", "/recover/"}) + @PutMapping(value = {"/recover", "/recover/"}, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public ResponseEntity recoverVault(@RequestParam final URI baseUri) { log.info("Received request to recover deleted vault with uri: {}", baseUri); vaultService.recover(baseUri); @@ -162,9 +157,8 @@ public ResponseEntity recoverVault(@RequestParam final URI baseUri) content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorModel.class)))}, parameters = { @Parameter(name = "baseUri", - example = BASE_URI, description = "The base URI of the vault we want to purge.", required = true)}, - requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @DeleteMapping(value = {"/purge", "/purge/"}) + example = BASE_URI, description = "The base URI of the vault we want to purge.", required = true)}) + @DeleteMapping(value = {"/purge", "/purge/"}, produces = APPLICATION_JSON_VALUE) public ResponseEntity purgeVault(@RequestParam final URI baseUri) { log.info("Received request to purge deleted vault with uri: {}", baseUri); return ResponseEntity.ok(vaultService.purge(baseUri)); @@ -189,7 +183,7 @@ public ResponseEntity purgeVault(@RequestParam final URI baseUri) { @Parameter(name = "remove", example = ALIAS2, description = "The base URI we want to remove from the aliases of the vault.")}, requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @PatchMapping(value = {"/alias", "/alias/"}) + @PatchMapping(value = {"/alias", "/alias/"}, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public ResponseEntity aliasUpdate(@RequestParam final URI baseUri, @RequestParam(required = false) final URI add, @RequestParam(required = false) final URI remove) { @@ -212,7 +206,8 @@ public ResponseEntity aliasUpdate(@RequestParam final URI baseUri, @Parameter(name = "regenerateCertificates", example = FALSE, description = "Whether we allow regeneration of certificates to let their validity match the new time-frame.")}, requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @PutMapping(value = {"/time/all", "/time/all/"}, params = {"seconds"}) + @PutMapping(value = {"/time/all", "/time/all/"}, params = {"seconds"}, + consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public ResponseEntity timeShiftAll( @RequestParam final int seconds, @RequestParam(required = false, defaultValue = "false") final boolean regenerateCertificates) { @@ -240,7 +235,8 @@ public ResponseEntity timeShiftAll( @Parameter(name = "regenerateCertificates", example = FALSE, description = "Whether we allow regeneration of certificates to let their validity match the new time-frame.")}, requestBody = @RequestBody(content = @Content(mediaType = APPLICATION_JSON_VALUE))) - @PutMapping(value = {"/time", "/time/" }, params = {"baseUri", "seconds"}) + @PutMapping(value = {"/time", "/time/"}, params = {"baseUri", "seconds"}, + consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public ResponseEntity timeShiftSingle( @RequestParam final URI baseUri, @RequestParam final int seconds, @RequestParam(required = false, defaultValue = "false") final boolean regenerateCertificates) { diff --git a/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImpl.java b/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImpl.java index 4c52151e..ddd62a4d 100644 --- a/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImpl.java +++ b/lowkey-vault-client/src/main/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImpl.java @@ -1,9 +1,6 @@ package com.github.nagyesta.lowkeyvault.http.management.impl; -import com.azure.core.http.HttpClient; -import com.azure.core.http.HttpMethod; -import com.azure.core.http.HttpRequest; -import com.azure.core.http.HttpResponse; +import com.azure.core.http.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -11,7 +8,6 @@ import com.github.nagyesta.lowkeyvault.http.management.*; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; -import org.apache.http.HttpHeaders; import reactor.util.annotation.Nullable; import java.io.ByteArrayInputStream; @@ -81,31 +77,28 @@ public VaultModel createVault(@NonNull final URI baseUri, final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_PATH); final HttpRequest request = new HttpRequest(HttpMethod.POST, uri.toString()) .setBody(body) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + .setHeader(HttpHeaderName.CONTENT_TYPE, APPLICATION_JSON); return sendAndProcess(request, r -> r.getResponseObject(VaultModel.class)); } @Override public List listVaults() { final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_PATH); - final HttpRequest request = new HttpRequest(HttpMethod.GET, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + final HttpRequest request = new HttpRequest(HttpMethod.GET, uri.toString()); return sendAndProcess(request, r -> r.getResponseObject(VAULT_MODEL_LIST_TYPE_REF)); } @Override public List listDeletedVaults() { final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_DELETED_PATH); - final HttpRequest request = new HttpRequest(HttpMethod.GET, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + final HttpRequest request = new HttpRequest(HttpMethod.GET, uri.toString()); return sendAndProcess(request, r -> r.getResponseObject(VAULT_MODEL_LIST_TYPE_REF)); } @Override public boolean delete(@NonNull final URI baseUri) { final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_PATH, Map.of(BASE_URI_QUERY_PARAM, baseUri.toString())); - final HttpRequest request = new HttpRequest(HttpMethod.DELETE, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + final HttpRequest request = new HttpRequest(HttpMethod.DELETE, uri.toString()); return sendAndProcess(request, r -> r.getResponseObject(Boolean.class)); } @@ -114,7 +107,7 @@ public VaultModel recover(@NonNull final URI baseUri) { final Map parameters = Map.of(BASE_URI_QUERY_PARAM, baseUri.toString()); final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_RECOVERY_PATH, parameters); final HttpRequest request = new HttpRequest(HttpMethod.PUT, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + .setHeader(HttpHeaderName.CONTENT_TYPE, APPLICATION_JSON); return sendAndProcess(request, r -> r.getResponseObject(VaultModel.class)); } @@ -136,8 +129,7 @@ public VaultModel removeAlias(@NonNull final URI baseUri, @NonNull final URI ali public boolean purge(@NonNull final URI baseUri) { final Map parameters = Map.of(BASE_URI_QUERY_PARAM, baseUri.toString()); final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_PURGE_PATH, parameters); - final HttpRequest request = new HttpRequest(HttpMethod.DELETE, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + final HttpRequest request = new HttpRequest(HttpMethod.DELETE, uri.toString()); return sendAndProcess(request, r -> r.getResponseObject(Boolean.class)); } @@ -153,15 +145,14 @@ public void timeShift(@NonNull final TimeShiftContext context) { final String path = optionalURI.map(u -> MANAGEMENT_VAULT_TIME_PATH).orElse(MANAGEMENT_VAULT_TIME_ALL_PATH); final URI uri = UriUtil.uriBuilderForPath(vaultUrl, path, parameters); final HttpRequest request = new HttpRequest(HttpMethod.PUT, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + .setHeader(HttpHeaderName.CONTENT_TYPE, APPLICATION_JSON); sendRaw(request); } @Override public String exportActive() { final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_EXPORT_ACTIVE_PATH); - final HttpRequest request = new HttpRequest(HttpMethod.GET, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + final HttpRequest request = new HttpRequest(HttpMethod.GET, uri.toString()); return sendRaw(request).getResponseBodyAsString(); } @@ -192,7 +183,7 @@ public byte[] compressBackup(@NonNull final String backup) throws IOException { private VaultModel performAliasUpdate(final Map parameters) { final URI uri = UriUtil.uriBuilderForPath(vaultUrl, MANAGEMENT_VAULT_ALIAS_PATH, parameters); final HttpRequest request = new HttpRequest(HttpMethod.PATCH, uri.toString()) - .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + .setHeader(HttpHeaderName.CONTENT_TYPE, APPLICATION_JSON); return sendAndProcess(request, r -> r.getResponseObject(VaultModel.class)); } diff --git a/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImplTest.java b/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImplTest.java index 9b521822..897e5b1a 100644 --- a/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImplTest.java +++ b/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/impl/LowkeyVaultManagementClientImplTest.java @@ -223,7 +223,7 @@ void testListVaultsShouldReturnVaultsWhenCalled() throws JsonProcessingException final HttpRequest request = httpRequestArgumentCaptor.getValue(); Assertions.assertEquals("/management/vault", request.getUrl().getPath()); Assertions.assertEquals(HttpMethod.GET, request.getHttpMethod()); - Assertions.assertEquals(APPLICATION_JSON, request.getHeaders().getValue(HttpHeaderName.CONTENT_TYPE)); + Assertions.assertNull(request.getHeaders().get(HttpHeaderName.CONTENT_TYPE)); verify(response).getStatusCode(); verify(response).getBodyAsString(eq(StandardCharsets.UTF_8)); verify(objectReader).forType(eq(VAULT_MODEL_LIST_TYPE_REF)); @@ -250,7 +250,7 @@ void testListDeletedVaultsShouldReturnVaultsWhenCalled() throws JsonProcessingEx final HttpRequest request = httpRequestArgumentCaptor.getValue(); Assertions.assertEquals("/management/vault/deleted", request.getUrl().getPath()); Assertions.assertEquals(HttpMethod.GET, request.getHttpMethod()); - Assertions.assertEquals(APPLICATION_JSON, request.getHeaders().getValue(HttpHeaderName.CONTENT_TYPE)); + Assertions.assertNull(request.getHeaders().get(HttpHeaderName.CONTENT_TYPE)); verify(response).getStatusCode(); verify(response).getBodyAsString(eq(StandardCharsets.UTF_8)); verify(objectReader).forType(eq(VAULT_MODEL_LIST_TYPE_REF)); @@ -276,7 +276,7 @@ void testDeleteShouldReturnBooleanStatusWhenCalled() throws JsonProcessingExcept final HttpRequest request = httpRequestArgumentCaptor.getValue(); Assertions.assertEquals("/management/vault", request.getUrl().getPath()); Assertions.assertEquals(HttpMethod.DELETE, request.getHttpMethod()); - Assertions.assertEquals(APPLICATION_JSON, request.getHeaders().getValue(HttpHeaderName.CONTENT_TYPE)); + Assertions.assertNull(request.getHeaders().get(HttpHeaderName.CONTENT_TYPE)); verify(response).getStatusCode(); verify(response).getBodyAsString(eq(StandardCharsets.UTF_8)); verify(objectReader).forType(eq(Boolean.class)); @@ -471,7 +471,7 @@ void testPurgeShouldReturnPurgeStatusWhenCalled() throws JsonProcessingException final HttpRequest request = httpRequestArgumentCaptor.getValue(); Assertions.assertEquals("/management/vault/purge", request.getUrl().getPath()); Assertions.assertEquals(HttpMethod.DELETE, request.getHttpMethod()); - Assertions.assertEquals(APPLICATION_JSON, request.getHeaders().getValue(HttpHeaderName.CONTENT_TYPE)); + Assertions.assertNull(request.getHeaders().get(HttpHeaderName.CONTENT_TYPE)); verify(response).getStatusCode(); verify(response).getBodyAsString(eq(StandardCharsets.UTF_8)); verify(objectReader).forType(eq(Boolean.class)); @@ -609,7 +609,7 @@ void testExportActiveShouldReturnFullResponseWhenCalledOnRunningServer() { final HttpRequest request = httpRequestArgumentCaptor.getValue(); Assertions.assertEquals("/management/vault/export", request.getUrl().getPath()); Assertions.assertEquals(HttpMethod.GET, request.getHttpMethod()); - Assertions.assertEquals(APPLICATION_JSON, request.getHeaders().getValue(HttpHeaderName.CONTENT_TYPE)); + Assertions.assertNull(request.getHeaders().get(HttpHeaderName.CONTENT_TYPE)); verify(response).getStatusCode(); verify(response).getBodyAsString(eq(StandardCharsets.UTF_8)); }