Add LoginTokenAdapter for LoginToken TTL/renewability lookup.

We now support LoginToken creation via LoginTokenAdapter that decorates a ClientAuthentication object.

Static tokens/tokens via lookup usually don't carry their remaining TTL and renewability details so these properties are required to be looked up for token renewal. LoginTokenAdapter performs a self-lookup with the token retrieved from the decorated ClientAuthentication to obtain TTL and renewability. Self-lookup increments the token usage counter.

Fixes gh-94.
This commit is contained in:
Mark Paluch
2017-05-25 17:43:36 +02:00
parent 978eb64f8b
commit 04baf2cfa6
3 changed files with 198 additions and 43 deletions

View File

@@ -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<VaultResponse> entity = restOperations.exchange(
options.getPath(), HttpMethod.GET,
new HttpEntity<Object>(
VaultHttpHeaders.from(options.getInitialToken())),
VaultResponse.class);
options.getPath(),
HttpMethod.GET,
new HttpEntity<Object>(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<String, Object> 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<String, Object> lookupSelf(VaultToken token) {
try {
ResponseEntity<VaultResponse> entity = restOperations.exchange(
"/auth/token/lookup-self", HttpMethod.GET,
new HttpEntity<Object>(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<String, Object> 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()));
}
}

View File

@@ -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.
* <p>
* 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<String, Object> 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<String, Object> lookupSelf(VaultToken token) {
try {
ResponseEntity<VaultResponse> entity = restOperations.exchange(
"/auth/token/lookup-self", HttpMethod.GET, new HttpEntity<Object>(
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())));
}
}
}

View File

@@ -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);
}
}