/*************************GO-LICENSE-START********************************* * Copyright 2014 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *************************GO-LICENSE-END***********************************/ package com.thoughtworks.go.server.security; import com.thoughtworks.go.config.*; import com.thoughtworks.go.server.domain.Username; import com.thoughtworks.go.server.service.GoConfigService; import com.thoughtworks.go.server.service.PluginRoleService; import com.thoughtworks.go.server.service.RoleConfigService; import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult; import com.thoughtworks.go.util.GoConfigFileHelper; import com.thoughtworks.go.util.TimeProvider; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.Authentication; import org.springframework.security.GrantedAuthority; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.providers.TestingAuthenticationToken; import org.springframework.security.ui.FilterChainOrder; import org.springframework.security.userdetails.User; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import static com.thoughtworks.go.server.security.RemoveAdminPermissionFilter.SECURITY_CONFIG_LAST_CHANGE; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:WEB-INF/applicationContext-global.xml", "classpath:WEB-INF/applicationContext-dataLocalAccess.xml", "classpath:WEB-INF/applicationContext-acegi-security.xml" }) public class RemoveAdminPermissionFilterIntegrationTest { private FilterChain chain; private HttpServletRequest request; private HttpServletResponse response; private HttpSession session; @Autowired private GoConfigService goConfigService; @Autowired private GoConfigDao goConfigDao; @Autowired private CachedGoConfig cachedGoConfig; @Autowired private PluginRoleService pluginRoleService; @Autowired private RoleConfigService roleService; private static final GoConfigFileHelper configHelper = new GoConfigFileHelper(); private TimeProvider timeProvider; @Before public void setUp() throws Exception { configHelper.usingCruiseConfigDao(goConfigDao); configHelper.onSetUp(); chain = mock(FilterChain.class); request = mock(HttpServletRequest.class); response = mock(HttpServletResponse.class); session = mock(HttpSession.class); timeProvider = mock(TimeProvider.class); when(request.getRequestedSessionId()).thenReturn("session_id"); when(timeProvider.currentTimeMillis()).thenReturn(100L); when(request.getSession()).thenReturn(session); } @After public void tearDown() throws IOException, ServletException { configHelper.usingCruiseConfigDao(goConfigDao); configHelper.onTearDown(); SecurityContextHolder.getContext().setAuthentication(null); verifyNoMoreInteractions(response); } @Test public void testGetOrder() throws IOException, ServletException { RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); assertThat(filter.getOrder(), is(FilterChainOrder.BASIC_PROCESSING_FILTER - 1)); } @Test public void testShouldContinueWithChainReturnIfAuthenticationIsNull() throws IOException, ServletException { RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.doFilterHttp(request, response, chain); verify(chain).doFilter(request, response); verifyNoMoreInteractions(chain); } @Test public void testShouldContinueWithChainReturnIfCruiseConfigIsNull() throws IOException, ServletException { RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.doFilterHttp(request, response, chain); verify(chain).doFilter(request, response); verifyNoMoreInteractions(chain); } @Test public void testShouldContinueWithTheChainIfTheSecurityConfigHasNotChanged() throws IOException, ServletException { Authentication authentication = setupAuthentication(); RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.doFilterHttp(request, response, chain); modifyArtifactRoot(); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(true)); } private void modifyArtifactRoot() { configHelper.currentConfig().server().updateArtifactRoot("something/else");//Config changed but security config did not. cachedGoConfig.forceReload(); } @Test public void testShouldReAuthenticateIffSecurityConfigChange() throws IOException, ServletException { Authentication authentication = setupAuthentication(); when(session.getAttribute(RemoveAdminPermissionFilter.SECURITY_CONFIG_LAST_CHANGE)).thenReturn(0L).thenReturn(0L).thenReturn(100L); RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.initialize(); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(true)); turnOnSecurity("pavan");//This changes the security config filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(false)); authentication.setAuthenticated(true); modifyArtifactRoot();//This changes something else filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(true)); } @Test public void testShouldForceReAuthenticationOnRoleConfigChange() throws Exception { final ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class); final Username username = new Username("bob"); final RoleConfig admin = new RoleConfig(new CaseInsensitiveString("admin")); final Authentication authentication = setupAuthentication(); final RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.initialize(); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(true)); roleService.create(username, admin, new HttpLocalizedOperationResult()); verify(session).setAttribute(eq(SECURITY_CONFIG_LAST_CHANGE), argumentCaptor.capture()); when(session.getAttribute(SECURITY_CONFIG_LAST_CHANGE)).thenReturn(argumentCaptor.getValue()); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(false)); } @Test public void testShouldReAuthenticateOnlyOnceAfterConfigChange() throws IOException, ServletException { goConfigService.security().securityAuthConfigs().add(new SecurityAuthConfig("github", "cd.go.authorization.github")); goConfigService.security().addRole(new PluginRoleConfig("spacetiger", "github")); Authentication authentication = setupAuthentication(); when(session.getAttribute(SECURITY_CONFIG_LAST_CHANGE)).thenReturn(0L).thenReturn(0L).thenReturn(100L); RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.initialize(); assertThat(authentication.isAuthenticated(), is(true));//good initial state filter.doFilterHttp(request, response, chain); pluginRoleService.invalidateRolesFor("cd.go.authorization.github"); assertThat(authentication.isAuthenticated(), is(true)); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(false)); authentication.setAuthenticated(true); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(true)); } @Test public void testShouldReAuthenticateOnlyOnceAfterAuthorizationPluginUnloaded() throws IOException, ServletException { Authentication authentication = setupAuthentication(); when(session.getAttribute(SECURITY_CONFIG_LAST_CHANGE)).thenReturn(0L).thenReturn(0L).thenReturn(100L); RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.initialize(); assertThat(authentication.isAuthenticated(), is(true));//good initial state filter.doFilterHttp(request, response, chain); turnOnSecurity("pavan"); assertThat(authentication.isAuthenticated(), is(true)); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(false)); authentication.setAuthenticated(true); filter.doFilterHttp(request, response, chain); assertThat(authentication.isAuthenticated(), is(true)); } @Test public void testShouldNotSetLastSecurityChangeAsASessionAttributeIfNotAuthenticatedYet() throws IOException, ServletException { when(timeProvider.currentTimeMillis()).thenReturn(100L); when(session.getAttribute(SECURITY_CONFIG_LAST_CHANGE)).thenReturn(null); RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.initialize(); turnOnSecurity("pavan"); filter.doFilterHttp(request, response, chain); verifyNoMoreInteractions(session); } @Test public void testShouldSetLastSecurityChangeAsASessionAttributeIfItsNotFound() throws IOException, ServletException { setupAuthentication(); when(timeProvider.currentTimeMillis()).thenReturn(100L); when(session.getAttribute(SECURITY_CONFIG_LAST_CHANGE)).thenReturn(null); RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.initialize(); turnOnSecurity("pavan"); filter.doFilterHttp(request, response, chain); verify(session).setAttribute(SECURITY_CONFIG_LAST_CHANGE, 100L); } @Test public void testShouldNotSetLastSecurityChangeAsASessionAttributeIfItHasNotChanged() throws IOException, ServletException { setupAuthentication(); when(timeProvider.currentTimeMillis()).thenReturn(100L); when(session.getAttribute(SECURITY_CONFIG_LAST_CHANGE)).thenReturn(100L); RemoveAdminPermissionFilter filter = new RemoveAdminPermissionFilter(goConfigService, timeProvider, pluginRoleService); filter.doFilterHttp(request, response, chain); verify(session).getAttribute(SECURITY_CONFIG_LAST_CHANGE);//Make sure the stub was indeed called. verifyNoMoreInteractions(session);//Make sure we did not set the config md5 again } private void turnOnSecurity(String username) throws IOException { configHelper.turnOnSecurity(); configHelper.addAdmins(username); cachedGoConfig.forceReload(); } private void stubSessionToReturn0() { when(session.getAttribute(SECURITY_CONFIG_LAST_CHANGE)).thenReturn(0L); } private Authentication setupAuthentication() { GrantedAuthority[] authorities = {}; Authentication authentication = new TestingAuthenticationToken(new User("loser", "secret", true, true, true, true, authorities), null, authorities); SecurityContextHolder.getContext().setAuthentication(authentication); authentication.setAuthenticated(true); return authentication; } }