Set parent token id

This commit is contained in:
René Pfeuffer
2018-11-30 09:22:02 +01:00
parent 0b1edaab08
commit 2adcbe5d99
5 changed files with 49 additions and 25 deletions

View File

@@ -80,7 +80,7 @@ public interface AccessToken {
*/ */
Date getExpiration(); Date getExpiration();
Date getRefreshExpiration(); Optional<Date> getRefreshExpiration();
/** /**
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this * Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this

View File

@@ -37,6 +37,8 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import static java.util.Optional.ofNullable;
/** /**
* Jwt implementation of {@link AccessToken}. * Jwt implementation of {@link AccessToken}.
* *
@@ -45,6 +47,8 @@ import java.util.Optional;
*/ */
public final class JwtAccessToken implements AccessToken { public final class JwtAccessToken implements AccessToken {
public static final String REFRESHABLE_UNTIL_CLAIM_KEY = "scm-manager.refreshableUntil";
public static final String PARENT_TOKEN_ID_CLAIM_KEY = "scm-manager.parentTokenId";
private final Claims claims; private final Claims claims;
private final String compact; private final String compact;
@@ -79,8 +83,8 @@ public final class JwtAccessToken implements AccessToken {
} }
@Override @Override
public Date getRefreshExpiration() { public Optional<Date> getRefreshExpiration() {
return claims.get("scm-manager.refreshableUntil", Date.class); return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class));
} }
@Override @Override

View File

