Skip to content

Commit 5f28579

Browse files
committed
Do not revoke batch tokens.
We now track the token type and no longer attempt revoking batch tokens. Also, refactor code duplicates to use LoginTokenUtil. Closes gh-764
1 parent 4de0ac6 commit 5f28579

File tree

10 files changed

+363
-52
lines changed

10 files changed

+363
-52
lines changed

spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@
4343
* <p>
4444
* This {@link SessionManager} also implements {@link DisposableBean} to revoke the
4545
* {@link LoginToken} once it's not required anymore. Token revocation will stop regular
46-
* token refresh. Tokens are only revoked only if the associated
47-
* {@link ClientAuthentication} returns a {@link LoginToken}.
46+
* token refresh. Tokens are only revoked if the associated {@link ClientAuthentication}
47+
* returns a {@link LoginToken#isServiceToken() service token}.
4848
* <p>
4949
* If Token renewal runs into a client-side error, it assumes the token was
5050
* revoked/expired. It discards the token state so the next attempt will lead to another
5151
* login attempt.
5252
* <p>
53-
* By default, {@link VaultToken} are looked up in Vault to determine renewability and the
54-
* remaining TTL, see {@link #setTokenSelfLookupEnabled(boolean)}.
53+
* By default, {@link VaultToken} are looked up in Vault to determine renewability,
54+
* remaining TTL, accessor and type, see {@link #setTokenSelfLookupEnabled(boolean)}.
5555
* <p>
5656
* The session manager dispatches authentication events to {@link AuthenticationListener}
5757
* and {@link AuthenticationErrorListener}. Event notifications are dispatched either on
@@ -386,7 +386,12 @@ public VaultToken getToken() {
386386
}
387387

388388
public boolean isRevocable() {
389-
return this.revocable;
389+
390+
if (token instanceof LoginToken && ((LoginToken) token).isServiceToken()) {
391+
return this.revocable;
392+
}
393+
394+
return false;
390395
}
391396

392397
}

spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java

Lines changed: 179 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
package org.springframework.vault.authentication;
1717

1818
import java.time.Duration;
19+
import java.util.Arrays;
1920

21+
import org.springframework.lang.Nullable;
2022
import org.springframework.util.Assert;
2123
import org.springframework.vault.support.VaultToken;
2224

@@ -34,12 +36,29 @@ public class LoginToken extends VaultToken {
3436
*/
3537
private final Duration leaseDuration;
3638

37-
private LoginToken(char[] token, Duration duration, boolean renewable) {
39+
@Nullable
40+
private final String accessor;
41+
42+
@Nullable
43+
private final String type;
44+
45+
private LoginToken(char[] token, Duration duration, boolean renewable, @Nullable String accessor,
46+
@Nullable String type) {
3847

3948
super(token);
4049

4150
this.leaseDuration = duration;
4251
this.renewable = renewable;
52+
this.accessor = accessor;
53+
this.type = type;
54+
}
55+
56+
/**
57+
* @return a new {@link LoginTokenBuilder}.
58+
* @since 2.3.3
59+
*/
60+
public static LoginTokenBuilder builder() {
61+
return new LoginTokenBuilder();
4362
}
4463

4564
/**
@@ -95,7 +114,7 @@ public static LoginToken of(char[] token, long leaseDurationSeconds) {
95114
Assert.isTrue(token.length > 0, "Token must not be empty");
96115
Assert.isTrue(leaseDurationSeconds >= 0, "Lease duration must not be negative");
97116

98-
return new LoginToken(token, Duration.ofSeconds(leaseDurationSeconds), false);
117+
return new LoginToken(token, Duration.ofSeconds(leaseDurationSeconds), false, null, null);
99118
}
100119

101120
/**
@@ -113,7 +132,7 @@ public static LoginToken of(char[] token, Duration leaseDuration) {
113132
Assert.notNull(leaseDuration, "Lease duration must not be null");
114133
Assert.isTrue(!leaseDuration.isNegative(), "Lease duration must not be negative");
115134

116-
return new LoginToken(token, leaseDuration, false);
135+
return new LoginToken(token, leaseDuration, false, null, null);
117136
}
118137

119138
/**
@@ -149,7 +168,7 @@ public static LoginToken renewable(char[] token, long leaseDurationSeconds) {
149168
Assert.isTrue(token.length > 0, "Token must not be empty");
150169
Assert.isTrue(leaseDurationSeconds >= 0, "Lease duration must not be negative");
151170

152-
return new LoginToken(token, Duration.ofSeconds(leaseDurationSeconds), true);
171+
return new LoginToken(token, Duration.ofSeconds(leaseDurationSeconds), true, null, null);
153172
}
154173

155174
/**
@@ -180,7 +199,7 @@ public static LoginToken renewable(char[] token, Duration leaseDuration) {
180199
Assert.notNull(leaseDuration, "Lease duration must not be null");
181200
Assert.isTrue(!leaseDuration.isNegative(), "Lease duration must not be negative");
182201

183-
return new LoginToken(token, leaseDuration, true);
202+
return new LoginToken(token, leaseDuration, true, null, null);
184203
}
185204

186205
/**
@@ -197,14 +216,169 @@ public boolean isRenewable() {
197216
return this.renewable;
198217
}
199218

219+
/**
220+
* @return the token accessor.
221+
* @since 2.3.3
222+
*/
223+
@Nullable
224+
public String getAccessor() {
225+
return accessor;
226+
}
227+
228+
/**
229+
* @return the token type.
230+
* @since 2.3.3
231+
* @see #isBatchToken()
232+
* @see #isServiceToken())
233+
*/
234+
@Nullable
235+
public String getType() {
236+
return type;
237+
}
238+
239+
/**
240+
* @return {@literal true} if the token is a batch token.
241+
* @since 2.3.3
242+
*/
243+
public boolean isBatchToken() {
244+
return "batch".equals(this.type);
245+
}
246+
247+
/**
248+
* @return {@literal true} if the token is a service token.
249+
* @since 2.3.3
250+
*/
251+
public boolean isServiceToken() {
252+
return this.type == null || "service".equals(this.type);
253+
}
254+
200255
@Override
201256
public String toString() {
202257
StringBuffer sb = new StringBuffer();
203258
sb.append(getClass().getSimpleName());
204259
sb.append(" [renewable=").append(this.renewable);
205260
sb.append(", leaseDuration=").append(this.leaseDuration);
261+
sb.append(", type=").append(this.type);
206262
sb.append(']');
207263
return sb.toString();
208264
}
209265

266+
/**
267+
* Builder for {@link LoginToken}.
268+
*
269+
* @since 2.3.3
270+
*/
271+
public static class LoginTokenBuilder {
272+
273+
@Nullable
274+
private char[] token;
275+
276+
private boolean renewable;
277+
278+
/**
279+
* Duration in seconds.
280+
*/
281+
private Duration leaseDuration = Duration.ZERO;
282+
283+
@Nullable
284+
private String accessor;
285+
286+
@Nullable
287+
private String type;
288+
289+
private LoginTokenBuilder() {
290+
}
291+
292+
/**
293+
* Configure the token value. This is a required builder property. Without this
294+
* property, you cannot {@link #build()} a {@link LoginToken}.
295+
* @param token must not be empty or {@literal null}.
296+
* @return {@code this} {@link LoginTokenBuilder}.
297+
*/
298+
public LoginTokenBuilder token(String token) {
299+
300+
Assert.hasText(token, "Token must not be empty");
301+
302+
return token(token.toCharArray());
303+
}
304+
305+
/**
306+
* Configure the token value. This is a required builder property. Without this
307+
* property, you cannot {@link #build()} a {@link LoginToken}.
308+
* @param token must not be empty or {@literal null}.
309+
* @return {@code this} {@link LoginTokenBuilder}.
310+
*/
311+
public LoginTokenBuilder token(char[] token) {
312+
313+
Assert.notNull(token, "Token must not be null");
314+
Assert.isTrue(token.length > 0, "Token must not be empty");
315+
316+
this.token = token;
317+
return this;
318+
}
319+
320+
/**
321+
* Configure whether the token is renewable.
322+
* @param renewable
323+
* @return {@code this} {@link LoginTokenBuilder}.
324+
*/
325+
public LoginTokenBuilder renewable(boolean renewable) {
326+
327+
this.renewable = renewable;
328+
return this;
329+
}
330+
331+
/**
332+
* Configure the lease duration.
333+
* @param leaseDuration must not be {@literal null}.
334+
* @return {@code this} {@link LoginTokenBuilder}.
335+
*/
336+
public LoginTokenBuilder leaseDuration(Duration leaseDuration) {
337+
338+
Assert.notNull(leaseDuration, "Lease duration must not be empty");
339+
340+
this.leaseDuration = leaseDuration;
341+
return this;
342+
}
343+
344+
/**
345+
* Configure the token accessor.
346+
* @param accessor must not be empty or {@literal null}.
347+
* @return {@code this} {@link LoginTokenBuilder}.
348+
*/
349+
public LoginTokenBuilder accessor(String accessor) {
350+
351+
Assert.hasText(accessor, "Token accessor must not be empty");
352+
353+
this.accessor = accessor;
354+
return this;
355+
}
356+
357+
/**
358+
* Configure the token type.
359+
* @param type must not be empty or {@literal null}.
360+
* @return {@code this} {@link LoginTokenBuilder}.
361+
*/
362+
public LoginTokenBuilder type(String type) {
363+
364+
Assert.hasText(type, "Token type must not be empty");
365+
366+
this.type = type;
367+
return this;
368+
}
369+
370+
/**
371+
* Build a new {@link LoginToken} instance. {@link #token} must be configured.
372+
* @return a new {@link LoginToken} instance.
373+
*/
374+
public LoginToken build() {
375+
376+
Assert.notNull(token, "Token must not be null");
377+
378+
return new LoginToken(Arrays.copyOf(this.token, this.token.length), this.leaseDuration, this.renewable,
379+
this.accessor, this.type);
380+
}
381+
382+
}
383+
210384
}

spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenAdapter.java

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@
1515
*/
1616
package org.springframework.vault.authentication;
1717

18-
import java.time.Duration;
1918
import java.util.Map;
2019

2120
import org.springframework.http.HttpEntity;
2221
import org.springframework.http.HttpMethod;
2322
import org.springframework.http.ResponseEntity;
24-
import org.springframework.lang.Nullable;
2523
import org.springframework.util.Assert;
2624
import org.springframework.vault.VaultException;
2725
import org.springframework.vault.client.VaultHttpHeaders;
@@ -35,7 +33,7 @@
3533
/**
3634
* Adapts tokens created by a {@link ClientAuthentication} to a {@link LoginToken}. Allows
3735
* decoration of a {@link ClientAuthentication} object to perform a self-lookup after
38-
* token retrieval to obtain the remaining TTL and renewability.
36+
* token retrieval to obtain the remaining TTL, renewability, accessor and token type.
3937
* <p>
4038
* Using this adapter decrements the usage counter for the created token.
4139
*
@@ -77,14 +75,7 @@ static LoginToken augmentWithSelfLookup(RestOperations restOperations, VaultToke
7775

7876
Map<String, Object> data = lookupSelf(restOperations, token);
7977

80-
Boolean renewable = (Boolean) data.get("renewable");
81-
Number ttl = (Number) data.get("ttl");
82-
83-
if (renewable != null && renewable) {
84-
return LoginToken.renewable(token.toCharArray(), getLeaseDuration(ttl));
85-
}
86-
87-
return LoginToken.of(token.toCharArray(), getLeaseDuration(ttl));
78+
return LoginTokenUtil.from(token.toCharArray(), data);
8879
}
8980

9081
private static Map<String, Object> lookupSelf(RestOperations restOperations, VaultToken token) {
@@ -106,8 +97,4 @@ private static Map<String, Object> lookupSelf(RestOperations restOperations, Vau
10697
}
10798
}
10899

109-
static Duration getLeaseDuration(@Nullable Number ttl) {
110-
return ttl == null ? Duration.ZERO : Duration.ofSeconds(ttl.longValue());
111-
}
112-
113100
}

spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Map;
2020

2121
import org.springframework.util.Assert;
22+
import org.springframework.util.StringUtils;
2223

2324
/**
2425
* Utility class for {@link LoginToken}.
@@ -51,26 +52,43 @@ static LoginToken from(Map<String, Object> auth) {
5152
* @return the {@link LoginToken}
5253
* @since 2.0
5354
*/
54-
static LoginToken from(char[] token, Map<String, Object> auth) {
55+
static LoginToken from(char[] token, Map<String, ?> auth) {
5556

5657
Assert.notNull(auth, "Authentication must not be null");
5758

5859
Boolean renewable = (Boolean) auth.get("renewable");
5960
Number leaseDuration = (Number) auth.get("lease_duration");
61+
String accessor = (String) auth.get("accessor");
62+
String type = (String) auth.get("type");
6063

6164
if (leaseDuration == null) {
6265
leaseDuration = (Number) auth.get("ttl");
6366
}
6467

65-
if (renewable != null && renewable) {
66-
return LoginToken.renewable(token, Duration.ofSeconds(leaseDuration.longValue()));
68+
if (type == null) {
69+
type = (String) auth.get("token_type");
70+
}
71+
72+
LoginToken.LoginTokenBuilder builder = LoginToken.builder();
73+
builder.token(token);
74+
75+
if (StringUtils.hasText(accessor)) {
76+
builder.accessor(accessor);
6777
}
6878

6979
if (leaseDuration != null) {
70-
return LoginToken.of(token, Duration.ofSeconds(leaseDuration.longValue()));
80+
builder.leaseDuration(Duration.ofSeconds(leaseDuration.longValue()));
81+
}
82+
83+
if (renewable != null) {
84+
builder.renewable(renewable);
85+
}
86+
87+
if (StringUtils.hasText(type)) {
88+
builder.type(type);
7189
}
7290

73-
return LoginToken.of(token);
91+
return builder.build();
7492
}
7593

7694
}

0 commit comments

Comments
 (0)