diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java
index fa2e5841..7681935a 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java
@@ -40,6 +40,7 @@ import org.springframework.web.client.RestOperations;
*
* @author Mark Paluch
* @since 2.0
+ * @see AuthenticationSteps
*/
public class AuthenticationStepsExecutor implements ClientAuthentication {
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsOperator.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsOperator.java
new file mode 100644
index 00000000..c19b91a8
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsOperator.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import reactor.core.publisher.Mono;
+
+import org.springframework.http.HttpEntity;
+import org.springframework.util.Assert;
+import org.springframework.vault.VaultException;
+import org.springframework.vault.authentication.AuthenticationSteps.HttpRequest;
+import org.springframework.vault.authentication.AuthenticationSteps.HttpRequestNode;
+import org.springframework.vault.authentication.AuthenticationSteps.MapStep;
+import org.springframework.vault.authentication.AuthenticationSteps.Node;
+import org.springframework.vault.authentication.AuthenticationSteps.OnNextStep;
+import org.springframework.vault.authentication.AuthenticationSteps.SupplierStep;
+import org.springframework.vault.support.VaultResponse;
+import org.springframework.vault.support.VaultToken;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
+
+/**
+ * {@link VaultTokenSupplier} using {@link AuthenticationSteps} to create an
+ * authentication flow emitting {@link VaultToken}.
+ *
+ * This class uses {@link WebClient} for non-blocking and reactive HTTP access. The
+ * {@link AuthenticationSteps authentication flow} is materialized as reactive sequence
+ * postponing execution until {@link Mono#subscribe() subscription}.
+ *
+ * @author Mark Paluch
+ * @since 2.0
+ * @see AuthenticationSteps
+ */
+public class AuthenticationStepsOperator implements VaultTokenSupplier {
+
+ private static final Log logger = LogFactory.getLog(AppIdAuthentication.class);
+
+ private final AuthenticationSteps chain;
+
+ private final WebClient webClient;
+
+ /**
+ * Create a new {@link AuthenticationStepsOperator} given {@link AuthenticationSteps}
+ * and {@link WebClient}.
+ *
+ * @param steps must not be {@literal null}.
+ * @param webClient must not be {@literal null}.
+ */
+ public AuthenticationStepsOperator(AuthenticationSteps steps, WebClient webClient) {
+
+ Assert.notNull(steps, "AuthenticationSteps must not be null");
+ Assert.notNull(webClient, "WebClient must not be null");
+
+ this.chain = steps;
+ this.webClient = webClient;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Mono getVaultToken() throws VaultException {
+
+ Mono state = Mono.just(Undefinded.INSTANCE);
+
+ for (Node> o : chain.steps) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String
+ .format("Executing %s with current state %s", o, state));
+ }
+
+ if (o instanceof HttpRequestNode) {
+ state = state.flatMap(stateObject -> doHttpRequest(
+ (HttpRequestNode) o, stateObject));
+ }
+
+ if (o instanceof AuthenticationSteps.MapStep) {
+ state = state.map(stateObject -> doMapStep((MapStep) o,
+ stateObject));
+ }
+
+ if (o instanceof OnNextStep) {
+ state = state.doOnNext(stateObject -> doOnNext((OnNextStep) o,
+ stateObject));
+ }
+
+ if (o instanceof AuthenticationSteps.SupplierStep>) {
+ state = state
+ .map(stateObject -> doSupplierStep((SupplierStep) o));
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Executed %s with current state %s", o, state));
+ }
+ }
+
+ return state
+ .map(stateObject -> {
+
+ if (stateObject instanceof VaultToken) {
+ return (VaultToken) stateObject;
+ }
+
+ if (stateObject instanceof VaultResponse) {
+
+ VaultResponse response = (VaultResponse) stateObject;
+ return LoginTokenUtil.from(response.getAuth());
+ }
+
+ throw new IllegalStateException(
+ String.format(
+ "Cannot retrieve VaultToken from authentication chain. Got instead %s",
+ stateObject));
+ });
+ }
+
+ private static Object doSupplierStep(SupplierStep supplierStep) {
+ return supplierStep.get();
+ }
+
+ private static Object doMapStep(MapStep o, Object state) {
+ return o.apply(state);
+ }
+
+ private static Object doOnNext(OnNextStep o, Object state) {
+ return o.apply(state);
+ }
+
+ private Mono doHttpRequest(HttpRequestNode step, Object state) {
+
+ HttpRequest definition = step.getDefinition();
+ HttpEntity> entity = getEntity(definition.getEntity(), state);
+
+ RequestBodySpec spec;
+ if (definition.getUri() == null) {
+
+ spec = webClient.method(definition.getMethod()).uri(
+ definition.getUriTemplate(), (Object[]) definition.getUrlVariables());
+ }
+ else {
+ spec = webClient.method(definition.getMethod()).uri(definition.getUri());
+ }
+
+ for (Entry> header : entity.getHeaders().entrySet()) {
+ spec = spec.header(header.getKey(), header.getValue().get(0));
+ }
+
+ if (entity.getBody() != null && !entity.getBody().equals(Undefinded.INSTANCE)) {
+ return spec.syncBody(entity.getBody()).retrieve()
+ .bodyToMono(definition.getResponseType());
+ }
+
+ return spec.retrieve().bodyToMono(definition.getResponseType());
+ }
+
+ private static HttpEntity> getEntity(HttpEntity> entity, Object state) {
+
+ if (entity == null) {
+ return state == null ? HttpEntity.EMPTY : new HttpEntity<>(state);
+ }
+
+ if (entity.getBody() == null && state != null) {
+ return new HttpEntity<>(state, entity.getHeaders());
+ }
+
+ return entity;
+ }
+
+ static class Undefinded {
+
+ static final Undefinded INSTANCE = new Undefinded();
+
+ private Undefinded() {
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CachingVaultTokenSupplier.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CachingVaultTokenSupplier.java
new file mode 100644
index 00000000..b8088a3c
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CachingVaultTokenSupplier.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.vault.VaultException;
+import org.springframework.vault.support.VaultToken;
+
+/**
+ * Default implementation of {@link VaultTokenSupplier} caching the {@link VaultToken}
+ * from a delegate {@link VaultTokenSupplier}.
+ *
+ * @author Mark Paluch
+ * @since 2.0
+ * @see VaultTokenSupplier
+ * @see VaultToken
+ */
+public class CachingVaultTokenSupplier implements VaultTokenSupplier {
+
+ private final static Mono EMPTY = Mono.empty();
+
+ private final VaultTokenSupplier clientAuthentication;
+
+ private final AtomicReference> tokenRef = new AtomicReference<>(
+ EMPTY);
+
+ private CachingVaultTokenSupplier(VaultTokenSupplier clientAuthentication) {
+ this.clientAuthentication = clientAuthentication;
+ }
+
+ /**
+ * Creates a new {@link CachingVaultTokenSupplier} given a {@link VaultTokenSupplier
+ * delegate supplier}.
+ *
+ * @param delegate must not be {@literal null}.
+ * @return the {@link CachingVaultTokenSupplier} for a {@link VaultTokenSupplier
+ * delegate supplier}.
+ */
+ public static CachingVaultTokenSupplier of(VaultTokenSupplier delegate) {
+ return new CachingVaultTokenSupplier(delegate);
+ }
+
+ @Override
+ public Mono getVaultToken() throws VaultException {
+
+ if (Objects.equals(tokenRef.get(), EMPTY)) {
+ tokenRef.compareAndSet(EMPTY, clientAuthentication.getVaultToken().cache());
+ }
+
+ return tokenRef.get();
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/VaultTokenSupplier.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/VaultTokenSupplier.java
new file mode 100644
index 00000000..576d82e8
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/VaultTokenSupplier.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.vault.VaultException;
+import org.springframework.vault.support.VaultToken;
+
+/**
+ * {@link VaultTokenSupplier} provides a {@link VaultToken} to be used for authenticated
+ * Vault access. Implementing classes usually use a login method to login and return a
+ * {@link VaultToken} implementing {@link #getVaultToken()}.
+ *
+ * @author Mark Paluch
+ */
+@FunctionalInterface
+public interface VaultTokenSupplier {
+
+ /**
+ * Return a {@link VaultToken}. This can declare a Vault login flow to obtain a
+ * {@link VaultToken token}.
+ *
+ * @return a {@link Mono} with the {@link VaultToken}.
+ */
+ Mono getVaultToken() throws VaultException;
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java
new file mode 100644
index 00000000..3ce69d0f
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.client;
+
+import org.springframework.core.codec.ByteArrayDecoder;
+import org.springframework.core.codec.ByteArrayEncoder;
+import org.springframework.core.codec.StringDecoder;
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.http.codec.CodecConfigurer.CustomCodecs;
+import org.springframework.http.codec.json.Jackson2JsonDecoder;
+import org.springframework.http.codec.json.Jackson2JsonEncoder;
+import org.springframework.util.Assert;
+import org.springframework.web.reactive.function.client.ExchangeStrategies;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.util.UriBuilderFactory;
+
+/**
+ * Vault Client factory to create {@link WebClient} configured to the needs of accessing
+ * Vault.
+ *
+ * @author Mark Paluch
+ * @since 2.0
+ */
+public class ReactiveVaultClients {
+
+ /**
+ * Create a {@link WebClient} configured with {@link VaultEndpoint} and
+ * {@link ClientHttpConnector}. The client accepts relative URIs without a leading
+ * slash that are expanded to use {@link VaultEndpoint}.
+ *
+ * Requires Jackson 2 for Object-to-JSON mapping.
+ *
+ * @param endpoint must not be {@literal null}.
+ * @param connector must not be {@literal null}.
+ * @return the configured {@link WebClient}.
+ */
+ public static WebClient createWebClient(VaultEndpoint endpoint,
+ ClientHttpConnector connector) {
+
+ Assert.notNull(endpoint, "VaultEndpoint must not be null");
+ Assert.notNull(connector, "ClientHttpConnector must not be null");
+
+ UriBuilderFactory uriBuilderFactory = VaultClients
+ .createUriBuilderFactory(endpoint);
+
+ ExchangeStrategies strategies = ExchangeStrategies.builder()
+ .codecs(configurer -> {
+
+ CustomCodecs cc = configurer.customCodecs();
+
+ cc.decoder(new ByteArrayDecoder());
+ cc.decoder(new Jackson2JsonDecoder());
+ cc.decoder(StringDecoder.allMimeTypes(false));
+
+ cc.encoder(new ByteArrayEncoder());
+ cc.encoder(new Jackson2JsonEncoder());
+
+ }).build();
+
+ return WebClient.builder().uriBuilderFactory(uriBuilderFactory)
+ .exchangeStrategies(strategies).clientConnector(connector).build();
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java
index f8b4edf6..63b8c828 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultClients.java
@@ -27,7 +27,10 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriTemplateHandler;
+import org.springframework.web.util.UriBuilder;
+import org.springframework.web.util.UriBuilderFactory;
/**
* Vault Client factory to create {@link RestTemplate} configured to the needs of
@@ -108,54 +111,82 @@ public class VaultClients {
return defaultUriTemplateHandler;
}
+ public static UriBuilderFactory createUriBuilderFactory(VaultEndpoint endpoint) {
+
+ String baseUrl = String.format("%s://%s:%s/%s/", endpoint.getScheme(),
+ endpoint.getHost(), endpoint.getPort(), "v1");
+
+ return new PrefixAwareUriBuilderFactory(baseUrl);
+ }
+
public static class PrefixAwareUriTemplateHandler extends DefaultUriTemplateHandler {
@Override
protected URI expandInternal(String uriTemplate, Map uriVariables) {
- return super.expandInternal(prepareUriTemplate(uriTemplate), uriVariables);
+ return super.expandInternal(prepareUriTemplate(getBaseUrl(), uriTemplate),
+ uriVariables);
}
@Override
protected URI expandInternal(String uriTemplate, Object... uriVariables) {
- return super.expandInternal(prepareUriTemplate(uriTemplate), uriVariables);
+ return super.expandInternal(prepareUriTemplate(getBaseUrl(), uriTemplate),
+ uriVariables);
+ }
+ }
+
+ /**
+ * @since 2.0
+ */
+ public static class PrefixAwareUriBuilderFactory extends DefaultUriBuilderFactory {
+
+ private final String baseUri;
+
+ public PrefixAwareUriBuilderFactory(String baseUri) {
+ super(baseUri);
+ this.baseUri = baseUri;
}
- /**
- * Strip/add leading slashes from {@code uriTemplate} depending on wheter the base
- * url has a trailing slash.
- *
- * @param uriTemplate
- * @return
- */
- private String prepareUriTemplate(String uriTemplate) {
+ @Override
+ public UriBuilder uriString(String uriTemplate) {
+ return super.uriString(prepareUriTemplate(baseUri, uriTemplate));
+ }
+ }
- if (getBaseUrl() != null) {
- if (uriTemplate.startsWith("/") && getBaseUrl().endsWith("/")) {
- return uriTemplate.substring(1);
- }
+ /**
+ * Strip/add leading slashes from {@code uriTemplate} depending on wheter the base url
+ * has a trailing slash.
+ *
+ * @param uriTemplate
+ * @return
+ */
+ static String prepareUriTemplate(String baseUrl, String uriTemplate) {
- if (!uriTemplate.startsWith("/") && !getBaseUrl().endsWith("/")) {
- return "/" + uriTemplate;
- }
-
- return uriTemplate;
+ if (baseUrl != null) {
+ if (uriTemplate.startsWith("/") && baseUrl.endsWith("/")) {
+ return uriTemplate.substring(1);
}
- try {
- URI uri = URI.create(uriTemplate);
-
- if (uri.getHost() != null) {
- return uriTemplate;
- }
- }
- catch (IllegalArgumentException e) {
- }
-
- if (!uriTemplate.startsWith("/")) {
+ if (!uriTemplate.startsWith("/") && !baseUrl.endsWith("/")) {
return "/" + uriTemplate;
}
return uriTemplate;
}
+
+ try {
+ URI uri = URI.create(uriTemplate);
+
+ if (uri.getHost() != null) {
+ return uriTemplate;
+ }
+ }
+ catch (IllegalArgumentException e) {
+ }
+
+ if (!uriTemplate.startsWith("/")) {
+ return "/" + uriTemplate;
+ }
+
+ return uriTemplate;
}
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponses.java b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponses.java
index 13ddf8cc..c4f54381 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponses.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponses.java
@@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -77,14 +78,19 @@ public abstract class VaultResponses {
Assert.notNull(e, "HttpStatusCodeException must not be null");
- String message = VaultResponses.getError(e.getResponseBodyAsString());
+ return buildException(e.getStatusCode(), path,
+ VaultResponses.getError(e.getResponseBodyAsString()));
+ }
+
+ public static VaultException buildException(HttpStatus statusCode, String path,
+ String message) {
if (StringUtils.hasText(message)) {
- return new VaultException(String.format("Status %s %s: %s",
- e.getStatusCode(), path, message));
+ return new VaultException(String.format("Status %s %s: %s", statusCode, path,
+ message));
}
- return new VaultException(String.format("Status %s %s", e.getStatusCode(), path));
+ return new VaultException(String.format("Status %s %s", statusCode, path));
}
/**
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java
new file mode 100644
index 00000000..59792186
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.config;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.util.Assert;
+import org.springframework.vault.authentication.AuthenticationStepsFactory;
+import org.springframework.vault.authentication.AuthenticationStepsOperator;
+import org.springframework.vault.authentication.CachingVaultTokenSupplier;
+import org.springframework.vault.authentication.ClientAuthentication;
+import org.springframework.vault.authentication.SessionManager;
+import org.springframework.vault.authentication.TokenAuthentication;
+import org.springframework.vault.authentication.VaultTokenSupplier;
+import org.springframework.vault.client.ReactiveVaultClients;
+import org.springframework.vault.core.ReactiveVaultTemplate;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * Base class for Spring Vault configuration using JavaConfig for a reactive
+ * infrastructure.
+ *
+ * Reactive Vault support creates a {@link VaultTokenSupplier} (for the session token)
+ * from the configured {@link #clientAuthentication()}. The authentication object must
+ * implement {@link AuthenticationStepsFactory} exposing
+ * {@link org.springframework.vault.authentication.AuthenticationSteps} to obtain
+ * authentication using reactive infrastructure.
+ *
+ * @author Mark Paluch
+ * @since 2.0
+ */
+public abstract class AbstractReactiveVaultConfiguration extends
+ AbstractVaultConfiguration {
+
+ /**
+ * Create a {@link ReactiveVaultTemplate}.
+ *
+ * @return the {@link ReactiveVaultTemplate}.
+ * @see #vaultEndpoint()
+ * @see #clientHttpConnector()
+ * @see #vaultTokenSupplier()
+ */
+ @Bean
+ public ReactiveVaultTemplate reactiveVaultTemplate() {
+ return new ReactiveVaultTemplate(vaultEndpoint(), clientHttpConnector(),
+ vaultTokenSupplier());
+ }
+
+ /**
+ * Construct a {@link VaultTokenSupplier} using {@link #clientAuthentication()}. This
+ * {@link SessionManager} uses {@link #threadPoolTaskScheduler()}.
+ *
+ * @return the {@link VaultTokenSupplier} for Vault session token management.
+ * @see VaultTokenSupplier
+ * @see #clientAuthentication()
+ */
+ @Bean
+ public VaultTokenSupplier vaultTokenSupplier() {
+
+ ClientAuthentication clientAuthentication = clientAuthentication();
+
+ Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
+
+ if (clientAuthentication instanceof TokenAuthentication) {
+
+ TokenAuthentication authentication = (TokenAuthentication) clientAuthentication;
+ return () -> Mono.just(authentication.login());
+ }
+
+ if (clientAuthentication instanceof AuthenticationStepsFactory) {
+
+ AuthenticationStepsFactory factory = (AuthenticationStepsFactory) clientAuthentication;
+
+ WebClient webClient = ReactiveVaultClients.createWebClient(vaultEndpoint(),
+ clientHttpConnector());
+ AuthenticationStepsOperator stepsOperator = new AuthenticationStepsOperator(
+ factory.getAuthenticationSteps(), webClient);
+
+ return CachingVaultTokenSupplier.of(stepsOperator);
+ }
+
+ throw new IllegalStateException(
+ String.format(
+ "Cannot construct VaultTokenSupplier from %s. "
+ + "ClientAuthentication must implement AuthenticationStepsFactory or be TokenAuthentication",
+ clientAuthentication));
+ }
+
+ /**
+ * Create a {@link ClientHttpConnector} configured with {@link ClientOptions} and
+ * {@link org.springframework.vault.support.SslConfiguration}.
+ *
+ * @return the {@link ClientHttpConnector} instance.
+ * @see #clientOptions()
+ * @see #sslConfiguration()
+ */
+ public ClientHttpConnector clientHttpConnector() {
+ return ClientHttpConnectorFactory.create(clientOptions(), sslConfiguration());
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpConnectorFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpConnectorFactory.java
new file mode 100644
index 00000000..243a3ec0
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpConnectorFactory.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.config;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.concurrent.atomic.AtomicLong;
+
+import io.netty.channel.ChannelOption;
+import io.netty.handler.ssl.SslContextBuilder;
+import reactor.ipc.netty.resources.PoolResources;
+
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.SslConfiguration;
+
+import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.createKeyManagerFactory;
+import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.createTrustManagerFactory;
+import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.hasSslConfiguration;
+
+/**
+ * Factory for {@link ClientHttpConnector} that supports
+ * {@link ReactorClientHttpConnector}.
+ *
+ * @author Mark Paluch
+ * @since 2.0
+ */
+public class ClientHttpConnectorFactory {
+
+ private static final AtomicLong POOL_COUNTER = new AtomicLong();
+
+ /**
+ * Create a {@link ClientHttpConnector} for the given {@link ClientOptions} and
+ * {@link SslConfiguration}.
+ *
+ * @param options must not be {@literal null}
+ * @param sslConfiguration must not be {@literal null}
+ * @return a new {@link ClientHttpConnector}.
+ */
+ public static ClientHttpConnector create(ClientOptions options,
+ SslConfiguration sslConfiguration) {
+
+ return new ReactorClientHttpConnector(builder -> {
+
+ if (hasSslConfiguration(sslConfiguration)) {
+
+ builder.sslSupport(sslContextBuilder -> {
+ configureSsl(sslConfiguration, sslContextBuilder);
+ }).poolResources(
+ PoolResources.elastic("vault-http-"
+ + POOL_COUNTER.incrementAndGet()));
+ }
+
+ builder.sslHandshakeTimeout(options.getConnectionTimeout());
+ builder.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
+ Math.toIntExact(options.getConnectionTimeout().toMillis()));
+ });
+ }
+
+ private static void configureSsl(SslConfiguration sslConfiguration,
+ SslContextBuilder sslContextBuilder) {
+
+ try {
+
+ if (sslConfiguration.getTrustStore() != null) {
+ sslContextBuilder.trustManager(createTrustManagerFactory(sslConfiguration
+ .getTrustStoreConfiguration()));
+ }
+
+ if (sslConfiguration.getKeyStore() != null) {
+ sslContextBuilder.keyManager(createKeyManagerFactory(sslConfiguration
+ .getKeyStoreConfiguration()));
+ }
+ }
+ catch (GeneralSecurityException | IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java
index 5241ced0..13f9b78e 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java
@@ -139,7 +139,7 @@ public class ClientHttpRequestFactoryFactory {
return sslContext;
}
- private static KeyManagerFactory createKeyManagerFactory(
+ static KeyManagerFactory createKeyManagerFactory(
KeyStoreConfiguration keyStoreConfiguration) throws GeneralSecurityException,
IOException {
@@ -158,7 +158,7 @@ public class ClientHttpRequestFactoryFactory {
return keyManagerFactory;
}
- private static TrustManagerFactory createTrustManagerFactory(
+ static TrustManagerFactory createTrustManagerFactory(
KeyStoreConfiguration keyStoreConfiguration) throws GeneralSecurityException,
IOException {
@@ -191,7 +191,7 @@ public class ClientHttpRequestFactoryFactory {
}
}
- private static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
+ static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
return sslConfiguration.getTrustStore() != null
|| sslConfiguration.getKeyStore() != null;
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultOperations.java
new file mode 100644
index 00000000..b2f8771a
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultOperations.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.core;
+
+import java.util.function.Function;
+
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import org.springframework.vault.VaultException;
+import org.springframework.vault.support.VaultResponse;
+import org.springframework.vault.support.VaultResponseSupport;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientException;
+
+/**
+ * Interface that specifies a basic set of Vault operations executed on a reactive
+ * infrastructure, implemented by {@link ReactiveVaultTemplate}. This is the main entry
+ * point to interact with Vault in an authenticated and unauthenticated context.
+ *
+ * {@link ReactiveVaultOperations} allows execution of callback methods. Callbacks can
+ * execute requests within a {@link ReactiveVaultOperations#doWithSession(Function)
+ * session context} and the {@link ReactiveVaultOperations#doWithVault(Function) without a
+ * session}.
+ *
+ * @author Mark Paluch
+ * @see ReactiveVaultOperations#doWithSession(Function)
+ * @see ReactiveVaultOperations#doWithVault(Function)
+ * @see WebClient
+ * @see VaultTemplate
+ * @see VaultTokenOperations
+ * @see org.springframework.vault.authentication.VaultTokenSupplier
+ */
+public interface ReactiveVaultOperations {
+
+ /**
+ * Read from a secret backend. Reading data using this method is suitable for secret
+ * backends that do not require a request body.
+ *
+ * @param path must not be {@literal null}.
+ * @return the data. May be {@literal null} if the path does not exist.
+ */
+ Mono read(String path);
+
+ /**
+ * Read from a secret backend. Reading data using this method is suitable for secret
+ * backends that do not require a request body.
+ *
+ * @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.
+ */
+ Mono> read(String path, Class responseType);
+
+ /**
+ * Enumerate keys from a secret backend.
+ *
+ * @param path must not be {@literal null}.
+ * @return the data. May be {@literal null} if the path does not exist.
+ */
+ Flux list(String path);
+
+ /**
+ * Write to a secret backend.
+ *
+ * @param path must not be {@literal null}.
+ * @param body the body, may be {@literal null} if absent.
+ * @return the configuration data. May be empty but never {@literal null}.
+ */
+ Mono write(String path, Object body);
+
+ /**
+ * Delete a path in the secret backend.
+ *
+ * @param path must not be {@literal null}.
+ */
+ Mono delete(String path);
+
+ /**
+ * Executes a Vault {@link RestOperationsCallback}. Allows to interact with Vault
+ * using {@link org.springframework.web.client.RestOperations} without requiring a
+ * session.
+ *
+ * @param clientCallback the request.
+ * @return the {@link RestOperationsCallback} return value.
+ * @throws VaultException when a
+ * {@link org.springframework.web.client.HttpStatusCodeException} occurs.
+ * @throws WebClientException exceptions from
+ * {@link org.springframework.web.reactive.function.client.WebClient}.
+ */
+ > T doWithVault(
+ Function clientCallback) throws VaultException,
+ WebClientException;
+
+ /**
+ * Executes a Vault {@link RestOperationsCallback}. Allows to interact with Vault in
+ * an authenticated session.
+ *
+ * @param sessionCallback the request.
+ * @return the {@link RestOperationsCallback} return value.
+ * @throws VaultException when a
+ * {@link org.springframework.web.client.HttpStatusCodeException} occurs.
+ * @throws WebClientException exceptions from
+ * {@link org.springframework.web.reactive.function.client.WebClient}.
+ */
+ > T doWithSession(
+ Function sessionCallback) throws VaultException,
+ WebClientException;
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java
new file mode 100644
index 00000000..2041e0b2
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.core;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.util.Assert;
+import org.springframework.vault.VaultException;
+import org.springframework.vault.authentication.SessionManager;
+import org.springframework.vault.authentication.VaultTokenSupplier;
+import org.springframework.vault.client.ReactiveVaultClients;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.client.VaultHttpHeaders;
+import org.springframework.vault.client.VaultResponses;
+import org.springframework.vault.support.VaultResponse;
+import org.springframework.vault.support.VaultResponseSupport;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.reactive.function.BodyExtractors;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientException;
+
+import static org.springframework.web.reactive.function.client.ExchangeFilterFunction.ofRequestProcessor;
+
+/**
+ * This class encapsulates main Vault interaction. {@link ReactiveVaultTemplate} will log
+ * into Vault on initialization and use the token throughout the whole lifetime.
+ *
+ * @author Mark Paluch
+ * @see SessionManager
+ */
+public class ReactiveVaultTemplate implements ReactiveVaultOperations {
+
+ private final WebClient statelessClient;
+
+ private final WebClient sessionClient;
+
+ /**
+ * Create a new {@link ReactiveVaultTemplate} with a {@link VaultEndpoint},
+ * {@link ClientHttpConnector} and {@link VaultTokenSupplier}.
+ *
+ * @param vaultEndpoint must not be {@literal null}.
+ * @param connector must not be {@literal null}.
+ * @param vaultTokenSupplier must not be {@literal null}.
+ */
+ public ReactiveVaultTemplate(VaultEndpoint vaultEndpoint,
+ ClientHttpConnector connector, VaultTokenSupplier vaultTokenSupplier) {
+
+ Assert.notNull(vaultEndpoint, "VaultEndpoint must not be null");
+ Assert.notNull(connector, "ClientHttpConnector must not be null");
+ Assert.notNull(vaultTokenSupplier, "AuthenticationSupplier must not be null");
+
+ ExchangeFilterFunction filter = ofRequestProcessor(request -> vaultTokenSupplier
+ .getVaultToken().map(token -> {
+
+ return ClientRequest.from(request).headers(headers -> {
+ headers.set(VaultHttpHeaders.VAULT_TOKEN, token.getToken());
+ }).build();
+ }));
+
+ this.statelessClient = ReactiveVaultClients.createWebClient(vaultEndpoint,
+ connector);
+ this.sessionClient = ReactiveVaultClients
+ .createWebClient(vaultEndpoint, connector).mutate().filter(filter)
+ .build();
+ }
+
+ @Override
+ public Mono read(String path) {
+
+ Assert.hasText(path, "Path must not be empty");
+
+ return doRead(path, VaultResponse.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Mono> read(String path, Class responseType) {
+
+ ParameterizedTypeReference> ref = VaultResponses
+ .getTypeReference(responseType);
+
+ return sessionClient.get().uri(path).exchange().flatMap(mapResponse(ref, path));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Flux list(String path) {
+
+ Assert.hasText(path, "Path must not be empty");
+
+ Mono read = doRead(
+ String.format("%s?list=true", path.endsWith("/") ? path : (path + "/")),
+ VaultListResponse.class);
+
+ return read.filter(
+ response -> response.getData() != null
+ && response.getData().containsKey("keys")) //
+ .flatMapIterable(
+ response -> (List) response.getData().get("keys"));
+ }
+
+ @Override
+ public Mono write(String path, Object body) {
+
+ Assert.hasText(path, "Path must not be empty");
+
+ return sessionClient.post().uri(path).syncBody(body).exchange()
+ .flatMap(mapResponse(VaultResponse.class, path)).then();
+ }
+
+ @Override
+ public Mono delete(String path) {
+
+ Assert.hasText(path, "Path must not be empty");
+
+ return sessionClient.delete().uri(path).exchange()
+ .flatMap(mapResponse(String.class, path)).then();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public > T doWithVault(
+ Function clientCallback) throws VaultException,
+ WebClientException {
+
+ Assert.notNull(clientCallback, "Client callback must not be null");
+
+ try {
+ return (T) clientCallback.apply(statelessClient);
+ }
+ catch (HttpStatusCodeException e) {
+ throw VaultResponses.buildException(e);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public > T doWithSession(
+ Function sessionCallback) throws VaultException,
+ WebClientException {
+
+ Assert.notNull(sessionCallback, "Session callback must not be null");
+
+ try {
+ return (T) sessionCallback.apply(sessionClient);
+ }
+ catch (HttpStatusCodeException e) {
+ throw VaultResponses.buildException(e);
+ }
+ }
+
+ private Mono doRead(String path, Class responseType) {
+
+ return doWithSession(client -> client.get() //
+ .uri(path).exchange().flatMap(mapResponse(responseType, path)));
+ }
+
+ private static Function> mapResponse(
+ Class bodyType, String path) {
+ return response -> isSuccess(response) ? response.bodyToMono(bodyType)
+ : mapOtherwise(response, path);
+ }
+
+ private static Function> mapResponse(
+ ParameterizedTypeReference typeReference, String path) {
+
+ return response -> isSuccess(response) ? response.body(BodyExtractors
+ .toMono(typeReference)) : mapOtherwise(response, path);
+ }
+
+ private static boolean isSuccess(ClientResponse response) {
+ return response.statusCode().is2xxSuccessful();
+ }
+
+ private static Mono extends T> mapOtherwise(ClientResponse response, String path) {
+
+ if (response.statusCode() == HttpStatus.NOT_FOUND) {
+ return Mono.empty();
+ }
+
+ return response.bodyToMono(String.class).flatMap(
+ body -> {
+
+ String error = VaultResponses.getError(body);
+
+ return Mono.error(VaultResponses.buildException(
+ response.statusCode(), path, error));
+ });
+ }
+
+ private static class VaultListResponse extends
+ VaultResponseSupport> {
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTestBase.java
new file mode 100644
index 00000000..948ac145
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTestBase.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+
+import org.springframework.vault.util.IntegrationTestSupport;
+
+/**
+ * Integration test base class for {@link AppIdAuthentication} tests.
+ *
+ * @author Mark Paluch
+ */
+public abstract class AppIdAuthenticationIntegrationTestBase extends
+ IntegrationTestSupport {
+
+ @Before
+ public void before() {
+
+ if (!prepare().hasAuth("app-id")) {
+ prepare().mountAuth("app-id");
+ }
+
+ prepare().getVaultOperations().doWithSession(restOperations -> {
+
+ Map appIdData = new HashMap();
+ appIdData.put("value", "dummy"); // policy
+ appIdData.put("display_name", "this is my test application");
+
+ restOperations.postForEntity("auth/app-id/map/app-id/myapp", appIdData,
+ Map.class);
+
+ Map userIdData = new HashMap();
+ userIdData.put("value", "myapp"); // name of the app-id
+ userIdData.put("cidr_block", "0.0.0.0/0");
+
+ restOperations.postForEntity(
+ "auth/app-id/map/user-id/static-userid-value", userIdData,
+ Map.class);
+
+ return null;
+ });
+ }
+
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java
index c4e99cb3..669119c6 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationIntegrationTests.java
@@ -15,15 +15,10 @@
*/
package org.springframework.vault.authentication;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.junit.Before;
import org.junit.Test;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultToken;
-import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
@@ -35,35 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Mark Paluch
*/
-public class AppIdAuthenticationIntegrationTests extends IntegrationTestSupport {
-
- @Before
- public void before() {
-
- if (!prepare().hasAuth("app-id")) {
- prepare().mountAuth("app-id");
- }
-
- prepare().getVaultOperations().doWithSession(restOperations -> {
-
- Map appIdData = new HashMap();
- appIdData.put("value", "dummy"); // policy
- appIdData.put("display_name", "this is my test application");
-
- restOperations.postForEntity("auth/app-id/map/app-id/myapp", appIdData,
- Map.class);
-
- Map userIdData = new HashMap();
- userIdData.put("value", "myapp"); // name of the app-id
- userIdData.put("cidr_block", "0.0.0.0/0");
-
- restOperations.postForEntity(
- "auth/app-id/map/user-id/static-userid-value", userIdData,
- Map.class);
-
- return null;
- });
- }
+public class AppIdAuthenticationIntegrationTests extends
+ AppIdAuthenticationIntegrationTestBase {
@Test
public void shouldLoginSuccessfully() {
@@ -97,45 +65,4 @@ public class AppIdAuthenticationIntegrationTests extends IntegrationTestSupport
new AppIdAuthentication(options, restTemplate).login();
}
-
- @Test
- public void authenticationStepsShouldLoginSuccessfully() {
-
- AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
- .appId("myapp") //
- .userIdMechanism(new StaticUserId("static-userid-value")) //
- .build();
-
- RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
- .createSslConfiguration());
-
- AppIdAuthentication authentication = new AppIdAuthentication(options,
- restTemplate);
-
- AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
- authentication.getAuthenticationSteps(), restTemplate);
-
- VaultToken login = executor.login();
-
- assertThat(login.getToken()).isNotEmpty();
- }
-
- @Test(expected = VaultException.class)
- public void authenticationStepsLoginShouldFail() {
-
- AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
- .appId("wrong") //
- .userIdMechanism(new StaticUserId("wrong")) //
- .build();
-
- RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
- .createSslConfiguration());
-
- AuthenticationSteps authenticationChain = new AppIdAuthentication(options,
- restTemplate).getAuthenticationSteps();
- AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
- authenticationChain, restTemplate);
-
- executor.login();
- }
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationOperatorIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationOperatorIntegrationTests.java
new file mode 100644
index 00000000..c66fa4c2
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationOperatorIntegrationTests.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import org.junit.Test;
+import reactor.test.StepVerifier;
+
+import org.springframework.vault.util.Settings;
+import org.springframework.vault.util.TestWebClientFactory;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * Integration tests for {@link AppIdAuthentication} using
+ * {@link AuthenticationStepsOperator}.
+ *
+ * @author Mark Paluch
+ */
+public class AppIdAuthenticationOperatorIntegrationTests extends
+ AppIdAuthenticationIntegrationTestBase {
+
+ WebClient webClient = TestWebClientFactory.create(Settings.createSslConfiguration());
+
+ @Test
+ public void authenticationStepsShouldLoginSuccessfully() {
+
+ AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
+ .appId("myapp") //
+ .userIdMechanism(new StaticUserId("static-userid-value")) //
+ .build();
+
+ AppIdAuthentication authentication = new AppIdAuthentication(options,
+ new RestTemplate());
+
+ AuthenticationStepsOperator supplier = new AuthenticationStepsOperator(
+ authentication.getAuthenticationSteps(), webClient);
+
+ StepVerifier.create(supplier.getVaultToken()).expectNextCount(1).verifyComplete();
+ }
+
+ @Test
+ public void authenticationStepsLoginShouldFail() {
+
+ AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
+ .appId("wrong") //
+ .userIdMechanism(new StaticUserId("wrong")) //
+ .build();
+
+ AppIdAuthentication authentication = new AppIdAuthentication(options,
+ new RestTemplate());
+
+ AuthenticationStepsOperator supplier = new AuthenticationStepsOperator(
+ authentication.getAuthenticationSteps(), webClient);
+
+ StepVerifier.create(supplier.getVaultToken()).expectError().verify();
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationStepsIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationStepsIntegrationTests.java
new file mode 100644
index 00000000..1e02c15a
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationStepsIntegrationTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import org.junit.Test;
+
+import org.springframework.vault.VaultException;
+import org.springframework.vault.support.VaultToken;
+import org.springframework.vault.util.Settings;
+import org.springframework.vault.util.TestRestTemplateFactory;
+import org.springframework.web.client.RestTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link AppIdAuthentication} using
+ * {@link AuthenticationStepsExecutor}.
+ *
+ * @author Mark Paluch
+ */
+public class AppIdAuthenticationStepsIntegrationTests extends
+ AppIdAuthenticationIntegrationTestBase {
+
+ @Test
+ public void authenticationStepsShouldLoginSuccessfully() {
+
+ AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
+ .appId("myapp") //
+ .userIdMechanism(new StaticUserId("static-userid-value")) //
+ .build();
+
+ RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
+ .createSslConfiguration());
+
+ AppIdAuthentication authentication = new AppIdAuthentication(options,
+ restTemplate);
+
+ AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
+ authentication.getAuthenticationSteps(), restTemplate);
+
+ VaultToken login = executor.login();
+
+ assertThat(login.getToken()).isNotEmpty();
+ }
+
+ @Test(expected = VaultException.class)
+ public void authenticationStepsLoginShouldFail() {
+
+ AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
+ .appId("wrong") //
+ .userIdMechanism(new StaticUserId("wrong")) //
+ .build();
+
+ RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
+ .createSslConfiguration());
+
+ AuthenticationSteps authenticationChain = new AppIdAuthentication(options,
+ restTemplate).getAuthenticationSteps();
+ AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
+ authenticationChain, restTemplate);
+
+ executor.login();
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AuthenticationStepsOperatorUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AuthenticationStepsOperatorUnitTests.java
new file mode 100644
index 00000000..0df2ae03
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AuthenticationStepsOperatorUnitTests.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.http.client.reactive.ClientHttpRequest;
+import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
+import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
+import org.springframework.vault.support.VaultResponse;
+import org.springframework.vault.support.VaultToken;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post;
+
+/**
+ * Unit tests for {@link AuthenticationStepsOperator}.
+ *
+ * @author Mark Paluch
+ */
+public class AuthenticationStepsOperatorUnitTests {
+
+ @Before
+ public void before() throws Exception {
+ }
+
+ @Test
+ public void justTokenShouldLogin() {
+
+ AuthenticationSteps steps = AuthenticationSteps.just(VaultToken.of("my-token"));
+
+ StepVerifier.create(login(steps)).expectNext(VaultToken.of("my-token"))
+ .verifyComplete();
+ }
+
+ @Test
+ public void supplierOfStringShouldLoginWithMap() {
+
+ AuthenticationSteps steps = AuthenticationSteps.fromSupplier(() -> "my-token")
+ .login(VaultToken::of);
+
+ StepVerifier.create(login(steps)).expectNext(VaultToken.of("my-token"))
+ .verifyComplete();
+ }
+
+ @Test
+ public void justLoginRequestShouldLogin() {
+
+ ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.POST,
+ "/auth/cert/login");
+ MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
+ response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+ response.setBody("{"
+ + "\"auth\":{\"client_token\":\"my-token\", \"renewable\": true, \"lease_duration\": 10}"
+ + "}");
+ ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(
+ Mono.just(response));
+
+ WebClient webClient = WebClient.builder().clientConnector(connector).build();
+
+ AuthenticationSteps steps = AuthenticationSteps.just(post("/auth/{path}/login",
+ "cert").as(VaultResponse.class));
+
+ StepVerifier.create(login(steps, webClient))
+ .expectNext(VaultToken.of("my-token")).verifyComplete();
+ }
+
+ @Test
+ public void justLoginShouldFail() {
+
+ ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.POST,
+ "/auth/cert/login");
+ MockClientHttpResponse response = new MockClientHttpResponse(
+ HttpStatus.BAD_REQUEST);
+ ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(
+ Mono.just(response));
+
+ WebClient webClient = WebClient.builder().clientConnector(connector).build();
+
+ AuthenticationSteps steps = AuthenticationSteps.just(post("/auth/{path}/login",
+ "cert").as(VaultResponse.class));
+
+ StepVerifier.create(login(steps, webClient)).expectError().verify();
+ }
+
+ private Mono login(AuthenticationSteps steps) {
+
+ AuthenticationStepsOperator operator = new AuthenticationStepsOperator(steps,
+ WebClient.create());
+ return operator.getVaultToken();
+ }
+
+ private Mono login(AuthenticationSteps steps, WebClient webClient) {
+
+ AuthenticationStepsOperator operator = new AuthenticationStepsOperator(steps,
+ webClient);
+ return operator.getVaultToken();
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTestBase.java
new file mode 100644
index 00000000..8d8f844f
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTestBase.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+
+import org.assertj.core.util.Files;
+import org.junit.Before;
+
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.vault.core.RestOperationsCallback;
+import org.springframework.vault.support.SslConfiguration;
+import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
+import org.springframework.vault.util.IntegrationTestSupport;
+
+import static org.springframework.vault.util.Settings.createSslConfiguration;
+import static org.springframework.vault.util.Settings.findWorkDir;
+
+/**
+ * Integration test base class for {@link ClientCertificateAuthentication} tests.
+ *
+ * @author Mark Paluch
+ */
+public abstract class ClientCertificateAuthenticationIntegrationTestBase extends
+ IntegrationTestSupport {
+
+ @Before
+ public void before() {
+
+ if (!prepare().hasAuth("cert")) {
+ prepare().mountAuth("cert");
+ }
+
+ prepare().getVaultOperations().doWithSession(
+ (RestOperationsCallback) restOperations -> {
+ File workDir = findWorkDir();
+
+ String certificate = Files.contentOf(new File(workDir,
+ "ca/certs/client.cert.pem"), StandardCharsets.US_ASCII);
+
+ return restOperations.postForEntity("auth/cert/certs/my-role",
+ Collections.singletonMap("certificate", certificate),
+ Map.class);
+ });
+ }
+
+ static SslConfiguration prepareCertAuthenticationMethod() {
+
+ SslConfiguration original = createSslConfiguration();
+
+ return new SslConfiguration(new KeyStoreConfiguration(new FileSystemResource(
+ new File(findWorkDir(), "client-cert.jks")), "changeit".toCharArray(),
+ null), original.getTrustStoreConfiguration());
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
index 6817e856..739622d0 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
@@ -15,33 +15,19 @@
*/
package org.springframework.vault.authentication;
-import java.io.File;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.Map;
-
-import org.assertj.core.util.Files;
-import org.junit.Before;
import org.junit.Test;
import org.springframework.core.NestedRuntimeException;
-import org.springframework.core.io.FileSystemResource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
-import org.springframework.vault.core.RestOperationsCallback;
import org.springframework.vault.support.ClientOptions;
-import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
-import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
-import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.vault.util.Settings.createSslConfiguration;
-import static org.springframework.vault.util.Settings.findWorkDir;
/**
* Integration tests for {@link ClientCertificateAuthentication}.
@@ -49,27 +35,7 @@ import static org.springframework.vault.util.Settings.findWorkDir;
* @author Mark Paluch
*/
public class ClientCertificateAuthenticationIntegrationTests extends
- IntegrationTestSupport {
-
- @Before
- public void before() {
-
- if (!prepare().hasAuth("cert")) {
- prepare().mountAuth("cert");
- }
-
- prepare().getVaultOperations().doWithSession(
- (RestOperationsCallback) restOperations -> {
- File workDir = findWorkDir();
-
- String certificate = Files.contentOf(new File(workDir,
- "ca/certs/client.cert.pem"), StandardCharsets.US_ASCII);
-
- return restOperations.postForEntity("auth/cert/certs/my-role",
- Collections.singletonMap("certificate", certificate),
- Map.class);
- });
- }
+ ClientCertificateAuthenticationIntegrationTestBase {
@Test
public void shouldLoginSuccessfully() {
@@ -98,48 +64,4 @@ public class ClientCertificateAuthenticationIntegrationTests extends
new ClientCertificateAuthentication(restTemplate).login();
}
-
- @Test
- public void authenticationStepsShouldLoginSuccessfully() {
-
- ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
- .create(new ClientOptions(), prepareCertAuthenticationMethod());
-
- RestTemplate restTemplate = VaultClients.createRestTemplate(
- TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
- ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
- restTemplate);
-
- AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
- authentication.getAuthenticationSteps(), restTemplate);
-
- VaultToken login = executor.login();
-
- assertThat(login.getToken()).isNotEmpty();
- }
-
- // Compatibility for Vault 0.6.0 and below. Vault 0.6.1 fixed that issue and we
- // receive a VaultException here.
- @Test(expected = NestedRuntimeException.class)
- public void authenticationStepsLoginShouldFail() {
-
- ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
- .create(new ClientOptions(), Settings.createSslConfiguration());
- RestTemplate restTemplate = VaultClients.createRestTemplate(
- TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
-
- AuthenticationSteps steps = new ClientCertificateAuthentication(restTemplate)
- .getAuthenticationSteps();
-
- new AuthenticationStepsExecutor(steps, restTemplate).login();
- }
-
- private SslConfiguration prepareCertAuthenticationMethod() {
-
- SslConfiguration original = createSslConfiguration();
-
- return new SslConfiguration(new KeyStoreConfiguration(new FileSystemResource(
- new File(findWorkDir(), "client-cert.jks")), "changeit".toCharArray(),
- null), original.getTrustStoreConfiguration());
- }
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationOperatorIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationOperatorIntegrationTests.java
new file mode 100644
index 00000000..b584f821
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationOperatorIntegrationTests.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import org.junit.Test;
+import reactor.test.StepVerifier;
+
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.vault.client.VaultClients;
+import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.util.TestRestTemplateFactory;
+import org.springframework.vault.util.TestWebClientFactory;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * Integration tests for {@link ClientCertificateAuthentication} using
+ * {@link AuthenticationStepsOperator}.
+ *
+ * @author Mark Paluch
+ */
+public class ClientCertificateAuthenticationOperatorIntegrationTests extends
+ ClientCertificateAuthenticationIntegrationTestBase {
+
+ @Test
+ public void authenticationStepsShouldLoginSuccessfully() {
+
+ WebClient webClient = TestWebClientFactory
+ .create(prepareCertAuthenticationMethod());
+
+ ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
+ .create(new ClientOptions(), prepareCertAuthenticationMethod());
+
+ RestTemplate restTemplate = VaultClients.createRestTemplate(
+ TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
+ ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
+ restTemplate);
+
+ AuthenticationStepsOperator operator = new AuthenticationStepsOperator(
+ authentication.getAuthenticationSteps(), webClient);
+
+ StepVerifier.create(operator.getVaultToken()).expectNextCount(1).verifyComplete();
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationStepsIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationStepsIntegrationTests.java
new file mode 100644
index 00000000..1f4af92a
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationStepsIntegrationTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import org.junit.Test;
+
+import org.springframework.core.NestedRuntimeException;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.vault.client.VaultClients;
+import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.VaultToken;
+import org.springframework.vault.util.Settings;
+import org.springframework.vault.util.TestRestTemplateFactory;
+import org.springframework.web.client.RestTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link ClientCertificateAuthentication} using
+ * {@link AuthenticationStepsExecutor}.
+ *
+ * @author Mark Paluch
+ */
+public class ClientCertificateAuthenticationStepsIntegrationTests extends
+ ClientCertificateAuthenticationIntegrationTestBase {
+
+ @Test
+ public void authenticationStepsShouldLoginSuccessfully() {
+
+ ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
+ .create(new ClientOptions(), prepareCertAuthenticationMethod());
+
+ RestTemplate restTemplate = VaultClients.createRestTemplate(
+ TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
+ ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
+ restTemplate);
+
+ AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
+ authentication.getAuthenticationSteps(), restTemplate);
+
+ VaultToken login = executor.login();
+
+ assertThat(login.getToken()).isNotEmpty();
+ }
+
+ // Compatibility for Vault 0.6.0 and below. Vault 0.6.1 fixed that issue and we
+ // receive a VaultException here.
+ @Test(expected = NestedRuntimeException.class)
+ public void authenticationStepsLoginShouldFail() {
+
+ ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory
+ .create(new ClientOptions(), Settings.createSslConfiguration());
+ RestTemplate restTemplate = VaultClients.createRestTemplate(
+ TestRestTemplateFactory.TEST_VAULT_ENDPOINT, clientHttpRequestFactory);
+
+ AuthenticationSteps steps = new ClientCertificateAuthentication(restTemplate)
+ .getAuthenticationSteps();
+
+ new AuthenticationStepsExecutor(steps, restTemplate).login();
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTestBase.java
new file mode 100644
index 00000000..9066031b
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTestBase.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import java.util.Map;
+
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.vault.support.VaultResponse;
+import org.springframework.vault.util.IntegrationTestSupport;
+
+import static org.junit.Assume.assumeNotNull;
+
+/**
+ * Integration test base class for {@link CubbyholeAuthentication} tests.
+ *
+ * @author Mark Paluch
+ */
+public abstract class CubbyholeAuthenticationIntegrationTestBase extends
+ IntegrationTestSupport {
+
+ protected Map prepareWrappedToken() {
+
+ ResponseEntity response = prepare().getVaultOperations()
+ .doWithSession(
+ restOperations -> {
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("X-Vault-Wrap-TTL", "10m");
+
+ return restOperations.exchange("auth/token/create",
+ HttpMethod.POST, new HttpEntity(headers),
+ VaultResponse.class);
+ });
+
+ Map wrapInfo = response.getBody().getWrapInfo();
+
+ // Response Wrapping requires Vault 0.6.0+
+ assumeNotNull(wrapInfo);
+ return wrapInfo;
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java
index 5e4c87e4..86b09c66 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java
@@ -19,28 +19,22 @@ import java.util.Map;
import org.junit.Test;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
import org.springframework.vault.VaultException;
-import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
-import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
-import static org.junit.Assume.assumeNotNull;
/**
* Integration tests for {@link CubbyholeAuthentication}.
*
* @author Mark Paluch
*/
-public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupport {
+public class CubbyholeAuthenticationIntegrationTests extends
+ CubbyholeAuthenticationIntegrationTestBase {
@Test
public void shouldCreateWrappedToken() {
@@ -60,28 +54,6 @@ public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupp
assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
}
- @Test
- public void authenticationStepsShouldCreateWrappedToken() {
-
- Map wrapInfo = prepareWrappedToken();
-
- String initialToken = wrapInfo.get("token");
-
- CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
- .initialToken(VaultToken.of(initialToken)).wrapped().build();
- RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
- .createSslConfiguration());
-
- CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
- restTemplate);
-
- AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
- authentication.getAuthenticationSteps(), restTemplate);
-
- VaultToken login = executor.login();
- assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
- }
-
@Test
public void loginShouldFail() {
@@ -103,24 +75,4 @@ public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupp
}
}
- private Map prepareWrappedToken() {
-
- ResponseEntity response = prepare().getVaultOperations()
- .doWithSession(
- restOperations -> {
-
- HttpHeaders headers = new HttpHeaders();
- headers.add("X-Vault-Wrap-TTL", "10m");
-
- return restOperations.exchange("auth/token/create",
- HttpMethod.POST, new HttpEntity(headers),
- VaultResponse.class);
- });
-
- Map wrapInfo = response.getBody().getWrapInfo();
-
- // Response Wrapping requires Vault 0.6.0+
- assumeNotNull(wrapInfo);
- return wrapInfo;
- }
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationOperatorIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationOperatorIntegrationTests.java
new file mode 100644
index 00000000..b2ff25fb
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationOperatorIntegrationTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import java.util.Map;
+
+import org.junit.Test;
+import reactor.test.StepVerifier;
+
+import org.springframework.vault.support.VaultToken;
+import org.springframework.vault.util.Settings;
+import org.springframework.vault.util.TestRestTemplateFactory;
+import org.springframework.vault.util.TestWebClientFactory;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link CubbyholeAuthentication} using
+ * {@link AuthenticationStepsOperator}.
+ *
+ * @author Mark Paluch
+ */
+public class CubbyholeAuthenticationOperatorIntegrationTests extends
+ CubbyholeAuthenticationIntegrationTestBase {
+
+ WebClient webClient = TestWebClientFactory.create(Settings.createSslConfiguration());
+
+ @Test
+ public void authenticationStepsShouldCreateWrappedToken() {
+
+ Map wrapInfo = prepareWrappedToken();
+
+ String initialToken = wrapInfo.get("token");
+
+ CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
+ .initialToken(VaultToken.of(initialToken)).wrapped().build();
+ RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
+ .createSslConfiguration());
+
+ CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
+ restTemplate);
+
+ AuthenticationStepsOperator operator = new AuthenticationStepsOperator(
+ authentication.getAuthenticationSteps(), webClient);
+
+ StepVerifier.create(operator.getVaultToken()).consumeNextWith(actual -> {
+
+ assertThat(actual).isNotEqualTo(Settings.token().getToken()).isNotNull();
+ }).verifyComplete();
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationStepsIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationStepsIntegrationTests.java
new file mode 100644
index 00000000..c6128349
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationStepsIntegrationTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.authentication;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+import org.springframework.vault.support.VaultToken;
+import org.springframework.vault.util.Settings;
+import org.springframework.vault.util.TestRestTemplateFactory;
+import org.springframework.web.client.RestTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link CubbyholeAuthentication} using
+ * {@link AuthenticationStepsExecutor}.
+ *
+ * @author Mark Paluch
+ */
+public class CubbyholeAuthenticationStepsIntegrationTests extends
+ CubbyholeAuthenticationIntegrationTestBase {
+
+ @Test
+ public void authenticationStepsShouldCreateWrappedToken() {
+
+ Map wrapInfo = prepareWrappedToken();
+
+ String initialToken = wrapInfo.get("token");
+
+ CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
+ .initialToken(VaultToken.of(initialToken)).wrapped().build();
+ RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
+ .createSslConfiguration());
+
+ CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
+ restTemplate);
+
+ AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
+ authentication.getAuthenticationSteps(), restTemplate);
+
+ VaultToken login = executor.login();
+ assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/ReactiveVaultTemplateGenericIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/ReactiveVaultTemplateGenericIntegrationTests.java
new file mode 100644
index 00000000..5d7d50c3
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/core/ReactiveVaultTemplateGenericIntegrationTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.core;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import reactor.test.StepVerifier;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.vault.util.IntegrationTestSupport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link ReactiveVaultTemplate} using the {@code generic} backend.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class)
+public class ReactiveVaultTemplateGenericIntegrationTests extends IntegrationTestSupport {
+
+ @Autowired
+ private ReactiveVaultOperations vaultOperations;
+
+ @Test
+ public void readShouldReturnAbsentKey() {
+ StepVerifier.create(vaultOperations.read("secret/absent")).verifyComplete();
+ }
+
+ @Test
+ public void readShouldReturnExistingKey() {
+
+ StepVerifier.create(
+ vaultOperations.write("secret/mykey",
+ Collections.singletonMap("hello", "world"))).verifyComplete();
+
+ StepVerifier
+ .create(vaultOperations.read("secret/mykey"))
+ .consumeNextWith(
+ actual -> assertThat(actual.getData()).containsEntry("hello",
+ "world")).verifyComplete();
+
+ }
+
+ @Test
+ public void readShouldReturnNestedPropertiesKey() throws IOException {
+
+ Map map = new ObjectMapper()
+ .readValue(
+ "{ \"hello.array[0]\":\"array-value0\", \"hello.array[1]\":\"array-value1\" }",
+ Map.class);
+
+ StepVerifier.create(vaultOperations.write("secret/mykey", map)).verifyComplete();
+
+ StepVerifier
+ .create(vaultOperations.read("secret/mykey"))
+ .consumeNextWith(
+ actual -> {
+ assertThat(actual.getData()).containsEntry("hello.array[0]",
+ "array-value0");
+ assertThat(actual.getData()).containsEntry("hello.array[1]",
+ "array-value1");
+ }).verifyComplete();
+
+ }
+
+ @Test
+ public void readShouldReturnNestedObjects() throws IOException {
+
+ Map map = new ObjectMapper().readValue(
+ "{ \"array\": [ {\"hello\": \"world\"}, {\"hello1\": \"world1\"} ] }",
+ Map.class);
+ StepVerifier.create(vaultOperations.write("secret/mykey", map)).verifyComplete();
+
+ List> expected = Arrays.asList(
+ Collections.singletonMap("hello", "world"),
+ Collections.singletonMap("hello1", "world1"));
+
+ StepVerifier.create(vaultOperations.read("secret/mykey"))
+ .consumeNextWith(actual -> {
+ assertThat(actual.getData()).containsEntry("array", expected);
+ }).verifyComplete();
+
+ }
+
+ @Test
+ public void readObjectShouldReadDomainClass() {
+
+ Map data = new HashMap<>();
+ data.put("firstname", "Walter");
+ data.put("password", "Secret");
+
+ StepVerifier.create(vaultOperations.write("secret/mykey", data)).verifyComplete();
+
+ StepVerifier.create(vaultOperations.read("secret/mykey", Person.class))
+ .consumeNextWith(actual -> {
+
+ Person person = actual.getData();
+ assertThat(person.getFirstname()).isEqualTo("Walter");
+ assertThat(person.getPassword()).isEqualTo("Secret");
+
+ }).verifyComplete();
+ }
+
+ @Test
+ public void listShouldReturnExistingKey() {
+
+ StepVerifier.create(
+ vaultOperations.write("secret/mykey",
+ Collections.singletonMap("hello", "world"))).verifyComplete();
+
+ StepVerifier.create(vaultOperations.list("secret").collectList())
+ .consumeNextWith(actual -> assertThat(actual).contains("mykey"))
+ .verifyComplete();
+
+ }
+
+ @Test
+ public void deleteShouldRemoveKey() {
+
+ StepVerifier.create(
+ vaultOperations.write("secret/mykey",
+ Collections.singletonMap("hello", "world"))).verifyComplete();
+
+ StepVerifier.create(vaultOperations.delete("secret/mykey")).verifyComplete();
+
+ StepVerifier.create(vaultOperations.read("secret/mykey")).verifyComplete();
+ }
+
+ static class Person {
+
+ String firstname;
+ String password;
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java
index 2869d9b6..9f6d8db6 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java
@@ -19,17 +19,17 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.client.VaultEndpoint;
-import org.springframework.vault.config.AbstractVaultConfiguration;
+import org.springframework.vault.config.AbstractReactiveVaultConfiguration;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.util.Settings;
/**
* Test configuration for Vault integration tests.
- *
+ *
* @author Mark Paluch
*/
@Configuration
-public class VaultIntegrationTestConfiguration extends AbstractVaultConfiguration {
+public class VaultIntegrationTestConfiguration extends AbstractReactiveVaultConfiguration {
@Override
public VaultEndpoint vaultEndpoint() {
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/util/TestWebClientFactory.java b/spring-vault-core/src/test/java/org/springframework/vault/util/TestWebClientFactory.java
new file mode 100644
index 00000000..24386d6c
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/util/TestWebClientFactory.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 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
+ *
+ * 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 org.springframework.vault.util;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.util.Assert;
+import org.springframework.vault.client.ReactiveVaultClients;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.config.ClientHttpConnectorFactory;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.SslConfiguration;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * @author Mark Paluch
+ */
+public class TestWebClientFactory {
+
+ public static final VaultEndpoint TEST_VAULT_ENDPOINT = new VaultEndpoint();
+
+ private static final AtomicReference connectorCache = new AtomicReference();
+
+ /**
+ * Create a new {@link WebClient} using the {@link SslConfiguration}. The underlying
+ * {@link WebClient} is cached. See
+ * {@link ReactiveVaultClients#createWebClient(VaultEndpoint, ClientHttpConnector)} to
+ * create {@link WebClient} for a given {@link ClientHttpConnector}.
+ *
+ * @param sslConfiguration must not be {@literal null}.
+ * @return
+ */
+ public static WebClient create(SslConfiguration sslConfiguration) {
+
+ Assert.notNull(sslConfiguration, "SslConfiguration must not be null!");
+
+ try {
+ initializeClientHttpConnector(sslConfiguration);
+ return ReactiveVaultClients.createWebClient(TEST_VAULT_ENDPOINT,
+ ClientHttpConnectorFactory.create(new ClientOptions(),
+ sslConfiguration));
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void initializeClientHttpConnector(SslConfiguration sslConfiguration)
+ throws Exception {
+
+ if (connectorCache.get() != null) {
+ // return;
+ }
+
+ ClientHttpConnector clientHttpConnector = ClientHttpConnectorFactory.create(
+ new ClientOptions(), sslConfiguration);
+
+ // connectorCache.compareAndSet(null, clientHttpConnector);
+ }
+}