Skip to content

Commit

Permalink
Bug: Management endpoints require Content-Type header even if no requ…
Browse files Browse the repository at this point in the history
…est body needed (#1042)

- Removes Content-Type header requirement from GET and DELETE endpoints
- Updates tests

Updates #1040
{patch}

Signed-off-by: Esta Nagy <nagyesta@gmail.com>
  • Loading branch information
nagyesta authored Jun 28, 2024
1 parent cf905c8 commit 628fae8
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<VaultBackupListModel> export() {
log.info("Received request to export active vaults.");
final List<VaultBackupModel> backupModels = vaultImportExportExecutor.backupVaultList(vaultService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<VaultModel> 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());
Expand All @@ -72,9 +70,8 @@ public ResponseEntity<VaultModel> 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<List<VaultModel>> listVaults() {
log.info("Received request to list vaults.");
final List<VaultModel> vaultFake = vaultService.list().stream()
Expand All @@ -91,9 +88,8 @@ public ResponseEntity<List<VaultModel>> 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<List<VaultModel>> listDeletedVaults() {
log.info("Received request to list deleted vaults.");
final List<VaultModel> vaultFake = vaultService.listDeleted().stream()
Expand All @@ -117,9 +113,8 @@ public ResponseEntity<List<VaultModel>> 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<Boolean> deleteVault(@RequestParam final URI baseUri) {
log.info("Received request to delete vault with uri: {}", baseUri);
return ResponseEntity.ok(vaultService.delete(baseUri));
Expand All @@ -140,7 +135,7 @@ public ResponseEntity<Boolean> 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<VaultModel> recoverVault(@RequestParam final URI baseUri) {
log.info("Received request to recover deleted vault with uri: {}", baseUri);
vaultService.recover(baseUri);
Expand All @@ -162,9 +157,8 @@ public ResponseEntity<VaultModel> 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<Boolean> purgeVault(@RequestParam final URI baseUri) {
log.info("Received request to purge deleted vault with uri: {}", baseUri);
return ResponseEntity.ok(vaultService.purge(baseUri));
Expand All @@ -189,7 +183,7 @@ public ResponseEntity<Boolean> 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<VaultModel> aliasUpdate(@RequestParam final URI baseUri,
@RequestParam(required = false) final URI add,
@RequestParam(required = false) final URI remove) {
Expand All @@ -212,7 +206,8 @@ public ResponseEntity<VaultModel> 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<Void> timeShiftAll(
@RequestParam final int seconds,
@RequestParam(required = false, defaultValue = "false") final boolean regenerateCertificates) {
Expand Down Expand Up @@ -240,7 +235,8 @@ public ResponseEntity<Void> 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<Void> timeShiftSingle(
@RequestParam final URI baseUri, @RequestParam final int seconds,
@RequestParam(required = false, defaultValue = "false") final boolean regenerateCertificates) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
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;
import com.fasterxml.jackson.databind.ObjectWriter;
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;
Expand Down Expand Up @@ -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<VaultModel> 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<VaultModel> 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));
}

Expand All @@ -114,7 +107,7 @@ public VaultModel recover(@NonNull final URI baseUri) {
final Map<String, String> 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));
}

Expand All @@ -136,8 +129,7 @@ public VaultModel removeAlias(@NonNull final URI baseUri, @NonNull final URI ali
public boolean purge(@NonNull final URI baseUri) {
final Map<String, String> 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));
}

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

Expand Down Expand Up @@ -192,7 +183,7 @@ public byte[] compressBackup(@NonNull final String backup) throws IOException {
private VaultModel performAliasUpdate(final Map<String, String> 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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
}
Expand Down

0 comments on commit 628fae8

Please sign in to comment.