SEC-599: Refactoring of FilterInvocationDefinitionSource implementations to use a LinkedHashMap internally rather than list of "EntryHolder" classes.

This commit is contained in:
Luke Taylor 2008-01-18 13:04:46 +00:00
parent ea70845987
commit 01569e5746
6 changed files with 55 additions and 175 deletions

View File

@ -17,8 +17,8 @@ package org.springframework.security.intercept.web;
import org.springframework.security.ConfigAttributeDefinition;
import java.util.List;
import java.util.Vector;
import java.util.Map;
import java.util.LinkedHashMap;
/**
@ -29,14 +29,13 @@ import java.util.Vector;
*/
public abstract class AbstractFilterInvocationDefinitionSource implements FilterInvocationDefinitionSource {
protected List requestMap = new Vector();
private Map requestMap = new LinkedHashMap();
private boolean convertUrlToLowercaseBeforeComparison = false;
//~ Methods ========================================================================================================
public ConfigAttributeDefinition getAttributes(Object object)
throws IllegalArgumentException {
public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException {
if ((object == null) || !this.supports(object.getClass())) {
throw new IllegalArgumentException("Object must be a FilterInvocation");
}
@ -78,7 +77,7 @@ public abstract class AbstractFilterInvocationDefinitionSource implements Filter
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
}
List getRequestMap() {
Map getRequestMap() {
return requestMap;
}
}

View File

@ -1,12 +1,14 @@
package org.springframework.security.intercept.web;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.util.FilterChainProxy;
import javax.servlet.Filter;
import java.util.*;
import java.util.regex.Pattern;
/**
* Used internally to provide backward compatibility for configuration of FilterChainProxy using a
@ -23,51 +25,34 @@ public class FIDSToFilterChainMapConverter {
private LinkedHashMap filterChainMap = new LinkedHashMap();
public FIDSToFilterChainMapConverter(FilterInvocationDefinitionSource fids, ApplicationContext appContext) {
public FIDSToFilterChainMapConverter(FilterInvocationDefinitionSource source, ApplicationContext appContext) {
// TODO: Check if this is necessary. Retained from refactoring of FilterChainProxy
Assert.notNull(source.getConfigAttributeDefinitions(), "FilterChainProxy requires the " +
"FilterInvocationDefinitionSource to return a non-null response to getConfigAttributeDefinitions()");
Assert.isTrue(
source instanceof PathBasedFilterInvocationDefinitionMap ||
source instanceof RegExpBasedFilterInvocationDefinitionMap,
"Can't handle FilterInvocationDefinitionSource type " + source.getClass());
List requestMap;
AbstractFilterInvocationDefinitionSource fids = (AbstractFilterInvocationDefinitionSource)source;
Map requestMap = fids.getRequestMap();
Iterator paths = requestMap.keySet().iterator();
// TODO: Check if this is necessary. Retained from refactoring of FilterChainProxy
if (fids.getConfigAttributeDefinitions() == null) {
throw new IllegalArgumentException("FilterChainProxy requires the FilterInvocationDefinitionSource to " +
"return a non-null response to getConfigAttributeDefinitions()");
}
if (fids instanceof PathBasedFilterInvocationDefinitionMap) {
requestMap = ((PathBasedFilterInvocationDefinitionMap)fids).getRequestMap();
} else if (fids instanceof RegExpBasedFilterInvocationDefinitionMap) {
requestMap = ((RegExpBasedFilterInvocationDefinitionMap)fids).getRequestMap();
} else {
throw new IllegalArgumentException("Can't handle FilterInvocationDefinitionSource type " + fids.getClass());
}
Iterator entries = requestMap.iterator();
while (entries.hasNext()) {
Object entry = entries.next();
String path;
ConfigAttributeDefinition configAttributeDefinition;
if (entry instanceof PathBasedFilterInvocationDefinitionMap.EntryHolder) {
path = ((PathBasedFilterInvocationDefinitionMap.EntryHolder)entry).getAntPath();
configAttributeDefinition = ((PathBasedFilterInvocationDefinitionMap.EntryHolder)entry).getConfigAttributeDefinition();
} else {
path = ((RegExpBasedFilterInvocationDefinitionMap.EntryHolder)entry).getCompiledPattern().pattern();
configAttributeDefinition = ((RegExpBasedFilterInvocationDefinitionMap.EntryHolder)entry).getConfigAttributeDefinition();
}
while (paths.hasNext()) {
Object entry = paths.next();
String path = entry instanceof Pattern ? ((Pattern)entry).pattern() : (String)entry;
ConfigAttributeDefinition configAttributeDefinition = (ConfigAttributeDefinition) requestMap.get(entry);
List filters = new ArrayList();
Iterator attributes = configAttributeDefinition.getConfigAttributes();
while (attributes.hasNext()) {
ConfigAttribute attr = (ConfigAttribute) attributes.next();
String filterName = attr.getAttribute();
if (filterName == null) {
throw new IllegalArgumentException("Configuration attribute: '" + attr
+ "' returned null to the getAttribute() method, which is invalid when used with FilterChainProxy");
}
Assert.notNull(filterName, "Configuration attribute: '" + attr + "' returned null to the getAttribute() " +
"method, which is invalid when used with FilterChainProxy");
if (!filterName.equals(FilterChainProxy.TOKEN_NONE)) {
filters.add(appContext.getBean(filterName, Filter.class));

View File

@ -23,22 +23,20 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
/**
* Maintains a <code>List</code> of <code>ConfigAttributeDefinition</code>s associated with different HTTP request
* URL Apache Ant path-based patterns.<p>Apache Ant path expressions are used to match a HTTP request URL against a
* <code>ConfigAttributeDefinition</code>.</p>
* <p>The order of registering the Ant paths using the {@link #addSecureUrl(String,ConfigAttributeDefinition)} is
* <code>ConfigAttributeDefinition</code>.
* <p>
* The order of registering the Ant paths using the {@link #addSecureUrl(String,ConfigAttributeDefinition)} is
* very important. The system will identify the <b>first</b> matching path for a given HTTP URL. It will not proceed
* to evaluate later paths if a match has already been found. Accordingly, the most specific paths should be
* registered first, with the most general paths registered last.</p>
* <p>If no registered paths match the HTTP URL, <code>null</code> is returned.</p>
* registered first, with the most general paths registered last.
* <p>
* If no registered paths match the HTTP URL, <code>null</code> is returned.
*
* @author Ben Alex
* @version $Id$
@ -60,7 +58,7 @@ public class PathBasedFilterInvocationDefinitionMap extends AbstractFilterInvoca
antPath = antPath.toLowerCase();
}
getRequestMap().add(new EntryHolder(antPath, attr));
getRequestMap().put(antPath, attr);
if (logger.isDebugEnabled()) {
logger.debug("Added Ant path: " + antPath + "; attributes: " + attr);
@ -68,15 +66,7 @@ public class PathBasedFilterInvocationDefinitionMap extends AbstractFilterInvoca
}
public Iterator getConfigAttributeDefinitions() {
Set set = new HashSet();
Iterator iter = getRequestMap().iterator();
while (iter.hasNext()) {
EntryHolder entryHolder = (EntryHolder) iter.next();
set.add(entryHolder.getConfigAttributeDefinition());
}
return set.iterator();
return getRequestMap().values().iterator();
}
public ConfigAttributeDefinition lookupAttributes(String url) {
@ -95,47 +85,22 @@ public class PathBasedFilterInvocationDefinitionMap extends AbstractFilterInvoca
}
}
Iterator iter = requestMap.iterator();
Iterator paths = getRequestMap().keySet().iterator();
while (iter.hasNext()) {
EntryHolder entryHolder = (EntryHolder) iter.next();
while (paths.hasNext()) {
String path = (String) paths.next();
boolean matched = pathMatcher.match(entryHolder.getAntPath(), url);
boolean matched = pathMatcher.match(path, url);
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " + entryHolder.getAntPath() + "; matched="
+ matched);
logger.debug("Candidate is: '" + url + "'; pattern is " + path + "; matched=" + matched);
}
if (matched) {
return entryHolder.getConfigAttributeDefinition();
return (ConfigAttributeDefinition) getRequestMap().get(path);
}
}
return null;
}
//~ Inner Classes ==================================================================================================
protected class EntryHolder {
private ConfigAttributeDefinition configAttributeDefinition;
private String antPath;
public EntryHolder(String antPath, ConfigAttributeDefinition attr) {
this.antPath = antPath;
this.configAttributeDefinition = attr;
}
protected EntryHolder() {
throw new IllegalArgumentException("Cannot use default constructor");
}
public String getAntPath() {
return antPath;
}
public ConfigAttributeDefinition getConfigAttributeDefinition() {
return configAttributeDefinition;
}
}
}

View File

@ -20,25 +20,22 @@ import org.springframework.security.ConfigAttributeDefinition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* Maintains a <code>List</code> of <code>ConfigAttributeDefinition</code>s associated with different HTTP request
* URL regular expression patterns.<p>Regular expressions are used to match a HTTP request URL against a
* <code>ConfigAttributeDefinition</code>.</p>
* <p>The order of registering the regular expressions using the {@link #addSecureUrl(String,
* URL regular expression patterns.
* <p>
* Regular expressions are used to match a HTTP request URL against a <code>ConfigAttributeDefinition</code>.
* <p>
* The order of registering the regular expressions using the {@link #addSecureUrl(String,
* ConfigAttributeDefinition)} is very important. The system will identify the <b>first</b> matching regular
* expression for a given HTTP URL. It will not proceed to evaluate later regular expressions if a match has already
* been found. Accordingly, the most specific regular expressions should be registered first, with the most general
* regular expressions registered last.</p>
* <p>If no registered regular expressions match the HTTP URL, <code>null</code> is returned.</p>
* regular expressions registered last.
* <p>
* If no registered regular expressions match the HTTP URL, <code>null</code> is returned.
*/
public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvocationDefinitionSource
implements FilterInvocationDefinition {
@ -51,7 +48,7 @@ public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvo
public void addSecureUrl(String regExp, ConfigAttributeDefinition attr) {
Pattern pattern = Pattern.compile(regExp);
getRequestMap().add(new EntryHolder(pattern, attr));
getRequestMap().put(pattern, attr);
if (logger.isDebugEnabled()) {
logger.debug("Added regular expression: " + regExp + "; attributes: " + attr);
@ -59,20 +56,10 @@ public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvo
}
public Iterator getConfigAttributeDefinitions() {
Set set = new HashSet();
Iterator iter = getRequestMap().iterator();
while (iter.hasNext()) {
EntryHolder entryHolder = (EntryHolder) iter.next();
set.add(entryHolder.getConfigAttributeDefinition());
}
return set.iterator();
return getRequestMap().values().iterator();
}
public ConfigAttributeDefinition lookupAttributes(String url) {
Iterator iter = getRequestMap().iterator();
if (isConvertUrlToLowercaseBeforeComparison()) {
url = url.toLowerCase();
@ -81,47 +68,21 @@ public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvo
}
}
while (iter.hasNext()) {
EntryHolder entryHolder = (EntryHolder) iter.next();
Iterator patterns = getRequestMap().keySet().iterator();
Matcher matcher = entryHolder.getCompiledPattern().matcher(url);
boolean matched = matcher.matches();
while (patterns.hasNext()) {
Pattern p = (Pattern) patterns.next();
boolean matched = p.matcher(url).matches();
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " + entryHolder.getCompiledPattern()
+ "; matched=" + matched);
logger.debug("Candidate is: '" + url + "'; pattern is " + p.pattern() + "; matched=" + matched);
}
if (matched) {
return entryHolder.getConfigAttributeDefinition();
return (ConfigAttributeDefinition) getRequestMap().get(p);
}
}
return null;
}
//~ Inner Classes ==================================================================================================
protected class EntryHolder {
private ConfigAttributeDefinition configAttributeDefinition;
private Pattern compiledPattern;
public EntryHolder(Pattern compiledPattern, ConfigAttributeDefinition attr) {
this.compiledPattern = compiledPattern;
this.configAttributeDefinition = attr;
}
protected EntryHolder() {
throw new IllegalArgumentException("Cannot use default constructor");
}
public Pattern getCompiledPattern() {
return compiledPattern;
}
public ConfigAttributeDefinition getConfigAttributeDefinition() {
return configAttributeDefinition;
}
}
}

View File

@ -177,17 +177,6 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
assertEquals(2, map.getMapSize());
}
public void testNoArgConstructorDoesntExist() {
Class clazz = RegExpBasedFilterInvocationDefinitionMap.EntryHolder.class;
try {
clazz.getDeclaredConstructor((Class[]) null);
fail("Should have thrown NoSuchMethodException");
} catch (NoSuchMethodException expected) {
assertTrue(true);
}
}
public void testNullReturnsEmptyMap() {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText(null);

View File

@ -47,14 +47,6 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
//~ Methods ========================================================================================================
public static void main(String[] args) {
junit.textui.TestRunner.run(FilterInvocationDefinitionSourceEditorWithPathsTests.class);
}
public final void setUp() throws Exception {
super.setUp();
}
public void testAntPathDirectiveIsDetected() {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText(
@ -133,17 +125,6 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
assertEquals(2, map.getMapSize());
}
public void testNoArgConstructorDoesntExist() {
Class clazz = PathBasedFilterInvocationDefinitionMap.EntryHolder.class;
try {
clazz.getDeclaredConstructor((Class[]) null);
fail("Should have thrown NoSuchMethodException");
} catch (NoSuchMethodException expected) {
assertTrue(true);
}
}
public void testOrderOfEntriesIsPreservedOrderA() {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText(