Introduce property mapper to apply request properties.
Closes gh-700
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.vault.core;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* Mapper to apply property values onto a target considering conversion rules and
|
||||
* filtering.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.4
|
||||
*/
|
||||
class PropertyMapper {
|
||||
|
||||
private static final Predicate<?> ALWAYS = (t) -> true;
|
||||
|
||||
private static final PropertyMapper INSTANCE = new PropertyMapper(null, null);
|
||||
|
||||
private final @Nullable PropertyMapper parent;
|
||||
|
||||
private final @Nullable SourceOperator sourceOperator;
|
||||
|
||||
private PropertyMapper(@Nullable PropertyMapper parent, @Nullable SourceOperator sourceOperator) {
|
||||
this.parent = parent;
|
||||
this.sourceOperator = sourceOperator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link PropertyMapper} instance that applies
|
||||
* {@link Source#whenNonNull() whenNonNull} to every source.
|
||||
* @return a new property mapper instance
|
||||
*/
|
||||
public PropertyMapper alwaysApplyingWhenNonNull() {
|
||||
return alwaysApplying(this::whenNonNull);
|
||||
}
|
||||
|
||||
private <T> Source<T> whenNonNull(Source<T> source) {
|
||||
return source.whenNonNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link PropertyMapper} instance that applies the given
|
||||
* {@link SourceOperator} to every source.
|
||||
* @param operator the source operator to apply
|
||||
* @return a new property mapper instance
|
||||
*/
|
||||
public PropertyMapper alwaysApplying(SourceOperator operator) {
|
||||
Assert.notNull(operator, "Operator must not be null");
|
||||
return new PropertyMapper(this, operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link Source} from the specified value supplier that can be used to
|
||||
* perform the mapping.
|
||||
* @param <T> the source type
|
||||
* @param supplier the value supplier
|
||||
* @return a {@link Source} that can be used to complete the mapping
|
||||
* @see #from(Object)
|
||||
*/
|
||||
public <T> Source<T> from(Supplier<T> supplier) {
|
||||
Assert.notNull(supplier, "Supplier must not be null");
|
||||
Source<T> source = getSource(supplier);
|
||||
if (this.sourceOperator != null) {
|
||||
source = this.sourceOperator.apply(source);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link Source} from the specified value that can be used to perform
|
||||
* the mapping.
|
||||
* @param <T> the source type
|
||||
* @param value the value
|
||||
* @return a {@link Source} that can be used to complete the mapping
|
||||
*/
|
||||
public <T> Source<T> from(T value) {
|
||||
return from(() -> value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Source<T> getSource(Supplier<T> supplier) {
|
||||
if (this.parent != null) {
|
||||
return this.parent.from(supplier);
|
||||
}
|
||||
return new Source<>(SingletonSupplier.of(supplier), (Predicate<T>) ALWAYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property mapper.
|
||||
* @return the property mapper
|
||||
*/
|
||||
public static PropertyMapper get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* An operation that can be applied to a {@link Source}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SourceOperator {
|
||||
|
||||
/**
|
||||
* Apply the operation to the given source.
|
||||
* @param <T> the source type
|
||||
* @param source the source to operate on
|
||||
* @return the updated source
|
||||
*/
|
||||
<T> Source<T> apply(Source<T> source);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A source that is in the process of being mapped.
|
||||
*
|
||||
* @param <T> the source type
|
||||
*/
|
||||
public static final class Source<T> {
|
||||
|
||||
private final Supplier<T> supplier;
|
||||
|
||||
private final Predicate<T> predicate;
|
||||
|
||||
private Source(Supplier<T> supplier, Predicate<T> predicate) {
|
||||
Assert.notNull(predicate, "Predicate must not be null");
|
||||
this.supplier = supplier;
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an adapted version of the source with {@link Integer} type.
|
||||
* @param <R> the resulting type
|
||||
* @param adapter an adapter to convert the current value to a number.
|
||||
* @return a new adapted source instance
|
||||
*/
|
||||
public <R extends Number> Source<Integer> asInt(Function<T, R> adapter) {
|
||||
return as(adapter).as(Number::intValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an adapted version of the source changed via the given adapter function.
|
||||
* @param <R> the resulting type
|
||||
* @param adapter the adapter to apply
|
||||
* @return a new adapted source instance
|
||||
*/
|
||||
public <R> Source<R> as(Function<T, R> adapter) {
|
||||
Assert.notNull(adapter, "Adapter must not be null");
|
||||
Supplier<Boolean> test = () -> this.predicate.test(this.supplier.get());
|
||||
Predicate<R> predicate = (t) -> test.get();
|
||||
Supplier<R> supplier = () -> {
|
||||
if (test.get()) {
|
||||
return adapter.apply(this.supplier.get());
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return new Source<>(supplier, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that won't map non-null values or
|
||||
* suppliers that throw a {@link NullPointerException}.
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> whenNonNull() {
|
||||
return new Source<>(new NullPointerExceptionSafeSupplier<>(this.supplier), Objects::nonNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that will only map values that are not
|
||||
* empty.
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> whenNotEmpty() {
|
||||
return when(f -> !ObjectUtils.isEmpty(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that will only map values that are
|
||||
* {@code true}.
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> whenTrue() {
|
||||
return when(Boolean.TRUE::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that will only map values that are
|
||||
* {@code false}.
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> whenFalse() {
|
||||
return when(Boolean.FALSE::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that will only map values that have a
|
||||
* {@code toString()} containing actual text.
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> whenHasText() {
|
||||
return when((value) -> StringUtils.hasText(Objects.toString(value, null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that will only map values equal to the
|
||||
* specified {@code object}.
|
||||
* @param object the object to match
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> whenEqualTo(Object object) {
|
||||
return when(object::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that will only map values that are an
|
||||
* instance of the given type.
|
||||
* @param <R> the target type
|
||||
* @param target the target type to match
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public <R extends T> Source<R> whenInstanceOf(Class<R> target) {
|
||||
return when(target::isInstance).as(target::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that won't map values that match the
|
||||
* given predicate.
|
||||
* @param predicate the predicate used to filter values
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> whenNot(Predicate<T> predicate) {
|
||||
Assert.notNull(predicate, "Predicate must not be null");
|
||||
return when(predicate.negate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered version of the source that won't map values that don't match
|
||||
* the given predicate.
|
||||
* @param predicate the predicate used to filter values
|
||||
* @return a new filtered source instance
|
||||
*/
|
||||
public Source<T> when(Predicate<T> predicate) {
|
||||
Assert.notNull(predicate, "Predicate must not be null");
|
||||
return new Source<>(this.supplier, (this.predicate != null) ? this.predicate.and(predicate) : predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the mapping by passing any non-filtered value to the specified
|
||||
* consumer.
|
||||
* @param consumer the consumer that should accept the value if it's not been
|
||||
* filtered
|
||||
*/
|
||||
public void to(Consumer<T> consumer) {
|
||||
Assert.notNull(consumer, "Consumer must not be null");
|
||||
T value = this.supplier.get();
|
||||
if (this.predicate.test(value)) {
|
||||
consumer.accept(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the mapping by passing any non-filtered value to the specified
|
||||
* consumer.
|
||||
* @param consumer the consumer that should accept the value if it's not been
|
||||
* filtered
|
||||
*/
|
||||
public void to(String key, Map<String, ? super T> consumer) {
|
||||
Assert.notNull(consumer, "Consumer must not be null");
|
||||
T value = this.supplier.get();
|
||||
if (this.predicate.test(value)) {
|
||||
consumer.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the mapping by creating a new instance from the non-filtered value.
|
||||
* @param <R> the resulting type
|
||||
* @param factory the factory used to create the instance
|
||||
* @return the instance
|
||||
* @throws NoSuchElementException if the value has been filtered
|
||||
*/
|
||||
public <R> R toInstance(Function<T, R> factory) {
|
||||
Assert.notNull(factory, "Factory must not be null");
|
||||
T value = this.supplier.get();
|
||||
if (!this.predicate.test(value)) {
|
||||
throw new NoSuchElementException("No value present");
|
||||
}
|
||||
return factory.apply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the mapping by calling the specified method when the value has not
|
||||
* been filtered.
|
||||
* @param runnable the method to call if the value has not been filtered
|
||||
*/
|
||||
public void toCall(Runnable runnable) {
|
||||
Assert.notNull(runnable, "Runnable must not be null");
|
||||
T value = this.supplier.get();
|
||||
if (this.predicate.test(value)) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplier that will catch and ignore any {@link NullPointerException}.
|
||||
*/
|
||||
private static class NullPointerExceptionSafeSupplier<T> implements Supplier<T> {
|
||||
|
||||
private final Supplier<T> supplier;
|
||||
|
||||
NullPointerExceptionSafeSupplier(Supplier<T> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
try {
|
||||
return this.supplier.get();
|
||||
}
|
||||
catch (NullPointerException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -158,41 +158,23 @@ public class VaultPkiTemplate implements VaultPkiOperations {
|
||||
Assert.notNull(certificateRequest, "Certificate request must not be null");
|
||||
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("common_name", certificateRequest.getCommonName());
|
||||
|
||||
if (!certificateRequest.getAltNames().isEmpty()) {
|
||||
request.put("alt_names", StringUtils.collectionToDelimitedString(certificateRequest.getAltNames(), ","));
|
||||
}
|
||||
PropertyMapper mapper = PropertyMapper.get();
|
||||
|
||||
if (!certificateRequest.getIpSubjectAltNames().isEmpty()) {
|
||||
request.put("ip_sans",
|
||||
StringUtils.collectionToDelimitedString(certificateRequest.getIpSubjectAltNames(), ","));
|
||||
}
|
||||
|
||||
if (!certificateRequest.getUriSubjectAltNames().isEmpty()) {
|
||||
request.put("uri_sans",
|
||||
StringUtils.collectionToDelimitedString(certificateRequest.getUriSubjectAltNames(), ","));
|
||||
}
|
||||
|
||||
if (!certificateRequest.getOtherSans().isEmpty()) {
|
||||
request.put("other_sans", StringUtils.collectionToDelimitedString(certificateRequest.getOtherSans(), ","));
|
||||
}
|
||||
|
||||
if (certificateRequest.getTtl() != null) {
|
||||
request.put("ttl", certificateRequest.getTtl().get(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (certificateRequest.isExcludeCommonNameFromSubjectAltNames()) {
|
||||
request.put("exclude_cn_from_sans", true);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(certificateRequest.getFormat())) {
|
||||
request.put("format", certificateRequest.getFormat());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(certificateRequest.getPrivateKeyFormat())) {
|
||||
request.put("private_key_format", certificateRequest.getPrivateKeyFormat());
|
||||
}
|
||||
mapper.from(certificateRequest::getCommonName).to("common_name", request);
|
||||
mapper.from(certificateRequest::getAltNames).whenNotEmpty()
|
||||
.as(i -> StringUtils.collectionToDelimitedString(i, ",")).to("alt_names", request);
|
||||
mapper.from(certificateRequest::getIpSubjectAltNames).whenNotEmpty()
|
||||
.as(i -> StringUtils.collectionToDelimitedString(i, ",")).to("ip_sans", request);
|
||||
mapper.from(certificateRequest::getUriSubjectAltNames).whenNotEmpty()
|
||||
.as(i -> StringUtils.collectionToDelimitedString(i, ",")).to("uri_sans", request);
|
||||
mapper.from(certificateRequest::getOtherSans).whenNotEmpty()
|
||||
.as(i -> StringUtils.collectionToDelimitedString(i, ",")).to("other_sans", request);
|
||||
mapper.from(certificateRequest::getTtl).whenNonNull().as(i -> i.get(ChronoUnit.SECONDS)).to("ttl", request);
|
||||
mapper.from(certificateRequest::isExcludeCommonNameFromSubjectAltNames).whenTrue().to("exclude_cn_from_sans",
|
||||
request);
|
||||
mapper.from(certificateRequest::getFormat).whenHasText().to("format", request);
|
||||
mapper.from(certificateRequest::getPrivateKeyFormat).whenHasText().to("private_key_format", request);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -348,16 +348,12 @@ public class VaultTransitTemplate implements VaultTransitOperations {
|
||||
Assert.hasText(keyName, "Key name must not be empty");
|
||||
Assert.notNull(hmacRequest, "HMAC request must not be null");
|
||||
|
||||
Map<String, Object> request = new LinkedHashMap<>();
|
||||
request.put("input", Base64Utils.encodeToString(hmacRequest.getPlaintext().getPlaintext()));
|
||||
Map<String, Object> request = new LinkedHashMap<>(3);
|
||||
PropertyMapper mapper = PropertyMapper.get();
|
||||
|
||||
if (StringUtils.hasText(hmacRequest.getAlgorithm())) {
|
||||
request.put("algorithm", hmacRequest.getAlgorithm());
|
||||
}
|
||||
|
||||
if (hmacRequest.getKeyVersion() != null) {
|
||||
request.put("key_version ", hmacRequest.getKeyVersion());
|
||||
}
|
||||
mapper.from(hmacRequest.getPlaintext()::getPlaintext).as(Base64Utils::encodeToString).to("input", request);
|
||||
mapper.from(hmacRequest::getAlgorithm).whenHasText().to("algorithm", request);
|
||||
mapper.from(hmacRequest::getKeyVersion).whenNonNull().to("key_version", request);
|
||||
|
||||
String hmac = (String) this.vaultOperations.write(String.format("%s/hmac/%s", this.path, keyName), request)
|
||||
.getRequiredData().get("hmac");
|
||||
@@ -382,16 +378,12 @@ public class VaultTransitTemplate implements VaultTransitOperations {
|
||||
Assert.hasText(keyName, "Key name must not be empty");
|
||||
Assert.notNull(signRequest, "Sign request must not be null");
|
||||
|
||||
Map<String, Object> request = new LinkedHashMap<>();
|
||||
request.put("input", Base64Utils.encodeToString(signRequest.getPlaintext().getPlaintext()));
|
||||
Map<String, Object> request = new LinkedHashMap<>(3);
|
||||
PropertyMapper mapper = PropertyMapper.get();
|
||||
|
||||
if (StringUtils.hasText(signRequest.getHashAlgorithm())) {
|
||||
request.put("hash_algorithm", signRequest.getHashAlgorithm());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(signRequest.getSignatureAlgorithm())) {
|
||||
request.put("signature_algorithm", signRequest.getSignatureAlgorithm());
|
||||
}
|
||||
mapper.from(signRequest.getPlaintext()::getPlaintext).as(Base64Utils::encodeToString).to("input", request);
|
||||
mapper.from(signRequest::getHashAlgorithm).whenHasText().to("hash_algorithm", request);
|
||||
mapper.from(signRequest::getSignatureAlgorithm).whenHasText().to("signature_algorithm", request);
|
||||
|
||||
String signature = (String) this.vaultOperations.write(String.format("%s/sign/%s", this.path, keyName), request)
|
||||
.getRequiredData().get("signature");
|
||||
@@ -416,24 +408,16 @@ public class VaultTransitTemplate implements VaultTransitOperations {
|
||||
Assert.hasText(keyName, "Key name must not be empty");
|
||||
Assert.notNull(verificationRequest, "Signature verification request must not be null");
|
||||
|
||||
Map<String, Object> request = new LinkedHashMap<>();
|
||||
request.put("input", Base64Utils.encodeToString(verificationRequest.getPlaintext().getPlaintext()));
|
||||
Map<String, Object> request = new LinkedHashMap<>(5);
|
||||
PropertyMapper mapper = PropertyMapper.get();
|
||||
|
||||
if (verificationRequest.getHmac() != null) {
|
||||
request.put("hmac", verificationRequest.getHmac().getHmac());
|
||||
}
|
||||
|
||||
if (verificationRequest.getSignature() != null) {
|
||||
request.put("signature", verificationRequest.getSignature().getSignature());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(verificationRequest.getHashAlgorithm())) {
|
||||
request.put("hash_algorithm", verificationRequest.getHashAlgorithm());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(verificationRequest.getSignatureAlgorithm())) {
|
||||
request.put("signature_algorithm", verificationRequest.getSignatureAlgorithm());
|
||||
}
|
||||
mapper.from(verificationRequest.getPlaintext()::getPlaintext).as(Base64Utils::encodeToString).to("input",
|
||||
request);
|
||||
mapper.from(verificationRequest::getHmac).whenNonNull().as(Hmac::getHmac).to("hmac", request);
|
||||
mapper.from(verificationRequest::getSignature).whenNonNull().as(Signature::getSignature).to("signature",
|
||||
request);
|
||||
mapper.from(verificationRequest::getHashAlgorithm).whenHasText().to("hash_algorithm", request);
|
||||
mapper.from(verificationRequest::getSignatureAlgorithm).whenHasText().to("signature_algorithm", request);
|
||||
|
||||
Map<String, Object> response = this.vaultOperations
|
||||
.write(String.format("%s/verify/%s", this.path, keyName), request).getRequiredData();
|
||||
|
||||
Reference in New Issue
Block a user