Authentication metrics (#1595)

Expose metrics about:

- User login attempts
- Failed user logins
- User logouts
- General successful accesses to SCM-Manager via any authentication realm
- General failed accesses to SCM-Manager

Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2021-03-24 08:50:14 +01:00
committed by GitHub
parent 97bad3e3a5
commit 3ec499d22c
17 changed files with 425 additions and 375 deletions

View File

@@ -26,6 +26,9 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
@@ -47,14 +50,15 @@ import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static java.net.URI.create;
import static java.util.Optional.of;
import static org.hamcrest.Matchers.containsString;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -75,6 +79,8 @@ public class AuthenticationResourceTest {
@Mock
private AccessTokenBuilder accessTokenBuilder;
private MeterRegistry meterRegistry;
private final AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class));
private final MockHttpResponse response = new MockHttpResponse();
@@ -135,7 +141,8 @@ public class AuthenticationResourceTest {
@Before
public void prepareEnvironment() {
authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer);
meterRegistry = new SimpleMeterRegistry();
authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer, meterRegistry);
dispatcher.addSingletonResource(authenticationResource);
AccessToken accessToken = mock(AccessToken.class);
@@ -157,6 +164,11 @@ public class AuthenticationResourceTest {
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
List<Meter> meters = meterRegistry.getMeters();
assertThat(meters).hasSize(3);
Optional<Meter> loginAttemptMeter = meters.stream().filter(m -> m.getId().getName().equals("scm.auth.login.attempts")).findFirst();
assertThat(loginAttemptMeter).isPresent();
assertThat(loginAttemptMeter.get().measure().iterator().next().getValue()).isEqualTo(1);
}
@Test
@@ -167,6 +179,12 @@ public class AuthenticationResourceTest {
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
List<Meter> meters = meterRegistry.getMeters();
assertThat(meters).hasSize(3);
Optional<Meter> loginAttemptMeter = meters.stream().filter(m -> m.getId().getName().equals("scm.auth.login.attempts")).findFirst();
assertThat(loginAttemptMeter).isPresent();
assertThat(loginAttemptMeter.get().measure().iterator().next().getValue()).isEqualTo(1);
}
@@ -178,6 +196,10 @@ public class AuthenticationResourceTest {
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus());
List<Meter> meters = meterRegistry.getMeters();
assertThat(meters).hasSize(3);
assertThat(meters.stream().map(m -> m.getId().getName())).contains("scm.auth.login.failed", "scm.auth.login.attempts", "scm.auth.logout");
}
@Test
@@ -187,6 +209,10 @@ public class AuthenticationResourceTest {
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus());
List<Meter> meters = meterRegistry.getMeters();
assertThat(meters).hasSize(3);
assertThat(meters.stream().map(m -> m.getId().getName())).contains("scm.auth.login.failed", "scm.auth.login.attempts", "scm.auth.logout");
}
@Test
@@ -216,6 +242,12 @@ public class AuthenticationResourceTest {
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
List<Meter> meters = meterRegistry.getMeters();
assertThat( meters).hasSize(3);
Optional<Meter> logoutMeter = meters.stream().filter(m -> m.getId().getName().equals("scm.auth.logout")).findFirst();
assertThat(logoutMeter).isPresent();
assertThat(logoutMeter.get().measure().iterator().next().getValue()).isEqualTo(1);
}
@Test
@@ -227,7 +259,7 @@ public class AuthenticationResourceTest {
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertThat(response.getContentAsString(), containsString("http://example.com/cas/logout"));
assertThat(response.getContentAsString()).contains("http://example.com/cas/logout");
}
@Test

View File

