From ac4a57f2f3794375b35dc9d5bf38814d3750a489 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 21 Dec 2018 08:35:18 +0100 Subject: [PATCH] replace TokenClaimsValidator with not so generic AccessTokenValidator interface and fixed duplicated code of BearerRealm and JwtAccessTokenResolve --- ...lidator.java => AccessTokenValidator.java} | 15 +- .../java/sonia/scm/security/BearerRealm.java | 111 ++---- .../scm/security/JwtAccessTokenResolver.java | 45 ++- .../main/java/sonia/scm/security/Scopes.java | 2 +- .../scm/security/XsrfAccessTokenEnricher.java | 2 +- ...tor.java => XsrfAccessTokenValidator.java} | 31 +- .../sonia/scm/security/BearerRealmTest.java | 315 ++++-------------- .../security/JwtAccessTokenResolverTest.java | 35 +- ...java => XsrfAccessTokenValidatorTest.java} | 52 +-- 9 files changed, 187 insertions(+), 421 deletions(-) rename scm-core/src/main/java/sonia/scm/security/{TokenClaimsValidator.java => AccessTokenValidator.java} (82%) rename scm-webapp/src/main/java/sonia/scm/security/{XsrfTokenClaimsValidator.java => XsrfAccessTokenValidator.java} (71%) rename scm-webapp/src/test/java/sonia/scm/security/{XsrfTokenClaimsValidatorTest.java => XsrfAccessTokenValidatorTest.java} (68%) diff --git a/scm-core/src/main/java/sonia/scm/security/TokenClaimsValidator.java b/scm-core/src/main/java/sonia/scm/security/AccessTokenValidator.java similarity index 82% rename from scm-core/src/main/java/sonia/scm/security/TokenClaimsValidator.java rename to scm-core/src/main/java/sonia/scm/security/AccessTokenValidator.java index 4389e7bfb7..24a92929f9 100644 --- a/scm-core/src/main/java/sonia/scm/security/TokenClaimsValidator.java +++ b/scm-core/src/main/java/sonia/scm/security/AccessTokenValidator.java @@ -30,26 +30,25 @@ */ package sonia.scm.security; -import java.util.Map; import sonia.scm.plugin.ExtensionPoint; /** - * Validates the claims of a jwt token. The validator is called durring authentication - * with a jwt token. + * Validates an {@link AccessToken}. The validator is called during authentication + * with an {@link AccessToken}. * * @author Sebastian Sdorra * @since 2.0.0 */ @ExtensionPoint -public interface TokenClaimsValidator { +public interface AccessTokenValidator { /** - * Returns {@code true} if the claims is valid. If the token is not valid and the + * Returns {@code true} if the {@link AccessToken} is valid. If the token is not valid and the * method returns {@code false}, the authentication is treated as failed. * - * @param claims token claims + * @param token the access token to verify * - * @return {@code true} if the claims is valid + * @return {@code true} if the token is valid */ - boolean validate(Map claims); + boolean validate(AccessToken token); } diff --git a/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java b/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java index 3b19351641..6847fd324c 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java @@ -31,35 +31,20 @@ package sonia.scm.security; -//~--- non-JDK imports -------------------------------------------------------- - import com.google.common.annotations.VisibleForTesting; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; - -import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; import org.apache.shiro.realm.AuthenticatingRealm; - import sonia.scm.group.GroupDAO; import sonia.scm.plugin.Extension; import sonia.scm.user.UserDAO; -import static com.google.common.base.Preconditions.checkArgument; - -import java.util.List; -import java.util.Set; - -//~--- JDK imports ------------------------------------------------------------ - import javax.inject.Inject; import javax.inject.Singleton; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static com.google.common.base.Preconditions.checkArgument; + /** * Realm for authentication with {@link BearerToken}. @@ -71,34 +56,29 @@ import org.slf4j.LoggerFactory; @Extension public class BearerRealm extends AuthenticatingRealm { - - /** - * the logger for BearerRealm - */ - private static final Logger LOG = LoggerFactory.getLogger(BearerRealm.class); /** realm name */ @VisibleForTesting static final String REALM = "BearerRealm"; - //~--- constructors --------------------------------------------------------- + + /** dao realm helper */ + private final DAORealmHelper helper; + + /** access token resolver **/ + private final AccessTokenResolver tokenResolver; /** * Constructs ... * * @param helperFactory dao realm helper factory - * @param resolver key resolver - * @param validators token claims validators + * @param tokenResolver resolve access token from bearer */ @Inject - public BearerRealm( - DAORealmHelperFactory helperFactory, SecureKeyResolver resolver, Set validators - ) - { + public BearerRealm(DAORealmHelperFactory helperFactory, AccessTokenResolver tokenResolver) { this.helper = helperFactory.create(REALM); - this.resolver = resolver; - this.validators = validators; - + this.tokenResolver = tokenResolver; + setCredentialsMatcher(new AllowAllCredentialsMatcher()); setAuthenticationTokenClass(BearerToken.class); } @@ -106,71 +86,26 @@ public class BearerRealm extends AuthenticatingRealm //~--- methods -------------------------------------------------------------- /** - * Validates the given jwt token and retrieves authentication data from + * Validates the given bearer token and retrieves authentication data from * {@link UserDAO} and {@link GroupDAO}. * * - * @param token jwt token + * @param token bearer token * * @return authentication data from user and group dao */ @Override - protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) - { - checkArgument(token instanceof BearerToken, "%s is required", - BearerToken.class); + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { + checkArgument(token instanceof BearerToken, "%s is required", BearerToken.class); BearerToken bt = (BearerToken) token; - Claims c = checkToken(bt); + AccessToken accessToken = tokenResolver.resolve(bt); - return helper.getAuthenticationInfo(c.getSubject(), bt.getCredentials(), Scopes.fromClaims(c)); + return helper.getAuthenticationInfo( + accessToken.getSubject(), + bt.getCredentials(), + Scopes.fromClaims(accessToken.getClaims()) + ); } - /** - * Validates the jwt token. - * - * - * @param token jwt token - * - * @return claim - */ - private Claims checkToken(BearerToken token) - { - Claims claims; - - try - { - //J- - claims = Jwts.parser() - .setSigningKeyResolver(resolver) - .parseClaimsJws(token.getCredentials()) - .getBody(); - //J+ - - // check all registered claims validators - validators.forEach((validator) -> { - if (!validator.validate(claims)) { - LOG.warn("token claims is invalid, marked by validator {}", validator.getClass()); - throw new AuthenticationException("token claims is invalid"); - } - }); - } - catch (JwtException ex) - { - throw new AuthenticationException("signature is invalid", ex); - } - - return claims; - } - - //~--- fields --------------------------------------------------------------- - - /** token claims validators **/ - private final Set validators; - - /** dao realm helper */ - private final DAORealmHelper helper; - - /** secure key resolver */ - private final SecureKeyResolver resolver; } diff --git a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenResolver.java b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenResolver.java index 72b2a85c95..291364b935 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenResolver.java +++ b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenResolver.java @@ -55,37 +55,48 @@ public final class JwtAccessTokenResolver implements AccessTokenResolver { private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenResolver.class); private final SecureKeyResolver keyResolver; - private final Set validators; + private final Set validators; @Inject - public JwtAccessTokenResolver(SecureKeyResolver keyResolver, Set validators) { + public JwtAccessTokenResolver(SecureKeyResolver keyResolver, Set validators) { this.keyResolver = keyResolver; this.validators = validators; } @Override public JwtAccessToken resolve(BearerToken bearerToken) { - Claims claims; - try { - // parse and validate - claims = Jwts.parser() + String compact = bearerToken.getCredentials(); + + Claims claims = Jwts.parser() .setSigningKeyResolver(keyResolver) - .parseClaimsJws(bearerToken.getCredentials()) + .parseClaimsJws(compact) .getBody(); - - // check all registered claims validators - validators.forEach((validator) -> { - if (!validator.validate(claims)) { - LOG.warn("token claims is invalid, marked by validator {}", validator.getClass()); - throw new AuthenticationException("token claims is invalid"); - } - }); + + JwtAccessToken token = new JwtAccessToken(claims, compact); + validate(token); + + return token; } catch (JwtException ex) { throw new AuthenticationException("signature is invalid", ex); } - - return new JwtAccessToken(claims, bearerToken.getCredentials()); + } + + + private void validate(AccessToken accessToken) { + validators.forEach(validator -> validate(validator, accessToken)); + } + + private void validate(AccessTokenValidator validator, AccessToken accessToken) { + if (!validator.validate(accessToken)) { + String msg = createValidationFailedMessage(validator, accessToken); + LOG.debug(msg); + throw new AuthenticationException(msg); + } + } + + private String createValidationFailedMessage(AccessTokenValidator validator, AccessToken accessToken) { + return String.format("token %s is invalid, marked by validator %s", accessToken.getId(), validator.getClass()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/Scopes.java b/scm-webapp/src/main/java/sonia/scm/security/Scopes.java index d5d6b74021..4286bafe90 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/Scopes.java +++ b/scm-webapp/src/main/java/sonia/scm/security/Scopes.java @@ -47,7 +47,7 @@ import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.authz.permission.PermissionResolver; /** - * Utile methods for {@link Scope}. + * Util methods for {@link Scope}. * * @author Sebastian Sdorra * @since 2.0.0 diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java index 7690c48b30..617950ddea 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java +++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java @@ -44,7 +44,7 @@ import sonia.scm.util.HttpUtil; /** * Xsrf access token enricher will add an xsrf custom field to the access token. The enricher will only * add the xsrf field, if the authentication request is issued from the web interface and xsrf protection is - * enabled. The xsrf field will be validated on every request by the {@link XsrfTokenClaimsValidator}. Xsrf protection + * enabled. The xsrf field will be validated on every request by the {@link XsrfAccessTokenValidator}. Xsrf protection * can be disabled with {@link ScmConfiguration#setEnabledXsrfProtection(boolean)}. * * @see Issue 793 diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsValidator.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenValidator.java similarity index 71% rename from scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsValidator.java rename to scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenValidator.java index c71be5706e..437174f57e 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsValidator.java +++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenValidator.java @@ -30,30 +30,23 @@ */ package sonia.scm.security; -import com.google.common.base.Strings; -import java.util.Map; +import sonia.scm.plugin.Extension; + import javax.inject.Inject; import javax.inject.Provider; import javax.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.plugin.Extension; +import java.util.Optional; /** - * Validates xsrf protected token claims. The validator check if the current request contains an xsrf key which is - * equal to the token in the claims. If the claims does not contain a xsrf key, the check is passed by. The xsrf keys - * are added by the {@link XsrfTokenClaimsEnricher}. + * Validates xsrf protected access tokens. The validator check if the current request contains an xsrf key which is + * equal to the one in the access token. If the token does not contain a xsrf key, the check is passed by. The xsrf keys + * are added by the {@link XsrfAccessTokenEnricher}. * * @author Sebastian Sdorra * @since 2.0.0 */ @Extension -public class XsrfTokenClaimsValidator implements TokenClaimsValidator { - - /** - * the logger for XsrfTokenClaimsEnricher - */ - private static final Logger LOG = LoggerFactory.getLogger(XsrfTokenClaimsValidator.class); +public class XsrfAccessTokenValidator implements AccessTokenValidator { private final Provider requestProvider; @@ -64,16 +57,16 @@ public class XsrfTokenClaimsValidator implements TokenClaimsValidator { * @param requestProvider http request provider */ @Inject - public XsrfTokenClaimsValidator(Provider requestProvider) { + public XsrfAccessTokenValidator(Provider requestProvider) { this.requestProvider = requestProvider; } @Override - public boolean validate(Map claims) { - String xsrfClaimValue = (String) claims.get(Xsrf.TOKEN_KEY); - if (!Strings.isNullOrEmpty(xsrfClaimValue)) { + public boolean validate(AccessToken accessToken) { + Optional xsrfClaim = accessToken.getCustom(Xsrf.TOKEN_KEY); + if (xsrfClaim.isPresent()) { String xsrfHeaderValue = requestProvider.get().getHeader(Xsrf.HEADER_KEY); - return xsrfClaimValue.equals(xsrfHeaderValue); + return xsrfClaim.get().equals(xsrfHeaderValue); } return true; } diff --git a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java index 26dfcb2099..e66462cd01 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java @@ -29,271 +29,98 @@ * */ - - package sonia.scm.security; -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwsHeader; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; -import org.apache.shiro.subject.PrincipalCollection; -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; +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.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.group.GroupDAO; -import sonia.scm.user.User; -import sonia.scm.user.UserDAO; -import sonia.scm.user.UserTestData; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; -import javax.crypto.spec.SecretKeySpec; -import java.util.Date; -import java.util.Set; +import java.util.HashMap; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static sonia.scm.security.SecureKeyTestUtil.createSecureKey; /** * Unit tests for {@link BearerRealm}. * * @author Sebastian Sdorra */ -@SuppressWarnings("unchecked") -@RunWith(MockitoJUnitRunner.class) -public class BearerRealmTest -{ - - @Rule - public ExpectedException expectedException = ExpectedException.none(); +@ExtendWith(MockitoExtension.class) +class BearerRealmTest { - /** - * Method description - * - */ - @Test - public void testDoGetAuthenticationInfo() - { - SecureKey key = createSecureKey(); + @Mock + private DAORealmHelperFactory realmHelperFactory; - User marvin = UserTestData.createMarvin(); + @Mock + private DAORealmHelper realmHelper; - when(userDAO.get(marvin.getName())).thenReturn(marvin); + @Mock + private AccessTokenResolver accessTokenResolver; - resolveKey(key); - - String compact = createCompactToken(marvin.getName(), key); - - BearerToken token = BearerToken.valueOf(compact); - AuthenticationInfo info = realm.doGetAuthenticationInfo(token); - - assertNotNull(info); - - PrincipalCollection principals = info.getPrincipals(); - - assertEquals(marvin.getName(), principals.getPrimaryPrincipal()); - assertEquals(marvin, principals.oneByType(User.class)); - assertNotNull(principals.oneByType(Scope.class)); - assertTrue(principals.oneByType(Scope.class).isEmpty()); - } - - /** - * Test {@link BearerRealm#doGetAuthenticationInfo(AuthenticationToken)} with scope. - * - */ - @Test - public void testDoGetAuthenticationInfoWithScope() - { - SecureKey key = createSecureKey(); - - User marvin = UserTestData.createMarvin(); - - when(userDAO.get(marvin.getName())).thenReturn(marvin); - - resolveKey(key); - - String compact = createCompactToken( - marvin.getName(), - key, - new Date(System.currentTimeMillis() + 60000), - Scope.valueOf("repo:*", "user:*") - ); - - AuthenticationInfo info = realm.doGetAuthenticationInfo(BearerToken.valueOf(compact)); - Scope scope = info.getPrincipals().oneByType(Scope.class); - assertThat(scope, Matchers.containsInAnyOrder("repo:*", "user:*")); - } - - /** - * Test {@link BearerRealm#doGetAuthenticationInfo(AuthenticationToken)} with a failed - * claims validation. - */ - @Test - public void testDoGetAuthenticationInfoWithInvalidClaims() - { - SecureKey key = createSecureKey(); - User marvin = UserTestData.createMarvin(); - - resolveKey(key); - - String compact = createCompactToken(marvin.getName(), key); - - // treat claims as invalid - when(validator.validate(Mockito.anyMap())).thenReturn(false); - - // expect exception - expectedException.expect(AuthenticationException.class); - expectedException.expectMessage(Matchers.containsString("claims")); - - // kick authentication - realm.doGetAuthenticationInfo(BearerToken.valueOf(compact)); - } - - /** - * Method description - * - */ - @Test(expected = AuthenticationException.class) - public void testDoGetAuthenticationInfoWithExpiredToken() - { - User trillian = UserTestData.createTrillian(); - - SecureKey key = createSecureKey(); - - resolveKey(key); - - Date exp = new Date(System.currentTimeMillis() - 600l); - String compact = createCompactToken(trillian.getName(), key, exp, Scope.empty()); - - realm.doGetAuthenticationInfo(BearerToken.valueOf(compact)); - } - - /** - * Method description - * - */ - @Test(expected = AuthenticationException.class) - public void testDoGetAuthenticationInfoWithInvalidSignature() - { - resolveKey(createSecureKey()); - - User trillian = UserTestData.createTrillian(); - String compact = createCompactToken(trillian.getName(), createSecureKey()); - - realm.doGetAuthenticationInfo(BearerToken.valueOf(compact)); - } - - /** - * Method description - * - */ - @Test(expected = AuthenticationException.class) - public void testDoGetAuthenticationInfoWithoutSignature() - { - String compact = Jwts.builder().setSubject("test").compact(); - - realm.doGetAuthenticationInfo(BearerToken.valueOf(compact)); - } - - /** - * Method description - * - */ - @Test(expected = IllegalArgumentException.class) - public void testDoGetAuthenticationInfoWrongToken() - { - realm.doGetAuthenticationInfo(new UsernamePasswordToken("test", "test")); - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - */ - @Before - public void setUp() - { - when(validator.validate(Mockito.anyMap())).thenReturn(true); - Set validators = Sets.newHashSet(validator); - realm = new BearerRealm(helperFactory, keyResolver, validators); - } - - //~--- methods -------------------------------------------------------------- - -private String createCompactToken(String subject, SecureKey key) { - return createCompactToken(subject, key, Scope.empty()); - } - - private String createCompactToken(String subject, SecureKey key, Scope scope) { - return createCompactToken(subject, key, new Date(System.currentTimeMillis() + 60000), scope); - } - - private String createCompactToken(String subject, SecureKey key, Date exp, Scope scope) { - return Jwts.builder() - .claim(Scopes.CLAIMS_KEY, ImmutableList.copyOf(scope)) - .setSubject(subject) - .setExpiration(exp) - .signWith(SignatureAlgorithm.HS256, key.getBytes()) - .compact(); - } - - private void resolveKey(SecureKey key) { - when( - keyResolver.resolveSigningKey( - any(JwsHeader.class), - any(Claims.class) - ) - ) - .thenReturn( - new SecretKeySpec( - key.getBytes(), - SignatureAlgorithm.HS256.getJcaName() - ) - ); - } - - //~--- fields --------------------------------------------------------------- - @InjectMocks - private DAORealmHelperFactory helperFactory; - - @Mock - private LoginAttemptHandler loginAttemptHandler; - - @Mock - private TokenClaimsValidator validator; - - /** Field description */ - @Mock - private GroupDAO groupDAO; - - /** Field description */ - @Mock - private SecureKeyResolver keyResolver; - - /** Field description */ private BearerRealm realm; - /** Field description */ @Mock - private UserDAO userDAO; + private AuthenticationInfo authenticationInfo; + + @BeforeEach + void prepareObjectUnderTest() { + when(realmHelperFactory.create(BearerRealm.REALM)).thenReturn(realmHelper); + realm = new BearerRealm(realmHelperFactory, accessTokenResolver); + } + + @Test + void shouldDoGetAuthentication() { + BearerToken bearerToken = BearerToken.valueOf("__bearer__"); + AccessToken accessToken = mock(AccessToken.class); + when(accessToken.getSubject()).thenReturn("trillian"); + when(accessToken.getClaims()).thenReturn(new HashMap<>()); + + when(accessTokenResolver.resolve(bearerToken)).thenReturn(accessToken); + + // we have to use answer, because we could not mock the result of Scopes + when(realmHelper.getAuthenticationInfo( + anyString(), anyString(), any(Scope.class) + )).thenAnswer(createAnswer("trillian", "__bearer__", true)); + + AuthenticationInfo result = realm.doGetAuthenticationInfo(bearerToken); + assertThat(result).isSameAs(authenticationInfo); + } + + @Test + void shouldThrowIllegalArgumentExceptionForWrongTypeOfToken() { + assertThrows(IllegalArgumentException.class, () -> realm.doGetAuthenticationInfo(new UsernamePasswordToken())); + } + + private Answer createAnswer(String expectedSubject, String expectedCredentials, boolean scopeEmpty) { + return (iom) -> { + String subject = iom.getArgument(0); + assertThat(subject).isEqualTo(expectedSubject); + String credentials = iom.getArgument(1); + assertThat(credentials).isEqualTo(expectedCredentials); + Scope scope = iom.getArgument(2); + assertThat(scope.isEmpty()).isEqualTo(scopeEmpty); + + return authenticationInfo; + }; + } + + private class MyAnswer implements Answer { + + @Override + public AuthenticationInfo answer(InvocationOnMock invocationOnMock) throws Throwable { + return null; + } + } } diff --git a/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenResolverTest.java b/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenResolverTest.java index d4341f104e..a1f1e36c9c 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenResolverTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenResolverTest.java @@ -40,26 +40,27 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; -import java.security.SecureRandom; -import java.util.Date; -import java.util.Set; -import javax.crypto.spec.SecretKeySpec; import org.apache.shiro.authc.AuthenticationException; import org.hamcrest.Matchers; -import org.junit.Test; -import static org.junit.Assert.*; -import static org.hamcrest.Matchers.*; import org.junit.Before; import org.junit.Rule; +import org.junit.Test; import org.junit.rules.ExpectedException; 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; +import javax.crypto.spec.SecretKeySpec; +import java.util.Date; +import java.util.Set; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; +import static sonia.scm.security.SecureKeyTestUtil.createSecureKey; + /** * Unit tests for {@link JwtAccessTokenResolver}. * @@ -70,14 +71,12 @@ public class JwtAccessTokenResolverTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - - private final SecureRandom random = new SecureRandom(); - + @Mock private SecureKeyResolver keyResolver; @Mock - private TokenClaimsValidator validator; + private AccessTokenValidator validator; private JwtAccessTokenResolver resolver; @@ -86,8 +85,8 @@ public class JwtAccessTokenResolverTest { */ @Before public void prepareObjectUnderTest() { - Set validators = Sets.newHashSet(validator); - when(validator.validate(anyMap())).thenReturn(true); + Set validators = Sets.newHashSet(validator); + when(validator.validate(Mockito.any(AccessToken.class))).thenReturn(true); resolver = new JwtAccessTokenResolver(keyResolver, validators); } @@ -115,11 +114,11 @@ public class JwtAccessTokenResolverTest { String compact = createCompactToken("marvin", secureKey); // prepare mock - when(validator.validate(anyMap())).thenReturn(false); + when(validator.validate(Mockito.any(AccessToken.class))).thenReturn(false); // expect exception expectedException.expect(AuthenticationException.class); - expectedException.expectMessage(Matchers.containsString("claims")); + expectedException.expectMessage(Matchers.containsString("token")); BearerToken bearer = BearerToken.valueOf(compact); resolver.resolve(bearer); diff --git a/scm-webapp/src/test/java/sonia/scm/security/XsrfTokenClaimsValidatorTest.java b/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenValidatorTest.java similarity index 68% rename from scm-webapp/src/test/java/sonia/scm/security/XsrfTokenClaimsValidatorTest.java rename to scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenValidatorTest.java index dbebb2c0cf..a89744cd6e 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/XsrfTokenClaimsValidatorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenValidatorTest.java @@ -31,88 +31,90 @@ package sonia.scm.security; -import com.google.common.collect.Maps; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import org.junit.Test; -import static org.junit.Assert.*; import org.junit.Before; +import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import static org.mockito.Mockito.*; import org.mockito.junit.MockitoJUnitRunner; +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + /** - * Tests {@link XsrfTokenClaimsValidator}. + * Tests {@link XsrfAccessTokenValidator}. * * @author Sebastian Sdorra */ @RunWith(MockitoJUnitRunner.class) -public class XsrfTokenClaimsValidatorTest { +public class XsrfAccessTokenValidatorTest { @Mock private HttpServletRequest request; - private XsrfTokenClaimsValidator validator; + @Mock + private AccessToken accessToken; + + private XsrfAccessTokenValidator validator; /** * Prepare object under test. */ @Before public void prepareObjectUnderTest() { - validator = new XsrfTokenClaimsValidator(() -> request); + validator = new XsrfAccessTokenValidator(() -> request); } /** - * Tests {@link XsrfTokenClaimsValidator#validate(java.util.Map)}. + * Tests {@link XsrfAccessTokenValidator#validate(AccessToken)}. */ @Test public void testValidate() { // prepare - Map claims = Maps.newHashMap(); - claims.put(Xsrf.TOKEN_KEY, "abc"); + when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc")); when(request.getHeader(Xsrf.HEADER_KEY)).thenReturn("abc"); // execute and assert - assertTrue(validator.validate(claims)); + assertTrue(validator.validate(accessToken)); } /** - * Tests {@link XsrfTokenClaimsValidator#validate(java.util.Map)} with wrong header. + * Tests {@link XsrfAccessTokenValidator#validate(AccessToken)} with wrong header. */ @Test public void testValidateWithWrongHeader() { // prepare - Map claims = Maps.newHashMap(); - claims.put(Xsrf.TOKEN_KEY, "abc"); + when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc")); when(request.getHeader(Xsrf.HEADER_KEY)).thenReturn("123"); // execute and assert - assertFalse(validator.validate(claims)); + assertFalse(validator.validate(accessToken)); } /** - * Tests {@link XsrfTokenClaimsValidator#validate(java.util.Map)} without header. + * Tests {@link XsrfAccessTokenValidator#validate(AccessToken)} without header. */ @Test public void testValidateWithoutHeader() { // prepare - Map claims = Maps.newHashMap(); - claims.put(Xsrf.TOKEN_KEY, "abc"); + when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc")); // execute and assert - assertFalse(validator.validate(claims)); + assertFalse(validator.validate(accessToken)); } /** - * Tests {@link XsrfTokenClaimsValidator#validate(java.util.Map)} without claims key. + * Tests {@link XsrfAccessTokenValidator#validate(AccessToken)} without claims key. */ @Test public void testValidateWithoutClaimsKey() { // prepare - Map claims = Maps.newHashMap(); + when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.empty()); // execute and assert - assertTrue(validator.validate(claims)); + assertTrue(validator.validate(accessToken)); } }