@@ -67,6 +67,7 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
private TimeUnit expiresInUnit = TimeUnit.HOURS; private TimeUnit expiresInUnit = TimeUnit.HOURS;
private long refreshableFor = 12; private long refreshableFor = 12;
private TimeUnit refreshableForUnit = TimeUnit.HOURS; private TimeUnit refreshableForUnit = TimeUnit.HOURS;
private String parentKeyId;
private Scope scope = Scope.empty(); private Scope scope = Scope.empty();
private final Map<String,Object> custom = Maps.newHashMap(); private final Map<String,Object> custom = Maps.newHashMap();
@@ -127,6 +128,11 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
return this; return this;
} }
public JwtAccessTokenBuilder parentKey(String parentKeyId) {
this.parentKeyId = parentKeyId;
return this;
}
private String getSubject(){ private String getSubject(){
if (subject == null) { if (subject == null) {
Subject currentSubject = SecurityUtils.getSubject(); Subject currentSubject = SecurityUtils.getSubject();
@@ -162,7 +168,12 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
if (refreshableFor > 0) { if (refreshableFor > 0) {
long refreshExpiration = refreshableForUnit.toMillis(refreshableFor); long refreshExpiration = refreshableForUnit.toMillis(refreshableFor);
claims.put("scm-manager.refreshableUntil", new Date(now.getTime() + refreshExpiration).getTime()); claims.put(JwtAccessToken.REFRESHABLE_UNTIL_CLAIM_KEY, new Date(now.getTime() + refreshExpiration).getTime());
}
if (parentKeyId == null) {
claims.put(JwtAccessToken.PARENT_TOKEN_ID_CLAIM_KEY, id);
} else {
claims.put(JwtAccessToken.PARENT_TOKEN_ID_CLAIM_KEY, parentKeyId);
} }
if ( issuer != null ) { if ( issuer != null ) {

View File

@@ -1,5 +1,8 @@
package sonia.scm.security; package sonia.scm.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import java.time.Clock; import java.time.Clock;
import java.util.Date; import java.util.Date;
@@ -9,6 +12,8 @@ import java.util.concurrent.TimeUnit;
public class JwtAccessTokenRefresher { public class JwtAccessTokenRefresher {
private static final Logger log = LoggerFactory.getLogger(JwtAccessTokenRefresher.class);
private final JwtAccessTokenBuilderFactory builderFactory; private final JwtAccessTokenBuilderFactory builderFactory;
private final JwtAccessTokenRefreshStrategy refreshStrategy; private final JwtAccessTokenRefreshStrategy refreshStrategy;
private final Clock clock; private final Clock clock;
@@ -30,8 +35,13 @@ public class JwtAccessTokenRefresher {
claims.forEach(builder::custom); claims.forEach(builder::custom);
if (canBeRefreshed(oldToken) && shouldBeRefreshed(oldToken)) { if (canBeRefreshed(oldToken) && shouldBeRefreshed(oldToken)) {
Optional<Object> parentTokenId = oldToken.getCustom("scm-manager.parentTokenId");
if (!parentTokenId.isPresent()) {
log.warn("no parent token id found in token; could not refresh");
return Optional.empty();
}
builder.expiresIn(1, TimeUnit.HOURS); builder.expiresIn(1, TimeUnit.HOURS);
// builder.custom("scm-manager.parentTokenId") builder.parentKey(parentTokenId.get().toString());
return Optional.of(builder.build()); return Optional.of(builder.build());
} else { } else {
return Optional.empty(); return Optional.empty();
@@ -47,8 +57,7 @@ public class JwtAccessTokenRefresher {
} }
private boolean tokenCanBeRefreshed(JwtAccessToken oldToken) { private boolean tokenCanBeRefreshed(JwtAccessToken oldToken) {
Date refreshExpiration = oldToken.getRefreshExpiration(); return oldToken.getRefreshExpiration().map(this::isAfterNow).orElse(false);
return refreshExpiration != null && isAfterNow(refreshExpiration);
} }
private boolean tokenIsValid(JwtAccessToken oldToken) { private boolean tokenIsValid(JwtAccessToken oldToken) {

View File

@@ -39,6 +39,8 @@ public class JwtAccessTokenRefresherTest {
@Mock @Mock
private Clock clock; private Clock clock;
private KeyGenerator keyGenerator = () -> "key";
private JwtAccessTokenRefresher refresher; private JwtAccessTokenRefresher refresher;
private JwtAccessTokenBuilder tokenBuilder; private JwtAccessTokenBuilder tokenBuilder;
@@ -49,11 +51,16 @@ public class JwtAccessTokenRefresherTest {
SecureKey secureKey = new SecureKey(bytes, System.currentTimeMillis()); SecureKey secureKey = new SecureKey(bytes, System.currentTimeMillis());
when(keyResolver.getSecureKey(any())).thenReturn(secureKey); when(keyResolver.getSecureKey(any())).thenReturn(secureKey);
JwtAccessTokenBuilderFactory builderFactory = new JwtAccessTokenBuilderFactory(new DefaultKeyGenerator(), keyResolver, Collections.emptySet()); JwtAccessTokenBuilderFactory builderFactory = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, Collections.emptySet());
refresher = new JwtAccessTokenRefresher(builderFactory, refreshStrategy, clock); refresher = new JwtAccessTokenRefresher(builderFactory, refreshStrategy, clock);
tokenBuilder = builderFactory.create(); tokenBuilder = builderFactory.create();
when(clock.instant()).thenAnswer(invocationOnMock -> Instant.now()); when(clock.instant()).thenAnswer(invocationOnMock -> Instant.now());
when(refreshStrategy.shouldBeRefreshed(any())).thenReturn(true); when(refreshStrategy.shouldBeRefreshed(any())).thenReturn(true);
// set default expiration values
tokenBuilder
.expiresIn(5, MINUTES)
.refreshableFor(10, MINUTES);
} }
@Test @Test
@@ -69,12 +76,9 @@ public class JwtAccessTokenRefresherTest {
@Test @Test
public void shouldNotRefreshTokenWhenTokenExpired() { public void shouldNotRefreshTokenWhenTokenExpired() {
Instant oneMinuteAgo = Instant.now().plus(ofMinutes(2)); Instant afterNormalExpiration = Instant.now().plus(ofMinutes(6));
when(clock.instant()).thenReturn(oneMinuteAgo); when(clock.instant()).thenReturn(afterNormalExpiration);
JwtAccessToken oldToken = tokenBuilder JwtAccessToken oldToken = tokenBuilder.build();
.expiresIn(1, MINUTES)
.refreshableFor(5, MINUTES)
.build();
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken); Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
@@ -83,10 +87,9 @@ public class JwtAccessTokenRefresherTest {
@Test @Test
public void shouldNotRefreshTokenWhenRefreshExpired() { public void shouldNotRefreshTokenWhenRefreshExpired() {
Instant oneMinuteAgo = Instant.now().plus(ofMinutes(2)); Instant afterRefreshExpiration = Instant.now().plus(ofMinutes(2));
when(clock.instant()).thenReturn(oneMinuteAgo); when(clock.instant()).thenReturn(afterRefreshExpiration);
JwtAccessToken oldToken = tokenBuilder JwtAccessToken oldToken = tokenBuilder
.expiresIn(5, MINUTES)
.refreshableFor(1, MINUTES) .refreshableFor(1, MINUTES)
.build(); .build();
@@ -97,9 +100,7 @@ public class JwtAccessTokenRefresherTest {
@Test @Test
public void shouldNotRefreshTokenWhenStrategyDoesNotSaySo() { public void shouldNotRefreshTokenWhenStrategyDoesNotSaySo() {
JwtAccessToken oldToken = tokenBuilder JwtAccessToken oldToken = tokenBuilder.build();
.refreshableFor(10, MINUTES)
.build();
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(false); when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(false);
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken); Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
@@ -109,14 +110,13 @@ public class JwtAccessTokenRefresherTest {
@Test @Test
public void shouldRefreshTokenWithEnabledRefresh() { public void shouldRefreshTokenWithEnabledRefresh() {
JwtAccessToken oldToken = tokenBuilder JwtAccessToken oldToken = tokenBuilder.build();
.expiresIn(1, MINUTES)
.refreshableFor(1, MINUTES)
.build();
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true); when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true);
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken); Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
assertThat(refreshedToken).isNotEmpty(); assertThat(refreshedToken).isNotEmpty();
assertThat(refreshedToken.get().getClaims())
.containsEntry(JwtAccessToken.PARENT_TOKEN_ID_CLAIM_KEY, "key");
} }
} }