@@ -29,7 +29,6 @@ import org.apache.shiro.authc.UsernamePasswordToken;
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.SCMContext;
@@ -56,7 +55,6 @@ class AnonymousRealmTest {
@Mock
private UserDAO userDAO;
@InjectMocks
private AnonymousRealm realm;
@Mock

View File

@@ -29,7 +29,6 @@ import org.apache.shiro.authc.UsernamePasswordToken;
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;
@@ -62,7 +61,6 @@ class BearerRealmTest {
@Mock
private AccessTokenResolver accessTokenResolver;
@InjectMocks
private BearerRealm realm;
@Mock

View File

@@ -21,10 +21,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.security;
//~--- non-JDK imports --------------------------------------------------------
package sonia.scm.security;
import com.google.common.collect.Collections2;
import org.apache.shiro.authc.AuthenticationInfo;
@@ -41,7 +39,6 @@ import org.apache.shiro.authz.permission.WildcardPermissionResolver;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,38 +53,38 @@ import sonia.scm.user.UserTestData;
import java.util.HashSet;
import java.util.Set;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultRealmTest
{
public class DefaultRealmTest {
@Mock
private DefaultAuthorizationCollector collector;
private Set<AuthorizationCollector> authorizationCollectors;
@Mock
private LoginAttemptHandler loginAttemptHandler;
@Mock
private GroupDAO groupDAO;
@Mock
private UserDAO userDAO;
@InjectMocks
private DAORealmHelperFactory helperFactory;
private DefaultRealm realm;
private DefaultPasswordService service;
/**
* Method description
*
*/
@Test(expected = DisabledAccountException.class)
public void testDisabledAccount()
{
public void testDisabledAccount() {
User user = UserTestData.createMarvin();
user.setActive(false);
@@ -97,37 +94,29 @@ public class DefaultRealmTest
realm.getAuthenticationInfo(token);
}
/**
* Method description
*
*/
@Test
public void testGetAuthorizationInfo()
{
public void testGetAuthorizationInfo() {
SimplePrincipalCollection col = new SimplePrincipalCollection();
realm.doGetAuthorizationInfo(col);
verify(collector, times(1)).collect(col);
}
/**
* Tests {@link DefaultRealm#doGetAuthorizationInfo(PrincipalCollection)} without scope.
*/
@Test
public void testGetAuthorizationInfoWithoutScope(){
public void testGetAuthorizationInfoWithoutScope() {
SimplePrincipalCollection col = new SimplePrincipalCollection();
SimpleAuthorizationInfo collectorsAuthz = new SimpleAuthorizationInfo();
collectorsAuthz.addStringPermission("repository:*");
when(collector.collect(col)).thenReturn(collectorsAuthz);
AuthorizationInfo realmsAutz = realm.doGetAuthorizationInfo(col);
assertThat(realmsAutz.getObjectPermissions(), is(nullValue()));
assertThat(realmsAutz.getStringPermissions(), Matchers.contains("repository:*"));
assertThat(realmsAutz.getObjectPermissions()).isNull();
assertThat(realmsAutz.getStringPermissions()).contains("repository:*");
}
@Test
public void testGetAuthorizationInfoWithMultipleAuthorizationCollectors(){
public void testGetAuthorizationInfoWithMultipleAuthorizationCollectors() {
SimplePrincipalCollection col = new SimplePrincipalCollection();
col.add(Scope.empty(), DefaultRealm.REALM);
@@ -151,124 +140,81 @@ public class DefaultRealmTest
authorizationCollectors.add(thirdCollector);
AuthorizationInfo realmsAuthz = realm.doGetAuthorizationInfo(col);
assertThat(realmsAuthz.getObjectPermissions(), contains(permission));
assertThat(realmsAuthz.getStringPermissions(), containsInAnyOrder("repository:*", "user:*"));
assertThat(realmsAuthz.getRoles(), Matchers.contains("awesome"));
assertThat(realmsAuthz.getObjectPermissions()).contains(permission);
assertThat(realmsAuthz.getStringPermissions()).containsExactlyInAnyOrder("repository:*", "user:*");
assertThat(realmsAuthz.getRoles()).contains("awesome");
}
/**
* Tests {@link DefaultRealm#doGetAuthorizationInfo(PrincipalCollection)} with empty scope.
*/
@Test
public void testGetAuthorizationInfoWithEmptyScope(){
public void testGetAuthorizationInfoWithEmptyScope() {
SimplePrincipalCollection col = new SimplePrincipalCollection();
col.add(Scope.empty(), DefaultRealm.REALM);
SimpleAuthorizationInfo collectorsAuthz = new SimpleAuthorizationInfo();
collectorsAuthz.addStringPermission("repository:*");
when(collector.collect(col)).thenReturn(collectorsAuthz);
AuthorizationInfo realmsAutz = realm.doGetAuthorizationInfo(col);
assertThat(realmsAutz.getObjectPermissions(), is(nullValue()));
assertThat(realmsAutz.getStringPermissions(), Matchers.contains("repository:*"));
assertThat(realmsAutz.getObjectPermissions()).isNull();
;
assertThat(realmsAutz.getStringPermissions()).contains("repository:*");
}
/**
* Tests {@link DefaultRealm#doGetAuthorizationInfo(PrincipalCollection)} with scope.
*/
@Test
public void testGetAuthorizationInfoWithScope(){
public void testGetAuthorizationInfoWithScope() {
SimplePrincipalCollection col = new SimplePrincipalCollection();
col.add(Scope.valueOf("user:*:me"), DefaultRealm.REALM);
SimpleAuthorizationInfo collectorsAuthz = new SimpleAuthorizationInfo();
collectorsAuthz.addStringPermission("repository:*");
collectorsAuthz.addStringPermission("user:*:me");
when(collector.collect(col)).thenReturn(collectorsAuthz);
AuthorizationInfo realmsAutz = realm.doGetAuthorizationInfo(col);
assertThat(
Collections2.transform(realmsAutz.getObjectPermissions(), Permission::toString),
allOf(
Matchers.contains("user:*:me"),
not(Matchers.contains("repository:*"))
)
);
assertThat(Collections2.transform(realmsAutz.getObjectPermissions(), Permission::toString)).contains("user:*:me").doesNotContain("repository:*");
}
/**
* Method description
*
*/
@Test
public void testSimpleAuthentication()
{
public void testSimpleAuthentication() {
User user = UserTestData.createTrillian();
UsernamePasswordToken token = daoUser(user, "secret");
AuthenticationInfo info = realm.getAuthenticationInfo(token);
assertNotNull(info);
assertThat(info).isNotNull();
PrincipalCollection collection = info.getPrincipals();
assertEquals(token.getUsername(), collection.getPrimaryPrincipal());
assertThat(collection.getRealmNames(), hasSize(1));
assertThat(collection.getRealmNames(), hasItem(DefaultRealm.REALM));
assertEquals(user, collection.oneByType(User.class));
assertThat(token.getUsername()).isEqualTo(collection.getPrimaryPrincipal());
assertThat(collection.getRealmNames()).hasSize(1);
assertThat(collection.getRealmNames()).contains(DefaultRealm.REALM);
assertThat(user).isEqualTo(collection.oneByType(User.class));
}
/**
* Method description
*
*/
@Test(expected = UnknownAccountException.class)
public void testUnknownAccount()
{
public void testUnknownAccount() {
realm.getAuthenticationInfo(new UsernamePasswordToken("tricia", "secret"));
}
/**
* Method description
*
*/
@Test(expected = IllegalArgumentException.class)
public void testWithoutUsername()
{
public void testWithoutUsername() {
realm.getAuthenticationInfo(new UsernamePasswordToken(null, "secret"));
}
/**
* Method description
*
*/
@Test(expected = IncorrectCredentialsException.class)
public void testWrongCredentials()
{
public void testWrongCredentials() {
UsernamePasswordToken token = daoUser(UserTestData.createDent(), "secret");
token.setPassword("secret123".toCharArray());
realm.getAuthenticationInfo(token);
}
/**
* Method description
*
*/
@Test(expected = IllegalArgumentException.class)
public void testWrongToken()
{
public void testWrongToken() {
realm.getAuthenticationInfo(new OtherAuthenticationToken());
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*/
@Before
public void setUp()
{
public void setUp() {
service = new DefaultPasswordService();
DefaultHashService hashService = new DefaultHashService();
@@ -281,96 +227,30 @@ public class DefaultRealmTest
authorizationCollectors.add(collector);
realm = new DefaultRealm(service, authorizationCollectors, helperFactory);
// set permission resolver
realm.setPermissionResolver(new WildcardPermissionResolver());
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param user
* @param password
*
* @return
*/
private UsernamePasswordToken daoUser(User user, String password)
{
private UsernamePasswordToken daoUser(User user, String password) {
user.setPassword(service.encryptPassword(password));
when(userDAO.get(user.getName())).thenReturn(user);
return new UsernamePasswordToken(user.getName(), password);
}
//~--- inner classes --------------------------------------------------------
private static class OtherAuthenticationToken implements AuthenticationToken {
/**
* Class description
*
*
* @version Enter version here..., 14/12/13
* @author Enter your name here...
*/
private static class OtherAuthenticationToken implements AuthenticationToken
{
/** Field description */
private static final long serialVersionUID = 8891352342377018022L;
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public Object getCredentials()
{
public Object getCredentials() {
throw new UnsupportedOperationException("Not supported yet."); // To change body of generated methods, choose Tools | Templates.
}
/**
* Method description
*
*
* @return
*/
@Override
public Object getPrincipal()
{
public Object getPrincipal() {
throw new UnsupportedOperationException("Not supported yet."); // To change body of generated methods, choose Tools | Templates.
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@Mock
private DefaultAuthorizationCollector collector;
private Set<AuthorizationCollector> authorizationCollectors;
@Mock
private LoginAttemptHandler loginAttemptHandler;
@Mock
private GroupDAO groupDAO;
@Mock
private UserDAO userDAO;
@InjectMocks
private DAORealmHelperFactory helperFactory;
/** Field description */
private DefaultRealm realm;
/** Field description */
private DefaultPasswordService service;
}

View File

@@ -24,10 +24,14 @@
package sonia.scm.security;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.MergableAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import org.junit.Test;
@@ -37,9 +41,11 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@@ -49,7 +55,7 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
private Realm realm;
@Mock
private AuthenticationToken token;
private UsernamePasswordToken token;
@Mock
MergableAuthenticationInfo singleRealmInfo;
@@ -71,7 +77,7 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
@Test
public void shouldAddNonNullThrowableToList() {
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(new SimpleMeterRegistry());
strategy.threadLocal.set(new ArrayList<>());
strategy.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, tokenExpiredException);
@@ -82,7 +88,7 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
@Test(expected = TokenExpiredException.class)
public void shouldRethrowTokenExpiredException() {
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(new SimpleMeterRegistry());
strategy.threadLocal.set(singletonList(tokenExpiredException));
strategy.afterAllAttempts(token, aggregateInfo);
@@ -90,7 +96,7 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
@Test(expected = TokenValidationFailedException.class)
public void shouldRethrowTokenValidationFailedException() {
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(new SimpleMeterRegistry());
strategy.threadLocal.set(singletonList(tokenValidationFailedException));
strategy.afterAllAttempts(token, aggregateInfo);
@@ -98,7 +104,7 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
@Test(expected = TokenExpiredException.class)
public void shouldPrioritizeRethrowingTokenExpiredExceptionOverTokenValidationFailedException() {
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(new SimpleMeterRegistry());
strategy.threadLocal.set(Arrays.asList(tokenValidationFailedException, tokenExpiredException));
strategy.afterAllAttempts(token, aggregateInfo);
@@ -106,7 +112,7 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
@Test(expected = AuthenticationException.class)
public void shouldThrowGenericErrorIfNonTokenExpiredExceptionWasCaught() {
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(new SimpleMeterRegistry());
strategy.threadLocal.set(singletonList(authenticationException));
strategy.afterAllAttempts(token, aggregateInfo);
@@ -114,7 +120,7 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
@Test()
public void shouldNotRethrowExceptionIfAuthenticationSuccessful() {
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(new SimpleMeterRegistry());
strategy.threadLocal.set(singletonList(tokenExpiredException));
when(aggregateInfo.getPrincipals()).thenReturn(principalCollection);
when(principalCollection.isEmpty()).thenReturn(false);
@@ -124,4 +130,50 @@ public class ScmAtLeastOneSuccessfulStrategyTest {
assertThat(authenticationInfo).isNotNull();
}
@Test()
public void shouldTrackSuccessfulRealmAuthenticationMetrics() {
MeterRegistry meterRegistry = new SimpleMeterRegistry();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(meterRegistry);
strategy.threadLocal.set(singletonList(tokenExpiredException));
when(aggregateInfo.getPrincipals()).thenReturn(principalCollection);
when(principalCollection.isEmpty()).thenReturn(false);
DefaultRealm realm = mock(DefaultRealm.class);
strategy.afterAttempt(realm, token, aggregateInfo, null, null);
assertThat(meterRegistry.getMeters()).hasSize(1);
Optional<Meter> realmAccessMeter = meterRegistry.getMeters()
.stream()
.filter(m -> m.getId().getName().equals("scm.auth.realm.successful"))
.findFirst();
assertThat(realmAccessMeter).isPresent();
assertThat(realmAccessMeter.get().measure().iterator().next().getValue()).isEqualTo(1);
assertThat(realmAccessMeter.get().getId().getTags()).contains(
Tag.of("realm", "sonia.scm.security.DefaultRealm"),
Tag.of("token", "org.apache.shiro.authc.UsernamePasswordToken")
);
}
@Test()
public void shouldTrackGeneralSuccessfulAuthenticationMetrics() {
MeterRegistry meterRegistry = new SimpleMeterRegistry();
final ScmAtLeastOneSuccessfulStrategy strategy = new ScmAtLeastOneSuccessfulStrategy(meterRegistry);
strategy.threadLocal.set(singletonList(tokenExpiredException));
when(aggregateInfo.getPrincipals()).thenReturn(principalCollection);
when(principalCollection.isEmpty()).thenReturn(false);
strategy.afterAllAttempts(token, aggregateInfo);
assertThat(meterRegistry.getMeters()).hasSize(1);
Optional<Meter> accessMeter = meterRegistry.getMeters()
.stream()
.filter(m -> m.getId().getName().equals("scm.auth.access.successful"))
.findFirst();
assertThat(accessMeter).isPresent();
assertThat(accessMeter.get().measure().iterator().next().getValue()).isEqualTo(1);
assertThat(accessMeter.get().getId().getTags()).contains(
Tag.of("token", "org.apache.shiro.authc.UsernamePasswordToken")
);
}
}

View File

@@ -24,11 +24,13 @@
package sonia.scm.web.security;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
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;
@@ -47,6 +49,7 @@ import java.util.Set;
import static java.util.Collections.singleton;
import static java.util.Optional.of;
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.never;
@@ -68,7 +71,6 @@ class TokenRefreshFilterTest {
@Mock
private AccessTokenCookieIssuer issuer;
@InjectMocks
private TokenRefreshFilter filter;
@Mock
@@ -78,9 +80,13 @@ class TokenRefreshFilterTest {
@Mock
private FilterChain filterChain;
private MeterRegistry meterRegistry;
@BeforeEach
void initGenerators() {
void init() {
when(tokenGenerators.iterator()).thenReturn(singleton(tokenGenerator).iterator());
meterRegistry = new SimpleMeterRegistry();
filter = new TokenRefreshFilter(tokenGenerators, refresher, resolver, issuer, meterRegistry);
}
@Test
@@ -130,6 +136,23 @@ class TokenRefreshFilterTest {
verify(filterChain).doFilter(request, response);
}
@Test
void shouldTrackMetricIfTokenWasRefreshed() throws IOException, ServletException {
BearerToken token = createValidToken();
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);
assertThat(meterRegistry.getMeters()).hasSize(1);
Meter.Id meterId = meterRegistry.getMeters().get(0).getId();
assertThat(meterId.getName()).isEqualTo("scm.auth.token.refresh");
assertThat(meterId.getTag("type")).isEqualTo("JWT");
}
BearerToken createValidToken() {
return valueOf("some.jwt.token");
}