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:
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user