Extend VaultVersionedKeyValueOperations with typed read.
We now allow type-safe reads of versioned secrets through VaultVersionedKeyValueOperations. Closes gh-247.
This commit is contained in:
@@ -134,20 +134,22 @@ public abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSu
|
||||
|
||||
JsonNode jsonNode = response.getRequiredData().at("/data");
|
||||
|
||||
try {
|
||||
|
||||
I data = mapper.reader().readValue(jsonNode.traverse(), deserializeAs);
|
||||
|
||||
return mappingFunction.apply(response, data);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new VaultException("Cannot deserialize response", e);
|
||||
}
|
||||
return mappingFunction.apply(response, deserialize(jsonNode, deserializeAs));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
<T> T deserialize(JsonNode jsonNode, Class<T> type) {
|
||||
|
||||
try {
|
||||
return mapper.reader().readValue(jsonNode.traverse(), type);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new VaultException("Cannot deserialize response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
<T> T doRead(String path, ParameterizedTypeReference<T> typeReference) {
|
||||
|
||||
|
||||
@@ -56,7 +56,32 @@ public interface VaultVersionedKeyValueOperations extends VaultKeyValueOperation
|
||||
* @return the data. May be {@literal null} if the path does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
Versioned<Map<String, Object>> get(String path, Version version);
|
||||
<T> Versioned<T> get(String path, Version version);
|
||||
|
||||
/**
|
||||
* Read the most recent secret at {@code path} and deserialize the secret to the given
|
||||
* {@link Class responseType}.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @param responseType must not be {@literal null}.
|
||||
* @return the data. May be {@literal null} if the path does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
default <T> Versioned<T> get(String path, Class<T> responseType) {
|
||||
return get(path, Version.unversioned(), responseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the requested {@link Version} of the secret at {@code path} and deserialize
|
||||
* the secret to the given {@link Class responseType}.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @param version must not be {@literal null}.
|
||||
* @param responseType must not be {@literal null}.
|
||||
* @return the data. May be {@literal null} if the path does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
<T> Versioned<T> get(String path, Version version, Class<T> responseType);
|
||||
|
||||
/**
|
||||
* Write the {@link Versioned versioned secret} at {@code path}. {@code body} may be
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -33,6 +35,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.client.VaultResponses;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultResponseSupport;
|
||||
import org.springframework.vault.support.Versioned;
|
||||
import org.springframework.vault.support.Versioned.Metadata;
|
||||
import org.springframework.vault.support.Versioned.Version;
|
||||
@@ -56,34 +59,50 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValueAccessor implem
|
||||
* @param path must not be empty or {@literal null}.
|
||||
*/
|
||||
public VaultVersionedKeyValueTemplate(VaultOperations vaultOperations, String path) {
|
||||
|
||||
super(vaultOperations, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Versioned<Map<String, Object>> get(String path, Version version) {
|
||||
|
||||
Assert.hasText(path, "Path must not be empty");
|
||||
Assert.notNull(version, "Version must not be null");
|
||||
|
||||
String secretPath = version.isVersioned() ? String.format(
|
||||
"%s?version=%d",
|
||||
createDataPath(path), version.getVersion())
|
||||
: createDataPath(path);
|
||||
return (Versioned) doRead(path, version, Map.class);
|
||||
}
|
||||
|
||||
VaultResponse response = doWithSession((restOperations, httpHeaders) -> {
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> Versioned<T> get(String path, Version version, Class<T> responseType) {
|
||||
|
||||
Assert.hasText(path, "Path must not be empty");
|
||||
Assert.notNull(version, "Version must not be null");
|
||||
Assert.notNull(responseType, "Response type must not be null");
|
||||
|
||||
return doRead(path, version, responseType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> Versioned<T> doRead(String path, Version version, Class<T> responseType) {
|
||||
|
||||
String secretPath = version.isVersioned() ? String.format("%s?version=%d",
|
||||
createDataPath(path), version.getVersion()) : createDataPath(path);
|
||||
|
||||
VersionedResponse response = doWithSession((restOperations, httpHeaders) -> {
|
||||
|
||||
try {
|
||||
return restOperations.exchange(secretPath, HttpMethod.GET,
|
||||
new HttpEntity<>(httpHeaders), VaultResponse.class).getBody();
|
||||
new HttpEntity<>(httpHeaders), VersionedResponse.class).getBody();
|
||||
}
|
||||
catch (HttpStatusCodeException e) {
|
||||
|
||||
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
if (e.getResponseBodyAsString().contains("deletion_time")) {
|
||||
|
||||
return VaultResponses.unwrap(e.getResponseBodyAsString(),
|
||||
VaultResponse.class);
|
||||
VersionedResponse.class);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -97,10 +116,12 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValueAccessor implem
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> responseData = response.getRequiredData();
|
||||
Metadata metadata = getMetadata((Map) responseData.get("metadata"));
|
||||
VaultResponseSupport<JsonNode> data = response.getRequiredData();
|
||||
Metadata metadata = getMetadata(data.getMetadata());
|
||||
|
||||
return Versioned.create((Map<String, Object>) responseData.get("data"), metadata);
|
||||
T body = deserialize(data.getRequiredData(), responseType);
|
||||
|
||||
return Versioned.create(body, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,4 +228,8 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValueAccessor implem
|
||||
doWrite(createBackendPath("destroy", path),
|
||||
Collections.singletonMap("versions", versions));
|
||||
}
|
||||
|
||||
private static class VersionedResponse extends
|
||||
VaultResponseSupport<VaultResponseSupport<JsonNode>> {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class VaultResponseSupport<T> {
|
||||
private T data;
|
||||
|
||||
@Nullable
|
||||
private Map<String, String> metadata;
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
@JsonProperty("wrap_info")
|
||||
@Nullable
|
||||
@@ -123,7 +123,7 @@ public class VaultResponseSupport<T> {
|
||||
* @return request metadata.
|
||||
*/
|
||||
@Nullable
|
||||
public Map<String, String> getMetadata() {
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public class VaultResponseSupport<T> {
|
||||
*
|
||||
* @param metadata request metadata.
|
||||
*/
|
||||
public void setMetadata(@Nullable Map<String, String> metadata) {
|
||||
public void setMetadata(@Nullable Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.Data;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -76,6 +77,21 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
|
||||
assertThat(metadata.getDeletedAt()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateComplexVersionedSecret() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Walter");
|
||||
person.setLastname("White");
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
versionedOperations.put(key, Versioned.create(person));
|
||||
|
||||
Versioned<Person> versioned = versionedOperations.get(key, Person.class);
|
||||
|
||||
assertThat(versioned.getData()).isEqualTo(person);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateVersionedWithCAS() {
|
||||
|
||||
@@ -209,4 +225,11 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
|
||||
assertThat(versioned.getMetadata().isDestroyed()).isTrue();
|
||||
assertThat(versioned.getMetadata().getDeletedAt()).isNull();
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Person {
|
||||
|
||||
String firstname;
|
||||
String lastname;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user