diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java index ab74d59f..e6e3275a 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java @@ -165,7 +165,10 @@ public class CubbyholeAuthentication implements ClientAuthentication { VaultToken tokenToUse = getToken(data); if (shouldEnhanceTokenWithSelfLookup(tokenToUse)) { - tokenToUse = augmentWithSelfLookup(tokenToUse); + + LoginTokenAdapter adapter = new LoginTokenAdapter(new TokenAuthentication( + tokenToUse), restOperations); + tokenToUse = adapter.login(); } logger.debug("Login successful using Cubbyhole authentication"); @@ -177,10 +180,10 @@ public class CubbyholeAuthentication implements ClientAuthentication { try { ResponseEntity entity = restOperations.exchange( - options.getPath(), HttpMethod.GET, - new HttpEntity( - VaultHttpHeaders.from(options.getInitialToken())), - VaultResponse.class); + options.getPath(), + HttpMethod.GET, + new HttpEntity(VaultHttpHeaders.from(options + .getInitialToken())), VaultResponse.class); return entity.getBody().getData(); } @@ -209,38 +212,6 @@ public class CubbyholeAuthentication implements ClientAuthentication { return true; } - private VaultToken augmentWithSelfLookup(VaultToken token) { - - Map data = lookupSelf(token); - - Boolean renewable = (Boolean) data.get("renewable"); - Number ttl = (Number) data.get("ttl"); - - if (renewable != null && renewable) { - return LoginToken.renewable(token.toCharArray(), - ttl == null ? 0 : ttl.longValue()); - } - - return LoginToken.of(token.toCharArray(), ttl == null ? 0 : ttl.longValue()); - } - - private Map lookupSelf(VaultToken token) { - - try { - ResponseEntity entity = restOperations.exchange( - "/auth/token/lookup-self", HttpMethod.GET, - new HttpEntity(VaultHttpHeaders.from(token)), - VaultResponse.class); - - return entity.getBody().getData(); - } - catch (HttpStatusCodeException e) { - throw new VaultException(String.format( - "Cannot self-lookup Token from Cubbyhole: %s %s", e.getStatusCode(), - VaultResponses.getError(e.getResponseBodyAsString()))); - } - } - private VaultToken getToken(Map data) { if (options.isWrappedToken()) { @@ -251,9 +222,10 @@ public class CubbyholeAuthentication implements ClientAuthentication { } if (data == null || data.isEmpty()) { - throw new VaultException(String.format( - "Cannot retrieve Token from Cubbyhole: Response at %s does not contain a token", - options.getPath())); + throw new VaultException( + String.format( + "Cannot retrieve Token from Cubbyhole: Response at %s does not contain a token", + options.getPath())); } if (data.size() == 1) { @@ -261,8 +233,9 @@ public class CubbyholeAuthentication implements ClientAuthentication { return VaultToken.of(token); } - throw new VaultException(String.format( - "Cannot retrieve Token from Cubbyhole: Response at %s does not contain an unique token", - options.getPath())); + throw new VaultException( + String.format( + "Cannot retrieve Token from Cubbyhole: Response at %s does not contain an unique token", + options.getPath())); } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenAdapter.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenAdapter.java new file mode 100644 index 00000000..1b665cf9 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenAdapter.java @@ -0,0 +1,100 @@ +/* + * 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.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.vault.VaultException; +import org.springframework.vault.client.VaultHttpHeaders; +import org.springframework.vault.client.VaultResponses; +import org.springframework.vault.support.VaultResponse; +import org.springframework.vault.support.VaultToken; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestOperations; + +/** + * Adapts tokens created by a {@link ClientAuthentication} to a {@link LoginToken}. Allows + * decoration of a {@link ClientAuthentication} object to perform a self-lookup after + * token retrieval to obtain the remaining TTL and renewability. + *

+ * Using this adapter decrements the usage counter for the created token. + * + * @author Mark Paluch + * @since 1.1 + * @see LoginToken + */ +public class LoginTokenAdapter implements ClientAuthentication { + + private final ClientAuthentication delegate; + + private final RestOperations restOperations; + + /** + * Create a new {@link LoginTokenAdapter} given {@link ClientAuthentication} to + * decorate and {@link RestOperations}. + * + * @param delegate must not be {@literal null}. + * @param restOperations must not be {@literal null}. + */ + public LoginTokenAdapter(ClientAuthentication delegate, RestOperations restOperations) { + + Assert.notNull(delegate, "ClientAuthentication delegate must not be null"); + Assert.notNull(restOperations, "RestOperations must not be null"); + + this.delegate = delegate; + this.restOperations = restOperations; + } + + @Override + public LoginToken login() throws VaultException { + return augmentWithSelfLookup(delegate.login()); + } + + private LoginToken augmentWithSelfLookup(VaultToken token) { + + Map data = lookupSelf(token); + + Boolean renewable = (Boolean) data.get("renewable"); + Number ttl = (Number) data.get("ttl"); + + if (renewable != null && renewable) { + return LoginToken.renewable(token.toCharArray(), + ttl == null ? 0 : ttl.longValue()); + } + + return LoginToken.of(token.toCharArray(), ttl == null ? 0 : ttl.longValue()); + } + + private Map lookupSelf(VaultToken token) { + + try { + ResponseEntity entity = restOperations.exchange( + "/auth/token/lookup-self", HttpMethod.GET, new HttpEntity( + VaultHttpHeaders.from(token)), VaultResponse.class); + + return entity.getBody().getData(); + } + catch (HttpStatusCodeException e) { + throw new VaultException(String.format( + "Cannot self-lookup Token from Cubbyhole: %s %s", e.getStatusCode(), + VaultResponses.getError(e.getResponseBodyAsString()))); + } + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LoginTokenAdapterUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LoginTokenAdapterUnitTests.java new file mode 100644 index 00000000..0544e826 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LoginTokenAdapterUnitTests.java @@ -0,0 +1,82 @@ +/* + * 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 org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.vault.client.VaultHttpHeaders; +import org.springframework.vault.client.VaultClients.PrefixAwareUriTemplateHandler; +import org.springframework.vault.support.VaultToken; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Unit tests for {@link LoginTokenAdapter}. + * + * @author Mark Paluch + */ +public class LoginTokenAdapterUnitTests { + + private RestTemplate restTemplate; + private MockRestServiceServer mockRest; + + @Before + public void before() throws Exception { + + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(new PrefixAwareUriTemplateHandler()); + + this.mockRest = MockRestServiceServer.createServer(restTemplate); + this.restTemplate = restTemplate; + } + + @Test + public void shouldSelfLookupToken() throws Exception { + + mockRest.expect(requestTo("/auth/token/lookup-self")) + .andExpect(method(HttpMethod.GET)) + .andExpect( + header(VaultHttpHeaders.VAULT_TOKEN, + "5e6332cf-f003-6369-8cba-5bce2330f6cc")) + .andRespond( + withSuccess().contentType(MediaType.APPLICATION_JSON).body( + "{\"data\": {\n" + " \"creation_ttl\": 600,\n" + + " \"renewable\": false,\n" + + " \"ttl\": 456} }")); + + LoginTokenAdapter adapter = new LoginTokenAdapter(new TokenAuthentication( + "5e6332cf-f003-6369-8cba-5bce2330f6cc"), restTemplate); + + VaultToken login = adapter.login(); + + assertThat(login).isInstanceOf(LoginToken.class); + assertThat(login.getToken()).isEqualTo("5e6332cf-f003-6369-8cba-5bce2330f6cc"); + + LoginToken loginToken = (LoginToken) login; + assertThat(loginToken.isRenewable()).isFalse(); + assertThat(loginToken.getLeaseDuration()).isEqualTo(456); + } + +}