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 index 3ec02e39..3a719ddf 100644 --- 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 @@ -131,7 +131,10 @@ public class AuthenticationStepsOperator implements VaultTokenSupplier { String.format( "Cannot retrieve VaultToken from authentication chain. Got instead %s", stateObject)); - }); + }) + .onErrorMap( + t -> new VaultException( + "Cannot retrieve VaultToken from authentication chain", t)); } private static Object doSupplierStep(SupplierStep supplierStep) { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java index 144676b8..d0baedf2 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java @@ -15,6 +15,7 @@ */ package org.springframework.vault.authentication; +import java.time.Duration; import java.util.Map; import lombok.experimental.UtilityClass; @@ -40,15 +41,35 @@ class LoginTokenUtil { Assert.notNull(auth, "Authentication must not be null"); String token = (String) auth.get("client_token"); + + return from(token.toCharArray(), auth); + } + + /** + * Construct a {@link LoginToken} from an auth response. + * + * @param auth {@link Map} holding a login response. + * @return the {@link LoginToken} + * @since 2.0 + */ + static LoginToken from(char[] token, Map auth) { + + Assert.notNull(auth, "Authentication must not be null"); + Boolean renewable = (Boolean) auth.get("renewable"); Number leaseDuration = (Number) auth.get("lease_duration"); + if (leaseDuration == null) { + leaseDuration = (Number) auth.get("ttl"); + } + if (renewable != null && renewable) { - return LoginToken.renewable(token, leaseDuration.longValue()); + return LoginToken.renewable(token, + Duration.ofSeconds(leaseDuration.longValue())); } if (leaseDuration != null) { - return LoginToken.of(token, leaseDuration.longValue()); + return LoginToken.of(token, Duration.ofSeconds(leaseDuration.longValue())); } return LoginToken.of(token); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java index 5ff1a32e..d087840c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/TokenAuthentication.java @@ -15,9 +15,17 @@ */ package org.springframework.vault.authentication; +import java.time.Duration; + +import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.vault.authentication.AuthenticationSteps.HttpRequest; +import org.springframework.vault.client.VaultHttpHeaders; +import org.springframework.vault.support.VaultResponse; import org.springframework.vault.support.VaultToken; +import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.get; + /** * Static Token-based {@link ClientAuthentication} method. * @@ -59,13 +67,27 @@ public class TokenAuthentication implements ClientAuthentication, * {@link VaultToken}. * * @param token must not be {@literal null}. + * @param selfLookup {@literal true} to perform a self-lookup using the given + * {@link VaultToken}. Self-lookup will create a {@link LoginToken} and provide + * renewability and TTL. * @return {@link AuthenticationSteps} for token authentication. * @since 2.0 */ - public static AuthenticationSteps createAuthenticationSteps(VaultToken token) { + public static AuthenticationSteps createAuthenticationSteps(VaultToken token, + boolean selfLookup) { Assert.notNull(token, "VaultToken must not be null"); + if (selfLookup) { + + HttpRequest httpRequest = get("auth/token/lookup-self").with( + VaultHttpHeaders.from(token)).as(VaultResponse.class); + + return AuthenticationSteps.fromHttpRequest(httpRequest).login( + response -> LoginTokenUtil.from(token.toCharArray(), + response.getRequiredData())); + } + return AuthenticationSteps.just(token); } @@ -76,6 +98,10 @@ public class TokenAuthentication implements ClientAuthentication, @Override public AuthenticationSteps getAuthenticationSteps() { - return createAuthenticationSteps(this.token); + return createAuthenticationSteps(this.token, false); + } + + private static Duration getLeaseDuration(@Nullable Number ttl) { + return ttl == null ? Duration.ZERO : Duration.ofSeconds(ttl.longValue()); } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationIntegrationTestBase.java new file mode 100644 index 00000000..15d96ff9 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationIntegrationTestBase.java @@ -0,0 +1,27 @@ +/* + * 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.springframework.vault.util.IntegrationTestSupport; + +/** + * Integration test base class for {@link TokenAuthentication} tests. + * + * @author Mark Paluch + */ +public abstract class TokenAuthenticationIntegrationTestBase extends + IntegrationTestSupport { +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationOperatorIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationOperatorIntegrationTests.java new file mode 100644 index 00000000..707a9e7e --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationOperatorIntegrationTests.java @@ -0,0 +1,89 @@ +/* + * 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.time.Duration; + +import org.junit.Test; +import reactor.test.StepVerifier; + +import org.springframework.vault.VaultException; +import org.springframework.vault.support.VaultToken; +import org.springframework.vault.support.VaultTokenRequest; +import org.springframework.vault.util.Settings; +import org.springframework.vault.util.TestWebClientFactory; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link TokenAuthentication} using + * {@link AuthenticationStepsOperator}. + * + * @author Mark Paluch + */ +public class TokenAuthenticationOperatorIntegrationTests extends + TokenAuthenticationIntegrationTestBase { + + WebClient webClient = TestWebClientFactory.create(Settings.createSslConfiguration()); + + @Test + public void shouldSelfLookup() { + + VaultTokenRequest tokenRequest = VaultTokenRequest.builder() + .ttl(Duration.ofSeconds(60)).renewable().numUses(1).build(); + + VaultToken token = prepare().getVaultOperations().opsForToken() + .create(tokenRequest).getToken(); + + AuthenticationStepsOperator operator = new AuthenticationStepsOperator( + TokenAuthentication.createAuthenticationSteps(token, true), webClient); + + StepVerifier + .create(operator.getVaultToken()) + .consumeNextWith( + actual -> { + + assertThat(actual).isInstanceOf(LoginToken.class); + + LoginToken loginToken = (LoginToken) actual; + + assertThat(loginToken.getLeaseDuration()).isBetween( + Duration.ofSeconds(40), Duration.ofSeconds(60)); + assertThat(loginToken.isRenewable()).isTrue(); + + }).verifyComplete(); + } + + @Test + public void shouldFailDuringSelfLookup() { + + VaultTokenRequest tokenRequest = VaultTokenRequest.builder() + .ttl(Duration.ofSeconds(60)).renewable().numUses(1).build(); + + VaultToken token = prepare().getVaultOperations().opsForToken() + .create(tokenRequest).getToken(); + + AuthenticationStepsOperator operator = new AuthenticationStepsOperator( + TokenAuthentication.createAuthenticationSteps(token, true), webClient); + + // first usage + StepVerifier.create(operator.getVaultToken()).expectNextCount(1).verifyComplete(); + + StepVerifier.create(operator.getVaultToken()).expectError(VaultException.class) + .verify(); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationStepsIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationStepsIntegrationTests.java new file mode 100644 index 00000000..ba29d11a --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/TokenAuthenticationStepsIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * 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.time.Duration; + +import org.junit.Test; + +import org.springframework.vault.VaultException; +import org.springframework.vault.support.VaultToken; +import org.springframework.vault.support.VaultTokenRequest; +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.assertThatThrownBy; + +/** + * Integration tests for {@link TokenAuthentication} using + * {@link AuthenticationStepsExecutor}. + * + * @author Mark Paluch + */ +public class TokenAuthenticationStepsIntegrationTests extends + TokenAuthenticationIntegrationTestBase { + + RestTemplate restTemplate = TestRestTemplateFactory.create(Settings + .createSslConfiguration()); + + @Test + public void shouldSelfLookup() { + + VaultTokenRequest tokenRequest = VaultTokenRequest.builder() + .ttl(Duration.ofSeconds(60)).renewable().numUses(1).build(); + + VaultToken token = prepare().getVaultOperations().opsForToken() + .create(tokenRequest).getToken(); + + AuthenticationStepsExecutor operator = new AuthenticationStepsExecutor( + TokenAuthentication.createAuthenticationSteps(token, true), restTemplate); + + VaultToken login = operator.login(); + assertThat(login).isInstanceOf(LoginToken.class); + + LoginToken loginToken = (LoginToken) login; + + assertThat(loginToken.getLeaseDuration()).isBetween(Duration.ofSeconds(40), + Duration.ofSeconds(60)); + assertThat(loginToken.isRenewable()).isTrue(); + } + + @Test + public void shouldFailDuringSelfLookup() { + + VaultTokenRequest tokenRequest = VaultTokenRequest.builder() + .ttl(Duration.ofSeconds(60)).renewable().numUses(1).build(); + + VaultToken token = prepare().getVaultOperations().opsForToken() + .create(tokenRequest).getToken(); + + AuthenticationStepsExecutor operator = new AuthenticationStepsExecutor( + TokenAuthentication.createAuthenticationSteps(token, true), restTemplate); + + operator.login(); + assertThatThrownBy(operator::login).isInstanceOf(VaultException.class); + } +}