From 7c9fad0477dc4e4527fda67a68d0e11bbc32e495 Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Fri, 18 Feb 2005 20:08:03 +0000 Subject: [PATCH] Added filter chain --- .../acegisecurity/util/FilterChainProxy.java | 248 +++++++++++++++++ .../util/FilterChainProxyTests.java | 261 ++++++++++++++++++ .../acegisecurity/util/filtertest-valid.xml | 16 ++ 3 files changed, 525 insertions(+) create mode 100644 core/src/main/java/org/acegisecurity/util/FilterChainProxy.java create mode 100644 core/src/test/java/org/acegisecurity/util/FilterChainProxyTests.java diff --git a/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java b/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java new file mode 100644 index 0000000000..4c948e87fe --- /dev/null +++ b/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java @@ -0,0 +1,248 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * 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. + */ + +package net.sf.acegisecurity.util; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import net.sf.acegisecurity.ConfigAttribute; +import net.sf.acegisecurity.ConfigAttributeDefinition; +import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Delegates Filter requests to a Spring-managed bean. + *

+ * This class acts as a proxy on behalf of a target Filter that + * is defined in the Spring bean context. It is necessary to specify which + * target Filter should be proxied as a filter initialization + * parameter. + *

+ *

+ * On filter initialisation, the class will use Spring's {@link + * WebApplicationContextUtils#getWebApplicationContext(ServletContext sc)} + * method to obtain an ApplicationContext instance. It will + * expect to find the target Filter in this + * ApplicationContext. + *

+ *

+ * To use this filter, it is necessary to specify one of the following + * filter initialization parameters: + *

+ * + * If both initialization parameters are specified, targetBean + * takes priority. + *

+ * An additional initialization parameter, init, is also + * supported. If set to "lazy" the initialization will take + * place on the first HTTP request, rather than at filter creation time. This + * makes it possible to use FilterToBeanProxy with the Spring + * ContextLoaderServlet. Where possible you should not use this + * initialization parameter, instead using ContextLoaderListener. + *

