mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 17:26:22 +01:00
merge
This commit is contained in:
@@ -61,7 +61,6 @@ import sonia.scm.user.UserDAO;
|
||||
import sonia.scm.user.UserTestData;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -71,6 +70,7 @@ import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link BearerRealm}.
|
||||
@@ -256,12 +256,6 @@ private String createCompactToken(String subject, SecureKey key) {
|
||||
.compact();
|
||||
}
|
||||
|
||||
private SecureKey createSecureKey() {
|
||||
byte[] bytes = new byte[32];
|
||||
random.nextBytes(bytes);
|
||||
return new SecureKey(bytes, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private void resolveKey(SecureKey key) {
|
||||
when(
|
||||
keyResolver.resolveSigningKey(
|
||||
@@ -272,16 +266,13 @@ private String createCompactToken(String subject, SecureKey key) {
|
||||
.thenReturn(
|
||||
new SecretKeySpec(
|
||||
key.getBytes(),
|
||||
SignatureAlgorithm.HS256.getValue()
|
||||
SignatureAlgorithm.HS256.getJcaName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
@InjectMocks
|
||||
private DAORealmHelperFactory helperFactory;
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -56,6 +55,7 @@ import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
|
||||
/**
|
||||
* Unit test for {@link JwtAccessTokenBuilder}.
|
||||
@@ -162,11 +162,4 @@ public class JwtAccessTokenBuilderTest {
|
||||
assertEquals("b", token.getCustom("a").get());
|
||||
assertEquals("[\"repo:*\"]", token.getScope().toString());
|
||||
}
|
||||
|
||||
private SecureKey createSecureKey() {
|
||||
byte[] bytes = new byte[32];
|
||||
new Random().nextBytes(bytes);
|
||||
return new SecureKey(bytes, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.time.Duration.ofMinutes;
|
||||
import static java.time.temporal.ChronoUnit.SECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
|
||||
@SubjectAware(
|
||||
username = "user",
|
||||
password = "secret",
|
||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JwtAccessTokenRefresherTest {
|
||||
|
||||
private static final Instant NOW = Instant.now().truncatedTo(SECONDS);
|
||||
private static final Instant TOKEN_CREATION = NOW.minus(ofMinutes(1));
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Mock
|
||||
private SecureKeyResolver keyResolver;
|
||||
@Mock
|
||||
private JwtAccessTokenRefreshStrategy refreshStrategy;
|
||||
@Mock
|
||||
private Clock refreshClock;
|
||||
|
||||
private KeyGenerator keyGenerator = () -> "key";
|
||||
|
||||
private JwtAccessTokenRefresher refresher;
|
||||
private JwtAccessTokenBuilder tokenBuilder;
|
||||
|
||||
@Before
|
||||
public void initKeyResolver() {
|
||||
when(keyResolver.getSecureKey(any())).thenReturn(createSecureKey());
|
||||
|
||||
Clock creationClock = mock(Clock.class);
|
||||
when(creationClock.instant()).thenReturn(TOKEN_CREATION);
|
||||
tokenBuilder = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, Collections.emptySet(), creationClock).create();
|
||||
|
||||
JwtAccessTokenBuilderFactory refreshBuilderFactory = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, Collections.emptySet(), refreshClock);
|
||||
refresher = new JwtAccessTokenRefresher(refreshBuilderFactory, refreshStrategy, refreshClock);
|
||||
when(refreshClock.instant()).thenReturn(NOW);
|
||||
when(refreshStrategy.shouldBeRefreshed(any())).thenReturn(true);
|
||||
|
||||
// set default expiration values
|
||||
tokenBuilder
|
||||
.expiresIn(5, MINUTES)
|
||||
.refreshableFor(10, MINUTES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotRefreshTokenWithDisabledRefresh() {
|
||||
JwtAccessToken oldToken = tokenBuilder
|
||||
.refreshableFor(0, MINUTES)
|
||||
.build();
|
||||
|
||||
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedToken).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotRefreshTokenWhenTokenExpired() {
|
||||
Instant afterNormalExpiration = NOW.plus(ofMinutes(6));
|
||||
when(refreshClock.instant()).thenReturn(afterNormalExpiration);
|
||||
JwtAccessToken oldToken = tokenBuilder.build();
|
||||
|
||||
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedToken).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotRefreshTokenWhenRefreshExpired() {
|
||||
Instant afterRefreshExpiration = Instant.now().plus(ofMinutes(2));
|
||||
when(refreshClock.instant()).thenReturn(afterRefreshExpiration);
|
||||
JwtAccessToken oldToken = tokenBuilder
|
||||
.refreshableFor(1, MINUTES)
|
||||
.build();
|
||||
|
||||
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedToken).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotRefreshTokenWhenStrategyDoesNotSaySo() {
|
||||
JwtAccessToken oldToken = tokenBuilder.build();
|
||||
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(false);
|
||||
|
||||
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedToken).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRefreshTokenWithParentId() {
|
||||
JwtAccessToken oldToken = tokenBuilder.build();
|
||||
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true);
|
||||
|
||||
Optional<JwtAccessToken> refreshedTokenResult = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedTokenResult).isNotEmpty();
|
||||
JwtAccessToken refreshedToken = refreshedTokenResult.get();
|
||||
assertThat(refreshedToken.getParentKey()).get().isEqualTo("key");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRefreshTokenWithSameExpiration() {
|
||||
JwtAccessToken oldToken = tokenBuilder.build();
|
||||
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true);
|
||||
|
||||
Optional<JwtAccessToken> refreshedTokenResult = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedTokenResult).isNotEmpty();
|
||||
JwtAccessToken refreshedToken = refreshedTokenResult.get();
|
||||
assertThat(refreshedToken.getExpiration()).isEqualTo(Date.from(NOW.plus(ofMinutes(5))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRefreshTokenWithSameRefreshExpiration() {
|
||||
JwtAccessToken oldToken = tokenBuilder.build();
|
||||
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true);
|
||||
|
||||
Optional<JwtAccessToken> refreshedTokenResult = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedTokenResult).isNotEmpty();
|
||||
JwtAccessToken refreshedToken = refreshedTokenResult.get();
|
||||
assertThat(refreshedToken.getRefreshExpiration()).get().isEqualTo(Date.from(TOKEN_CREATION.plus(ofMinutes(10))));
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,8 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
@@ -214,12 +216,6 @@ public class JwtAccessTokenResolverTest {
|
||||
.compact();
|
||||
}
|
||||
|
||||
private SecureKey createSecureKey() {
|
||||
byte[] bytes = new byte[32];
|
||||
random.nextBytes(bytes);
|
||||
return new SecureKey(bytes, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private void resolveKey(SecureKey key) {
|
||||
when(
|
||||
keyResolver.resolveSigningKey(
|
||||
@@ -230,7 +226,7 @@ public class JwtAccessTokenResolverTest {
|
||||
.thenReturn(
|
||||
new SecretKeySpec(
|
||||
key.getBytes(),
|
||||
SignatureAlgorithm.HS256.getValue()
|
||||
SignatureAlgorithm.HS256.getJcaName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.MINUTES;
|
||||
import static java.time.temporal.ChronoUnit.SECONDS;
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
|
||||
@SubjectAware(
|
||||
username = "user",
|
||||
password = "secret",
|
||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||
)
|
||||
public class PercentageJwtAccessTokenRefreshStrategyTest {
|
||||
|
||||
private static final Instant TOKEN_CREATION = Instant.now().truncatedTo(SECONDS);
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
private KeyGenerator keyGenerator = () -> "key";
|
||||
|
||||
private Clock refreshClock = mock(Clock.class);
|
||||
|
||||
private JwtAccessTokenBuilder tokenBuilder;
|
||||
private PercentageJwtAccessTokenRefreshStrategy refreshStrategy;
|
||||
|
||||
@Before
|
||||
public void initToken() {
|
||||
SecureKeyResolver keyResolver = mock(SecureKeyResolver.class);
|
||||
when(keyResolver.getSecureKey(any())).thenReturn(createSecureKey());
|
||||
|
||||
Clock creationClock = mock(Clock.class);
|
||||
when(creationClock.instant()).thenReturn(TOKEN_CREATION);
|
||||
|
||||
tokenBuilder = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, Collections.emptySet(), creationClock).create();
|
||||
tokenBuilder.expiresIn(1, HOURS);
|
||||
tokenBuilder.refreshableFor(1, HOURS);
|
||||
|
||||
refreshStrategy = new PercentageJwtAccessTokenRefreshStrategy(refreshClock, 0.5F);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotRefreshWhenTokenIsYoung() {
|
||||
when(refreshClock.instant()).thenReturn(TOKEN_CREATION.plus(29, MINUTES));
|
||||
assertThat(refreshStrategy.shouldBeRefreshed(tokenBuilder.build())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRefreshWhenTokenIsOld() {
|
||||
when(refreshClock.instant()).thenReturn(TOKEN_CREATION.plus(31, MINUTES));
|
||||
assertThat(refreshStrategy.shouldBeRefreshed(tokenBuilder.build())).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class SecureKeyTestUtil {
|
||||
public static SecureKey createSecureKey() {
|
||||
byte[] bytes = new byte[32];
|
||||
new SecureRandom().nextBytes(bytes);
|
||||
return new SecureKey(bytes, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package sonia.scm.web.security;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||
import sonia.scm.security.AccessTokenResolver;
|
||||
import sonia.scm.security.BearerToken;
|
||||
import sonia.scm.security.JwtAccessToken;
|
||||
import sonia.scm.security.JwtAccessTokenRefresher;
|
||||
import sonia.scm.web.WebTokenGenerator;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.singleton;
|
||||
import static java.util.Optional.of;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith({MockitoExtension.class})
|
||||
class TokenRefreshFilterTest {
|
||||
|
||||
@Mock
|
||||
private Set<WebTokenGenerator> tokenGenerators;
|
||||
@Mock
|
||||
private WebTokenGenerator tokenGenerator;
|
||||
@Mock
|
||||
private JwtAccessTokenRefresher refresher;
|
||||
@Mock
|
||||
private AccessTokenResolver resolver;
|
||||
@Mock
|
||||
private AccessTokenCookieIssuer issuer;
|
||||
|
||||
@InjectMocks
|
||||
private TokenRefreshFilter filter;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
@Mock
|
||||
private FilterChain filterChain;
|
||||
|
||||
@BeforeEach
|
||||
void initGenerators() {
|
||||
when(tokenGenerators.iterator()).thenReturn(singleton(tokenGenerator).iterator());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldContinueChain() throws IOException, ServletException {
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verify(issuer, never()).authenticate(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRefreshNonBearerToken() throws IOException, ServletException {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
when(tokenGenerator.createToken(request)).thenReturn(token);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(issuer, never()).authenticate(any(), any(), any());
|
||||
verify(filterChain).doFilter(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRefreshNonJwtToken() throws IOException, ServletException {
|
||||
BearerToken token = mock(BearerToken.class);
|
||||
JwtAccessToken jwtToken = mock(JwtAccessToken.class);
|
||||
when(tokenGenerator.createToken(request)).thenReturn(token);
|
||||
when(resolver.resolve(token)).thenReturn(jwtToken);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(issuer, never()).authenticate(any(), any(), any());
|
||||
verify(filterChain).doFilter(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRefreshIfRefreshable() throws IOException, ServletException {
|
||||
BearerToken token = mock(BearerToken.class);
|
||||
JwtAccessToken jwtToken = mock(JwtAccessToken.class);
|
||||
JwtAccessToken newJwtToken = mock(JwtAccessToken.class);
|
||||
when(tokenGenerator.createToken(request)).thenReturn(token);
|
||||
when(resolver.resolve(token)).thenReturn(jwtToken);
|
||||
when(refresher.refresh(jwtToken)).thenReturn(of(newJwtToken));
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(issuer).authenticate(request, response, newJwtToken);
|
||||
verify(filterChain).doFilter(request, response);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user