mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 16:05:44 +01:00
Set parent token id
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user