+ * +// *
+// * <bean id="filterChain" class="net.sf.acegisecurity.FilterChain">
+// *   <property name="filters">
+// *   <value>
+// *     channelProcessingFilter=/*
+// *     authenticationProcessingFilter=/*
+// *     basicProcessingFilter=/*
+// *     sessionIntegrationFilter=/*
+// *     securityEnforcementFilter=/*
+// *   </value>
+// *   </property>
+// * </bean>
+// * 
+ * + * @author Carlos Sanchez + * @version $Id$ + */ +public class FilterChainProxy + implements Filter, InitializingBean +{ + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(FilterChainProxy.class); + + //~ Instance fields + // ======================================================== + + private Filter delegate; + + private List filters; + + private FilterConfig filterConfig; + + private boolean initialized = false; + + private FilterInvocationDefinitionSource filterInvocationDefinitionSource; + + //~ Methods + // ================================================================ + + public void setFilterInvocationDefinitionSource( + FilterInvocationDefinitionSource filterInvocationDefinitionSource) { + this.filterInvocationDefinitionSource = filterInvocationDefinitionSource; + } + + public FilterInvocationDefinitionSource getFilterInvocationDefinitionSource() { + return filterInvocationDefinitionSource; + } + + public void destroy() + { + Iterator it = filters.iterator(); + while ( it.hasNext() ) + { + Filter filter = (Filter) it.next(); + if ( filter != null ) + { + filter.destroy(); + } + } + } + + public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, + ServletException + { + if ( !initialized ) + { + doInit(); + } + + Iterator it = filters.iterator(); + while ( it.hasNext() ) + { + Filter filter = (Filter) it.next(); + filter.doFilter( request, response, chain ); + } + } + + public void init( FilterConfig filterConfig ) throws ServletException + { + this.filterConfig = filterConfig; + + String strategy = filterConfig.getInitParameter( "init" ); + + if ( (strategy != null) && strategy.toLowerCase().equals( "lazy" ) ) + { + return; + } + + doInit(); + } + + /** + * Allows test cases to override where application context obtained from. + * + * @param filterConfig + * which can be used to find the ServletContext + * @return the Spring application context + */ + protected ApplicationContext getContext( FilterConfig filterConfig ) + { + return WebApplicationContextUtils.getRequiredWebApplicationContext( filterConfig.getServletContext() ); + } + + private void doInit() throws ServletException + { + initialized = true; + + Iterator it = filters.iterator(); + while ( it.hasNext() ) + { + Filter filter = (Filter) it.next(); + filter.init( filterConfig ); + } + + } + + public void afterPropertiesSet() throws Exception { + if (filterInvocationDefinitionSource == null) { + throw new IllegalArgumentException( + "filterInvocationDefinitionSource must be specified"); + } + + Iterator iter = this.filterInvocationDefinitionSource + .getConfigAttributeDefinitions(); + + if (iter == null) { + if (logger.isWarnEnabled()) { + logger.warn( + "Could not validate configuration attributes as the FilterInvocationDefinitionSource did not return a ConfigAttributeDefinition Iterator"); + } + + return; + } + + Set set = new HashSet(); + + while (iter.hasNext()) { + ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter + .next(); + Iterator attributes = def.getConfigAttributes(); + + while (attributes.hasNext()) { + ConfigAttribute attr = (ConfigAttribute) attributes.next(); + } + } + + if (set.size() == 0) { + if (logger.isInfoEnabled()) { + logger.info("Validated configuration attributes"); + } + } else { + throw new IllegalArgumentException( + "Unsupported configuration attributes: " + set.toString()); + } + + iter = filterInvocationDefinitionSource.getConfigAttributeDefinitions(); + while ( iter.hasNext() ) + { + ConfigAttributeDefinition element = (ConfigAttributeDefinition) iter.next(); + Iterator configAttributes = element.getConfigAttributes(); + } + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/acegisecurity/util/FilterChainProxyTests.java b/core/src/test/java/org/acegisecurity/util/FilterChainProxyTests.java new file mode 100644 index 0000000000..90342528ca --- /dev/null +++ b/core/src/test/java/org/acegisecurity/util/FilterChainProxyTests.java @@ -0,0 +1,261 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * 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. + */ + +package net.sf.acegisecurity.util; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.MockFilterConfig; +import net.sf.acegisecurity.MockHttpServletRequest; +import net.sf.acegisecurity.MockHttpServletResponse; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/** + * Tests {@link FilterChainProxy}. + * + * @author Carlos Sanchez + * @version $Id$ + */ +public class FilterChainProxyTests + extends TestCase +{ + //~ Constructors + // =========================================================== + + public FilterChainProxyTests() + { + super(); + } + + public FilterChainProxyTests( String arg0 ) + { + super( arg0 ); + } + + //~ Methods + // ================================================================ + + public final void setUp() throws Exception + { + super.setUp(); +// ApplicationContext applicationContext = new ClassPathXmlApplicationContext( +// "net/sf/acegisecurity/util/filtertest-valid.xml" ); +// FilterChainProxy filterChainProxy = (FilterChainProxy)applicationContext.getBean("filterChain"); +// System.out.println(filterChainProxy); + } + + public static void main( String[] args ) + { + junit.textui.TestRunner.run( FilterChainProxyTests.class ); + } + + public void testDetectsTargetBeanIsNotAFilter() throws Exception + { + // Setup our filter + MockFilterConfig config = new MockFilterConfig(); + config.setInitParmeter( "targetClass", "net.sf.acegisecurity.util.MockNotAFilter" ); + + FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" ); + + try + { + filter.init( config ); + fail( "Should have thrown ServletException" ); + } + catch ( ServletException expected ) + { + assertEquals( "Bean 'mockNotAFilter' does not implement javax.servlet.Filter", expected.getMessage() ); + } + } + + public void testDetectsTargetBeanNotInBeanContext() throws Exception + { + // Setup our filter + MockFilterConfig config = new MockFilterConfig(); + config.setInitParmeter( "targetBean", "WRONG_NAME" ); + + FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" ); + + try + { + filter.init( config ); + fail( "Should have thrown ServletException" ); + } + catch ( ServletException expected ) + { + assertEquals( "targetBean 'WRONG_NAME' not found in context", expected.getMessage() ); + } + } + + public void testIgnoresEmptyTargetBean() throws Exception + { + // Setup our filter + MockFilterConfig config = new MockFilterConfig(); + config.setInitParmeter( "targetClass", "net.sf.acegisecurity.util.FilterChainProxy" ); + config.setInitParmeter( "targetBean", "" ); + + // Setup our expectation that the filter chain will be invoked + MockFilterChain chain = new MockFilterChain( true ); + + MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletRequest request = new MockHttpServletRequest( "/go" ); + + FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" ); + + executeFilterInContainerSimulator( config, filter, request, response, chain ); + } + + public void testNormalOperationWithLazyTrue() throws Exception + { + // Setup our filter + MockFilterConfig config = new MockFilterConfig(); + config.setInitParmeter( "targetBean", "filterChain" ); + config.setInitParmeter( "init", "lazy" ); + + // Setup our expectation that the filter chain will be invoked + MockFilterChain chain = new MockFilterChain( true ); + + MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletRequest request = new MockHttpServletRequest( "/go" ); + + FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" ); + + executeFilterInContainerSimulator( config, filter, request, response, chain ); + } + + public void testNormalOperationWithSpecificBeanName() throws Exception + { + // Setup our filter + MockFilterConfig config = new MockFilterConfig(); + config.setInitParmeter( "targetBean", "filterChain" ); + + // Setup our expectation that the filter chain will be invoked + MockFilterChain chain = new MockFilterChain( true ); + + MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletRequest request = new MockHttpServletRequest( "/go" ); + + FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" ); + + executeFilterInContainerSimulator( config, filter, request, response, chain ); + } + + public void testNormalOperationWithTargetClass() throws Exception + { + // Setup our filter + MockFilterConfig config = new MockFilterConfig(); + config.setInitParmeter( "targetClass", "net.sf.acegisecurity.util.FilterChainProxy" ); + + // Setup our expectation that the filter chain will be invoked + MockFilterChain chain = new MockFilterChain( true ); + + MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletRequest request = new MockHttpServletRequest( "/go" ); + + FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" ); + + executeFilterInContainerSimulator( config, filter, request, response, chain ); + } + + public void testNullDelegateDoesNotCauseNullPointerException() throws Exception + { + // Setup our filter + MockFilterConfig config = new MockFilterConfig(); + config.setInitParmeter( "targetBean", "aFilterThatDoesntExist" ); + config.setInitParmeter( "init", "lazy" ); + + // Setup our expectation that the filter chain will be invoked + MockFilterChain chain = new MockFilterChain( true ); + + MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletRequest request = new MockHttpServletRequest( "/go" ); + + FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" ); + + // do not init (which would hapen if called .doFilter) + filter.destroy(); + } + + private void executeFilterInContainerSimulator( FilterConfig filterConfig, Filter filter, ServletRequest request, + ServletResponse response, FilterChain filterChain ) throws ServletException, IOException + { + filter.init( filterConfig ); + filter.doFilter( request, response, filterChain ); + filter.destroy(); + } + + //~ Inner Classes + // ========================================================== + + private class MockFilterChain + implements FilterChain + { + private boolean expectToProceed; + + public MockFilterChain( boolean expectToProceed ) + { + this.expectToProceed = expectToProceed; + } + + private MockFilterChain() + { + super(); + } + + public void doFilter( ServletRequest request, ServletResponse response ) throws IOException, ServletException + { + if ( expectToProceed ) + { + assertTrue( true ); + } + else + { + fail( "Did not expect filter chain to proceed" ); + } + } + } + + private class MockFilterToBeanProxy + extends FilterToBeanProxy + { + private String appContextLocation; + + public MockFilterToBeanProxy( String appContextLocation ) + { + this.appContextLocation = appContextLocation; + } + + private MockFilterToBeanProxy() + { + super(); + } + + protected ApplicationContext getContext( FilterConfig filterConfig ) + { + return new ClassPathXmlApplicationContext( appContextLocation ); + } + } +} \ No newline at end of file diff --git a/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml b/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml index 78c34b76e4..ccb388a6e1 100644 --- a/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml +++ b/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml @@ -24,4 +24,20 @@ + + + + + /*=mockFilter + + + + +