Clear external group cache on explicit logout or user deletion (#1819)

Clears the external group cache whenever a user gets logged out by the logout rest method or the user gets deleted.

Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
Sebastian Sdorra
2021-10-06 14:34:10 +02:00
committed by GitHub
parent 1318f40e6d
commit d1de7bf214
8 changed files with 160 additions and 25 deletions

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
@@ -35,14 +35,17 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.event.ScmEventBus;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilder;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.AccessTokenCookieIssuer;
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
import sonia.scm.security.LogoutEvent;
import sonia.scm.web.RestDispatcher;
import javax.servlet.http.HttpServletRequest;
@@ -52,14 +55,14 @@ import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
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.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SubjectAware(
@@ -79,6 +82,9 @@ public class AuthenticationResourceTest {
@Mock
private AccessTokenBuilder accessTokenBuilder;
@Mock
private ScmEventBus eventBus;
private MeterRegistry meterRegistry;
private final AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class));
@@ -142,7 +148,7 @@ public class AuthenticationResourceTest {
@Before
public void prepareEnvironment() {
meterRegistry = new SimpleMeterRegistry();
authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer, meterRegistry);
authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer, meterRegistry, eventBus);
dispatcher.addSingletonResource(authenticationResource);
AccessToken accessToken = mock(AccessToken.class);
@@ -248,9 +254,29 @@ public class AuthenticationResourceTest {
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);
verify(eventBus).post(any(LogoutEvent.class));
}
@Test
@SubjectAware(username = "trillian", password = "secret")
public void shouldFireLogoutEvent() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.delete("/" + AuthenticationResource.PATH + "/access_token");
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
verify(eventBus).post(captor.capture());
Object event = captor.getValue();
assertThat(event).isInstanceOfSatisfying(LogoutEvent.class, logoutEvent -> {
assertThat(logoutEvent.getPrimaryPrincipal()).isEqualTo("trillian");
});
}
@Test
@SubjectAware(username = "trillian", password = "secret")
public void shouldHandleLogoutRedirection() throws URISyntaxException, UnsupportedEncodingException {
authenticationResource.setLogoutRedirection(() -> of(create("http://example.com/cas/logout")));
@@ -263,6 +289,7 @@ public class AuthenticationResourceTest {
}
@Test
@SubjectAware(username = "trillian", password = "secret")
public void shouldHandleDisabledLogoutRedirection() throws URISyntaxException {
authenticationResource.setLogoutRedirection(Optional::empty);

View File

@@ -32,10 +32,12 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContext;
import sonia.scm.HandlerEventType;
import sonia.scm.cache.MapCache;
import sonia.scm.cache.MapCacheManager;
import sonia.scm.security.Authentications;
import sonia.scm.security.LogoutEvent;
import sonia.scm.user.User;
import sonia.scm.user.UserEvent;
import java.util.HashSet;
import java.util.List;
@@ -98,6 +100,27 @@ class DefaultGroupCollectorTest {
verify(groupResolver, never()).resolve("trillian");
}
@Test
void shouldClearCacheOnLogout() {
MapCache<String, Set<String>> cache = mapCacheManager.getCache(DefaultGroupCollector.CACHE_NAME);
cache.put("trillian", ImmutableSet.of("awesome", "incredible"));
collector.clearCacheOnLogOut(new LogoutEvent("trillian"));
assertThat(cache.get("trillian")).isNull();
}
@Test
void shouldClearCacheOnUserDeletion() {
MapCache<String, Set<String>> cache = mapCacheManager.getCache(DefaultGroupCollector.CACHE_NAME);
cache.put("trillian", ImmutableSet.of("awesome", "incredible"));
collector.clearCacheOnUserDeletion(new UserEvent(HandlerEventType.DELETE, new User("trillian")));
assertThat(cache.get("trillian")).isNull();
}
@Test
void shouldNotCallResolverForAnonymous() {
groupResolvers.add(groupResolver);

View File

@@ -21,41 +21,51 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.security;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.http.HttpServletRequest;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
/**
* Created by masuewer on 04.07.18.
*/
@RunWith(MockitoJUnitRunner.class)
public class SecurityRequestsTest {
@ExtendWith(MockitoExtension.class)
class SecurityRequestsTest {
@Mock
private HttpServletRequest request;
@Test
public void testIsAuthenticationRequestWithContextPath() {
void shouldReturnTrueWithContextPath() {
when(request.getRequestURI()).thenReturn("/scm/api/auth/access_token");
when(request.getContextPath()).thenReturn("/scm");
assertTrue(SecurityRequests.isAuthenticationRequest(request));
assertThat(SecurityRequests.isAuthenticationRequest(request)).isTrue();
}
@Test
public void testIsAuthenticationRequest() throws Exception {
assertTrue(SecurityRequests.isAuthenticationRequest("/api/auth/access_token"));
assertTrue(SecurityRequests.isAuthenticationRequest("/api/v2/auth/access_token"));
assertFalse(SecurityRequests.isAuthenticationRequest("/api/repositories"));
assertFalse(SecurityRequests.isAuthenticationRequest("/api/v2/repositories"));
void shouldDetectAuthenticationResource() {
assertThat(SecurityRequests.isAuthenticationRequest("/api/auth/access_token")).isTrue();
assertThat(SecurityRequests.isAuthenticationRequest("/api/v2/auth/access_token")).isTrue();
assertThat(SecurityRequests.isAuthenticationRequest("/api/repositories")).isFalse();
assertThat(SecurityRequests.isAuthenticationRequest("/api/v2/repositories")).isFalse();
}
@Test
void shouldReturnFalseForLogout() {
when(request.getRequestURI()).thenReturn("/scm/api/auth/access_token");
when(request.getContextPath()).thenReturn("/scm");
when(request.getMethod()).thenReturn("DELETE");
assertThat(SecurityRequests.isAuthenticationRequest(request)).isFalse();
}
}