/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.security; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import javax.servlet.http.HttpServletResponse; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.security.auth.AbstractAuthenticationProviderTest; import org.geoserver.security.impl.GeoServerUser; import org.geoserver.security.validation.FilterConfigException; import org.geoserver.security.xml.XMLUserGroupService; import org.geoserver.test.http.AbstractHttpClient; import org.geotools.data.ows.HTTPResponse; import org.junit.Test; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; public class AuthKeyAuthenticationTest extends AbstractAuthenticationProviderTest { class TestHttpClient extends AbstractHttpClient { private String authkey; private String response; public TestHttpClient(String authkey, String response) { super(); this.authkey = authkey; this.response = response; } @Override public HTTPResponse get(final URL url) throws IOException { return new HTTPResponse() { @Override public InputStream getResponseStream() throws IOException { if(url.getPath().substring(1).equals(authkey)) { return new ByteArrayInputStream(new String(response).getBytes()); } return new ByteArrayInputStream(new String("").getBytes()); } @Override public String getResponseHeader(String arg0) { // TODO Auto-generated method stub return null; } @Override public String getResponseCharset() { // TODO Auto-generated method stub return null; } @Override public String getContentType() { // TODO Auto-generated method stub return null; } @Override public void dispose() { // TODO Auto-generated method stub } }; } @Override public HTTPResponse post(URL url, InputStream in, String arg) throws IOException { return null; } } @Override protected void onSetUp(org.geoserver.data.test.SystemTestData testData) throws Exception { super.onSetUp(testData); } @Test public void testMapperParameters() throws Exception { String authKeyUrlParam = "myAuthKeyParams"; String filterName = "testAuthKeyParams1"; AuthenticationKeyFilterConfig config = new AuthenticationKeyFilterConfig(); config.setClassName(GeoServerAuthenticationKeyFilter.class.getName()); config.setName(filterName); config.setUserGroupServiceName("ug1"); config.setAuthKeyParamName(authKeyUrlParam); config.setAuthKeyMapperName("fakeMapper"); Map<String, String> mapperParams = new HashMap<String, String>(); mapperParams.put("param1", "value1"); mapperParams.put("param2", "value2"); config.setMapperParameters(mapperParams); getSecurityManager().saveFilter(config); GeoServerAuthenticationKeyFilter filter = (GeoServerAuthenticationKeyFilter) getSecurityManager().loadFilter(filterName); assertTrue(filter.getMapper() instanceof FakeMapper); FakeMapper fakeMapper = (FakeMapper)filter.getMapper(); assertEquals("value1", fakeMapper.getMapperParameter("param1")); assertEquals("value2", fakeMapper.getMapperParameter("param2")); } @Test public void testMapperParamsFilterConfigValidation() throws Exception { AuthenticationKeyFilterConfigValidator validator=new AuthenticationKeyFilterConfigValidator(getSecurityManager()); AuthenticationKeyFilterConfig config = new AuthenticationKeyFilterConfig(); config.setClassName(GeoServerAuthenticationKeyFilter.class.getName()); config.setName("fakeFilter"); config.setUserGroupServiceName(XMLUserGroupService.DEFAULT_NAME); config.setAuthKeyParamName("authkey"); config.setAuthKeyMapperName("fakeMapper"); Map<String, String> mapperParams = new HashMap<String, String>(); mapperParams.put("param1", "value1"); mapperParams.put("param2", "value2"); config.setMapperParameters(mapperParams); boolean failed = false; try { validator.validateFilterConfig(config); } catch (FilterConfigException ex){ failed=true; } assertFalse(failed); mapperParams.put("param3", "value3"); try { validator.validateFilterConfig(config); } catch (FilterConfigException ex){ assertEquals(AuthenticationKeyFilterConfigException.INVALID_AUTH_KEY_MAPPER_PARAMETER_$3, ex.getId()); assertEquals(1,ex.getArgs().length); assertEquals("param3",ex.getArgs()[0]); LOGGER.info(ex.getMessage()); failed=true; } assertTrue(failed); } @Test public void testFileBasedWithSession() throws Exception { String authKeyUrlParam = "myAuthKey"; String filterName = "testAuthKeyFilter1"; AuthenticationKeyFilterConfig config = new AuthenticationKeyFilterConfig(); config.setClassName(GeoServerAuthenticationKeyFilter.class.getName()); config.setName(filterName); config.setUserGroupServiceName("ug1"); config.setAuthKeyParamName(authKeyUrlParam); config.setAuthKeyMapperName("propertyMapper"); getSecurityManager().saveFilter(config); GeoServerAuthenticationKeyFilter filter = (GeoServerAuthenticationKeyFilter) getSecurityManager().loadFilter(filterName); PropertyAuthenticationKeyMapper mapper = (PropertyAuthenticationKeyMapper) filter.getMapper(); mapper.synchronize(); prepareFilterChain(pattern,filterName); modifyChain(pattern, false, true, null); SecurityContextHolder.getContext().setAuthentication(null); // Test entry point MockHttpServletRequest request = createRequest("/foo/bar"); MockHttpServletResponse response = new MockHttpServletResponse(); MockFilterChain chain = new MockFilterChain(); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); // test success String authKey=null; for (Entry<Object,Object> entry : mapper.authKeyProps.entrySet()) { if (testUserName.equals(entry.getValue())) { authKey=(String)entry.getKey(); break; } } request = createRequest("/foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); request.setQueryString(authKeyUrlParam+"=" + authKey); request.addParameter(authKeyUrlParam, authKey); getProxy().doFilter(request, response, chain); assertFalse(response.getStatus() == MockHttpServletResponse.SC_MOVED_TEMPORARILY); SecurityContext ctx = (SecurityContext) request.getSession(false).getAttribute( HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); assertNotNull(ctx); Authentication auth = ctx.getAuthentication(); assertNotNull(auth); assertNull(SecurityContextHolder.getContext().getAuthentication()); checkForAuthenticatedRole(auth); assertEquals(testUserName, auth.getPrincipal()); // check unknown user username = "unknown"; password = username; request = createRequest("/foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); request.setQueryString(authKeyUrlParam+"=abc"); request.addParameter(authKeyUrlParam, "abc"); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); assertNull(SecurityContextHolder.getContext().getAuthentication()); // check disabled user username = testUserName; password = username; updateUser("ug1", username, false); request = createRequest("/foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); request.setQueryString(authKeyUrlParam+"=" + authKey); request.addParameter(authKeyUrlParam, authKey); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); assertNull(SecurityContextHolder.getContext().getAuthentication()); updateUser("ug1", username, true); insertAnonymousFilter(); request = createRequest("foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); // Anonymous context is not stored in http session, no further testing removeAnonymousFilter(); } @Test public void testUserPropertyWithCache() throws Exception { String authKeyUrlParam = "myAuthKey"; String filterName = "testAuthKeyFilter2"; AuthenticationKeyFilterConfig config = new AuthenticationKeyFilterConfig(); config.setClassName(GeoServerAuthenticationKeyFilter.class.getName()); config.setName(filterName); config.setUserGroupServiceName("ug1"); config.setAuthKeyParamName(authKeyUrlParam); config.setAuthKeyMapperName("userPropertyMapper"); getSecurityManager().saveFilter(config); GeoServerAuthenticationKeyFilter filter = (GeoServerAuthenticationKeyFilter) getSecurityManager().loadFilter(filterName); UserPropertyAuthenticationKeyMapper mapper = (UserPropertyAuthenticationKeyMapper) filter.getMapper(); mapper.synchronize(); prepareFilterChain(pattern,filterName); modifyChain(pattern, false, false, null); SecurityContextHolder.getContext().setAuthentication(null); // Test entry point MockHttpServletRequest request = createRequest("/foo/bar"); MockHttpServletResponse response = new MockHttpServletResponse(); MockFilterChain chain = new MockFilterChain(); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); // test success GeoServerUser user= (GeoServerUser) getSecurityManager().loadUserGroupService("ug1").loadUserByUsername(testUserName); String authKey=user.getProperties().getProperty(mapper.getUserPropertyName()); assertNotNull(authKey); request = createRequest("/foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); request.setQueryString(authKeyUrlParam+"=" + authKey); request.addParameter(authKeyUrlParam, authKey); getProxy().doFilter(request, response, chain); assertFalse(response.getStatus() == MockHttpServletResponse.SC_MOVED_TEMPORARILY); Authentication auth = (Authentication) getCache().get(filterName,authKey); assertNotNull(auth); assertNull(request.getSession(false)); checkForAuthenticatedRole(auth); assertEquals(testUserName, auth.getPrincipal()); // check unknown user username = "unknown"; password = username; request = createRequest("/foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); request.setQueryString(authKeyUrlParam+"=abc"); request.addParameter(authKeyUrlParam, "abc"); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); assertNull(SecurityContextHolder.getContext().getAuthentication()); getCache().removeAll(); // check disabled user username = testUserName; password = username; updateUser("ug1", username, false); request = createRequest("/foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); request.setQueryString(authKeyUrlParam+"=" + authKey); request.addParameter(authKeyUrlParam, authKey); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); assertNull(getCache().get(filterName, authKey)); assertNull(SecurityContextHolder.getContext().getAuthentication()); updateUser("ug1", username, true); insertAnonymousFilter(); request = createRequest("foo/bar"); response = new MockHttpServletResponse(); chain = new MockFilterChain(); getProxy().doFilter(request, response, chain); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); // Anonymous context is not stored in http session, no further testing removeAnonymousFilter(); } @Test public void testWebServiceAutheKeyMapper() throws Exception { GeoServerUserGroupService ugservice = createUserGroupService("testWebServiceAuthKey"); GeoServerUserGroupStore ugstore = ugservice.createStore(); GeoServerUser u1 = ugstore.createUserObject("user1", "passwd1", true); ugstore.addUser(u1); GeoServerUser u2 = ugstore.createUserObject("user2", "passwd2", true); ugstore.addUser(u2); ugstore.store(); WebServiceAuthenticationKeyMapper propMapper = GeoServerExtensions.extensions(WebServiceAuthenticationKeyMapper.class).iterator().next(); propMapper.setUserGroupServiceName("testWebServiceAuthKey"); propMapper.setSecurityManager(getSecurityManager()); propMapper.setWebServiceUrl("http://service/{key}"); propMapper.setHttpClient(new TestHttpClient("testkey", "user1")); GeoServerUser user = propMapper.getUser("testkey"); assertNotNull(user); assertEquals(user.getUsername(), "user1"); boolean error = false; try { user = propMapper.getUser("wrongkey"); } catch(UsernameNotFoundException e) { error = true; } assertTrue(error); } @Test public void testWebServiceAutheKeyMapperSearchUser() throws Exception { GeoServerUserGroupService ugservice = createUserGroupService("testWebServiceAuthKey2"); GeoServerUserGroupStore ugstore = ugservice.createStore(); GeoServerUser u1 = ugstore.createUserObject("user1", "passwd1", true); ugstore.addUser(u1); GeoServerUser u2 = ugstore.createUserObject("user2", "passwd2", true); ugstore.addUser(u2); ugstore.store(); WebServiceAuthenticationKeyMapper propMapper = GeoServerExtensions.extensions(WebServiceAuthenticationKeyMapper.class).iterator().next(); propMapper.setUserGroupServiceName("testWebServiceAuthKey2"); propMapper.setSecurityManager(getSecurityManager()); propMapper.setWebServiceUrl("http://service/{key}"); propMapper.setSearchUser("^.*?\"user\"\\s*:\\s*\"([^\"]+)\".*$"); propMapper.setHttpClient(new TestHttpClient("testkey", "{\n \"user\": \"user1\", \"detail\": \"mydetail\"\n }")); GeoServerUser user = propMapper.getUser("testkey"); assertNotNull(user); assertEquals(user.getUsername(), "user1"); propMapper.setSearchUser("^.*?<username>(.*?)</username>.*$"); propMapper.setHttpClient(new TestHttpClient("testkey", "<root>\n<userdetail>\n<username>user1</username>\n</userdetail>\n</root>")); user = propMapper.getUser("testkey"); assertNotNull(user); assertEquals(user.getUsername(), "user1"); boolean error = false; try { user = propMapper.getUser("wrongkey"); } catch(UsernameNotFoundException e) { error = true; } assertTrue(error); } @Test public void testAuthKeyMapperSynchronize() throws Exception { GeoServerUserGroupService ugservice = createUserGroupService("testAuthKey"); GeoServerUserGroupStore ugstore = ugservice.createStore(); GeoServerUser u1 = ugstore.createUserObject("user1", "passwd1", true); ugstore.addUser(u1); GeoServerUser u2 = ugstore.createUserObject("user2", "passwd2", true); ugstore.addUser(u2); ugstore.store(); PropertyAuthenticationKeyMapper propMapper = GeoServerExtensions.extensions(PropertyAuthenticationKeyMapper.class).iterator().next(); UserPropertyAuthenticationKeyMapper userpropMapper = GeoServerExtensions.extensions(UserPropertyAuthenticationKeyMapper.class).iterator().next(); propMapper.setSecurityManager(getSecurityManager()); propMapper.setUserGroupServiceName("testAuthKey"); userpropMapper.setSecurityManager(getSecurityManager()); userpropMapper.setUserGroupServiceName("testAuthKey"); // File Property Mapper assertEquals(2,propMapper.synchronize()); File authKeyFile = new File(getSecurityManager().getUserGroupRoot(),"testAuthKey"); authKeyFile=new File(authKeyFile,"authkeys.properties"); assertTrue(authKeyFile.exists()); Properties props = new Properties(); loadPropFile(authKeyFile, props); assertEquals(2, props.size()); String user1KeyA=null,user2KeyA=null,user3KeyA=null, user1KeyB=null,user2KeyB=null,user3KeyB=null; for (Entry<Object, Object> entry : props.entrySet()) { if ("user1".equals(entry.getValue())) user1KeyA=(String) entry.getKey(); if ("user2".equals(entry.getValue())) user2KeyA=(String) entry.getKey(); } assertNotNull(user1KeyA); assertNotNull(user2KeyA); assertEquals(u1, propMapper.getUser(user1KeyA)); assertEquals(u2, propMapper.getUser(user2KeyA)); assertNull(propMapper.getUser("blblal")); // user property mapper assertEquals(2,userpropMapper.synchronize()); u1 = (GeoServerUser) ugservice.loadUserByUsername("user1"); user1KeyB=u1.getProperties().getProperty(userpropMapper.getUserPropertyName()); u2 = (GeoServerUser) ugservice.loadUserByUsername("user2"); user2KeyB=u2.getProperties().getProperty(userpropMapper.getUserPropertyName()); assertEquals(u1, userpropMapper.getUser(user1KeyB)); assertEquals(u2, userpropMapper.getUser(user2KeyB)); assertNull(userpropMapper.getUser("blblal")); // modify user/group database ugstore = ugservice.createStore(); GeoServerUser u3 = ugstore.createUserObject("user3", "passwd3", true); ugstore.addUser(u3); ugstore.removeUser(u1); ugstore.store(); assertEquals(1,propMapper.synchronize()); props = new Properties(); loadPropFile(authKeyFile, props); assertEquals(2, props.size()); for (Entry<Object, Object> entry : props.entrySet()) { if ("user2".equals(entry.getValue())) assertEquals(user2KeyA,(String) entry.getKey()); if ("user3".equals(entry.getValue())) user3KeyA=(String) entry.getKey(); } assertNotNull(user3KeyA); assertNull(propMapper.getUser(user1KeyA)); assertEquals(u2, propMapper.getUser(user2KeyA)); assertEquals(u3,propMapper.getUser(user3KeyA)); // user property mapper assertEquals(1,userpropMapper.synchronize()); u2 = (GeoServerUser) ugservice.loadUserByUsername("user2"); assertEquals(user2KeyB,u2.getProperties().getProperty(userpropMapper.getUserPropertyName())); u3 = (GeoServerUser) ugservice.loadUserByUsername("user3"); user3KeyB=u3.getProperties().getProperty(userpropMapper.getUserPropertyName()); assertNull( userpropMapper.getUser(user1KeyB)); assertEquals(u2, userpropMapper.getUser(user2KeyB)); assertEquals(u3, userpropMapper.getUser(user3KeyB)); // test disabled user ugstore = ugservice.createStore(); u2 = (GeoServerUser) ugstore.loadUserByUsername("user2"); u2.setEnabled(false); ugstore.updateUser(u2); ugstore.store(); assertNull( propMapper.getUser(user2KeyA)); assertNull( userpropMapper.getUser(user2KeyB)); } private void loadPropFile(File authKeyFile, Properties props) throws FileNotFoundException, IOException { FileInputStream propFile = new FileInputStream(authKeyFile); try { props.load(propFile); } finally { propFile.close(); } } }