diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/MetadataResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/MetadataResponse.java new file mode 100644 index 0000000..9f00c07 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/MetadataResponse.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-2018 Stefan Kalscheuer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.stklcode.jvault.connector.exception.InvalidResponseException; +import de.stklcode.jvault.connector.model.response.embedded.SecretMetadata; +import de.stklcode.jvault.connector.model.response.embedded.VersionMetadata; + +import java.io.IOException; +import java.util.Map; + +/** + * Vault response for secret metadata (KV v2). + * + * @author Stefan Kalscheuer + * @since 0.8 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class MetadataResponse extends VaultDataResponse { + + private SecretMetadata metadata; + + @Override + public final void setData(final Map data) throws InvalidResponseException { + ObjectMapper mapper = new ObjectMapper(); + try { + this.metadata = mapper.readValue(mapper.writeValueAsString(data), SecretMetadata.class); + } catch (IOException e) { + throw new InvalidResponseException("Failed deserializing response", e); + } + } + + /** + * Get the actual metadata. + * @return + */ + public SecretMetadata getMetadata() { + return metadata; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java index 8dd9180..b77bbdd 100644 --- a/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java +++ b/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java @@ -19,6 +19,7 @@ package de.stklcode.jvault.connector.model.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.ObjectMapper; import de.stklcode.jvault.connector.exception.InvalidResponseException; +import de.stklcode.jvault.connector.model.response.embedded.VersionMetadata; import java.io.IOException; import java.util.HashMap; @@ -33,10 +34,25 @@ import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) public class SecretResponse extends VaultDataResponse { private Map data; + private VersionMetadata metadata; @Override public final void setData(final Map data) throws InvalidResponseException { - this.data = data; + if (data.size() == 2 + && data.containsKey("data") && data.get("data") instanceof Map + && data.containsKey("metadata") && data.get("metadata") instanceof Map) { + ObjectMapper mapper = new ObjectMapper(); + try { + // This is apparently a KV v2 value. + this.data = (Map) data.get("data"); + this.metadata = mapper.readValue(mapper.writeValueAsString(data.get("metadata")), VersionMetadata.class); + } catch (ClassCastException | IOException e) { + throw new InvalidResponseException("Failed deserializing response", e); + } + } else { + // For KV v1 without metadata just store the data map. + this.data = data; + } } /** @@ -46,11 +62,22 @@ public class SecretResponse extends VaultDataResponse { * @since 0.4.0 */ public final Map getData() { - if (data == null) + if (data == null) { return new HashMap<>(); + } return data; } + /** + * Get secret metadata. This is only available for KV v2 secrets. + * + * @return Metadata of the secret. + * @since 0.8 + */ + public final VersionMetadata getMetadata() { + return metadata; + } + /** * Get a single value for given key. * @@ -59,8 +86,9 @@ public class SecretResponse extends VaultDataResponse { * @since 0.4.0 */ public final Object get(final String key) { - if (data == null) + if (data == null) { return null; + } return getData().get(key); } @@ -74,8 +102,9 @@ public class SecretResponse extends VaultDataResponse { @Deprecated public final String getValue() { Object value = get("value"); - if (value == null) + if (value == null) { return null; + } return value.toString(); } @@ -97,7 +126,7 @@ public class SecretResponse extends VaultDataResponse { /** * Get response parsed as JSON. * - * @param key the key + * @param key the key * @param type Class to parse response * @param Class to parse response * @return Parsed object or {@code null} if absent @@ -107,8 +136,9 @@ public class SecretResponse extends VaultDataResponse { public final T get(final String key, final Class type) throws InvalidResponseException { try { Object rawValue = get(key); - if (rawValue == null) + if (rawValue == null) { return null; + } return new ObjectMapper().readValue(rawValue.toString(), type); } catch (IOException e) { throw new InvalidResponseException("Unable to parse response payload: " + e.getMessage()); diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/SecretMetadata.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/SecretMetadata.java new file mode 100644 index 0000000..f1d2f02 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/SecretMetadata.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016-2018 Stefan Kalscheuer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.stklcode.jvault.connector.model.response.embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Map; + +/** + * Embedded metadata for Key-Value v2 secrets. + * + * @author Stefan Kalscheuer + * @since 0.8 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class SecretMetadata { + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSX"); + + @JsonProperty("created_time") + private String createdTimeString; + + @JsonProperty("current_version") + private Integer currentVersion; + + @JsonProperty("max_version") + private Integer maxVersions; + + @JsonProperty("oldest_version") + private Integer oldestVersion; + + @JsonProperty("updated_time") + private String updatedTime; + + @JsonProperty("versions") + private Map versions; + + /** + * @return Time of secret creation as raw string representation. + */ + public String getCreatedTimeString() { + return createdTimeString; + } + + /** + * @return Time of secret creation. + */ + public ZonedDateTime getCreatedTime() { + if (createdTimeString != null && !createdTimeString.isEmpty()) { + try { + return ZonedDateTime.parse(createdTimeString, TIME_FORMAT); + } catch (DateTimeParseException e) { + // Ignore. + } + } + + return null; + } + + /** + * @return Current version number. + */ + public Integer getCurrentVersion() { + return currentVersion; + } + + /** + * @return Maximum number of versions. + */ + public Integer getMaxVersions() { + return maxVersions; + } + + /** + * @return Oldest available version number. + */ + public Integer getOldestVersion() { + return oldestVersion; + } + + /** + * @return Time of secret update as raw string representation. + */ + public String getUpdatedTimeString() { + return updatedTime; + } + + /** + * @return Time of secret update.. + */ + public ZonedDateTime getUpdatedTime() { + if (updatedTime != null && !updatedTime.isEmpty()) { + try { + return ZonedDateTime.parse(updatedTime, TIME_FORMAT); + } catch (DateTimeParseException e) { + // Ignore. + } + } + + return null; + } + + /** + * @return Version of the entry. + */ + public Map getVersions() { + return versions; + } + +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/VersionMetadata.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/VersionMetadata.java new file mode 100644 index 0000000..2759af1 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/VersionMetadata.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016-2018 Stefan Kalscheuer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.stklcode.jvault.connector.model.response.embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Map; + +/** + * Embedded metadata for a single Key-Value v2 version. + * + * @author Stefan Kalscheuer + * @since 0.8 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class VersionMetadata { + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSX"); + + @JsonProperty("created_time") + private String createdTimeString; + + @JsonProperty("deletion_time") + private String deletionTimeString; + + @JsonProperty("destroyed") + private boolean destroyed; + + @JsonProperty("version") + private Integer version; + + /** + * @return Time of secret creation as raw string representation. + */ + public String getCreatedTimeString() { + return createdTimeString; + } + + /** + * @return Time of secret creation. + */ + public ZonedDateTime getCreatedTime() { + if (createdTimeString != null && !createdTimeString.isEmpty()) { + try { + return ZonedDateTime.parse(createdTimeString, TIME_FORMAT); + } catch (DateTimeParseException e) { + // Ignore. + } + } + + return null; + } + + /** + * @return Time for secret deletion as raw string representation. + */ + public String getDeletionTimeString() { + return deletionTimeString; + } + + /** + * @return Time for secret deletion. + */ + public ZonedDateTime getDeletionTime() { + if (deletionTimeString != null && !deletionTimeString.isEmpty()) { + try { + return ZonedDateTime.parse(deletionTimeString, TIME_FORMAT); + } catch (DateTimeParseException e) { + // Ignore. + } + } + + return null; + } + + /** + * @return Whether the secret is destroyed. + */ + public boolean isDestroyed() { + return destroyed; + } + + /** + * @return Version of the entry. + */ + public Integer getVersion() { + return version; + } + +}