SEC-1033: Refactored DefaultFilterInvocationDefinitionSource to remove legacy methods and make it immutable.

This commit is contained in:
Luke Taylor 2008-12-07 22:46:36 +00:00
parent bed00e10f5
commit fd3990c1f8
3 changed files with 87 additions and 97 deletions

View File

@ -57,12 +57,8 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
/** //private Map<Object, List<ConfigAttribute>> requestMap = new LinkedHashMap<Object, List<ConfigAttribute>>();
* Non method-specific map of URL patterns to <tt>List<ConfiAttribute></tt>s /** Stores request maps keyed by specific HTTP methods. A null key matches any method */
* TODO: Store in the httpMethod map with null key.
*/
private Map<Object, List<ConfigAttribute>> requestMap = new LinkedHashMap<Object, List<ConfigAttribute>>();
/** Stores request maps keyed by specific HTTP methods */
private Map<String, Map<Object, List<ConfigAttribute>>> httpMethodMap = private Map<String, Map<Object, List<ConfigAttribute>>> httpMethodMap =
new HashMap<String, Map<Object, List<ConfigAttribute>>>(); new HashMap<String, Map<Object, List<ConfigAttribute>>>();
@ -70,13 +66,7 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
private boolean stripQueryStringFromUrls; private boolean stripQueryStringFromUrls;
/** //~ Constructors ===================================================================================================
* Creates a FilterInvocationDefinitionSource with the supplied URL matching strategy.
* @param urlMatcher
*/
DefaultFilterInvocationDefinitionSource(UrlMatcher urlMatcher) {
this.urlMatcher = urlMatcher;
}
/** /**
* Builds the internal request map from the supplied map. The key elements should be of type {@link RequestKey}, * Builds the internal request map from the supplied map. The key elements should be of type {@link RequestKey},
@ -97,17 +87,13 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
void addSecureUrl(String pattern, List<ConfigAttribute> attr) {
addSecureUrl(pattern, null, attr);
}
/** /**
* Adds a URL,attribute-list pair to the request map, first allowing the <tt>UrlMatcher</tt> to * Adds a URL,attribute-list pair to the request map, first allowing the <tt>UrlMatcher</tt> to
* process the pattern if required, using its <tt>compile</tt> method. The returned object will be used as the key * process the pattern if required, using its <tt>compile</tt> method. The returned object will be used as the key
* to the request map and will be passed back to the <tt>UrlMatcher</tt> when iterating through the map to find * to the request map and will be passed back to the <tt>UrlMatcher</tt> when iterating through the map to find
* a match for a particular URL. * a match for a particular URL.
*/ */
void addSecureUrl(String pattern, String method, List<ConfigAttribute> attr) { private void addSecureUrl(String pattern, String method, List<ConfigAttribute> attr) {
Map<Object, List<ConfigAttribute>> mapToUse = getRequestMapForHttpMethod(method); Map<Object, List<ConfigAttribute>> mapToUse = getRequestMapForHttpMethod(method);
mapToUse.put(urlMatcher.compile(pattern), attr); mapToUse.put(urlMatcher.compile(pattern), attr);
@ -124,29 +110,28 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
* @return map of URL patterns to <tt>ConfigAttribute</tt>s for this method. * @return map of URL patterns to <tt>ConfigAttribute</tt>s for this method.
*/ */
private Map<Object, List<ConfigAttribute>> getRequestMapForHttpMethod(String method) { private Map<Object, List<ConfigAttribute>> getRequestMapForHttpMethod(String method) {
if (method == null) { if (method != null && !HTTP_METHODS.contains(method)) {
return requestMap;
}
if (!HTTP_METHODS.contains(method)) {
throw new IllegalArgumentException("Unrecognised HTTP method: '" + method + "'"); throw new IllegalArgumentException("Unrecognised HTTP method: '" + method + "'");
} }
Map<Object, List<ConfigAttribute>> methodRequestmap = httpMethodMap.get(method); Map<Object, List<ConfigAttribute>> methodRequestMap = httpMethodMap.get(method);
if (methodRequestmap == null) { if (methodRequestMap == null) {
methodRequestmap = new LinkedHashMap<Object, List<ConfigAttribute>>(); methodRequestMap = new LinkedHashMap<Object, List<ConfigAttribute>>();
httpMethodMap.put(method, methodRequestmap); httpMethodMap.put(method, methodRequestMap);
} }
return methodRequestmap; return methodRequestMap;
} }
public Collection<ConfigAttribute> getAllConfigAttributes() { public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>(); Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
for(List<ConfigAttribute> attrs : requestMap.values()) { for (Map.Entry<String, Map<Object, List<ConfigAttribute>>> entry : httpMethodMap.entrySet()) {
for (List<ConfigAttribute> attrs : entry.getValue().values()) {
allAttributes.addAll(attrs); allAttributes.addAll(attrs);
} }
}
return allAttributes; return allAttributes;
} }
@ -163,10 +148,6 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
return lookupAttributes(url, method); return lookupAttributes(url, method);
} }
protected List<? extends ConfigAttribute> lookupAttributes(String url) {
return lookupAttributes(url, null);
}
/** /**
* Performs the actual lookup of the relevant <tt>ConfigAttribute</tt>s for the given <code>FilterInvocation</code>. * Performs the actual lookup of the relevant <tt>ConfigAttribute</tt>s for the given <code>FilterInvocation</code>.
* <p> * <p>
@ -179,9 +160,9 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
* @param method the HTTP method (GET, POST, DELETE...). * @param method the HTTP method (GET, POST, DELETE...).
* *
* @return the <code>ConfigAttribute</code>s that apply to the specified <code>FilterInvocation</code> * @return the <code>ConfigAttribute</code>s that apply to the specified <code>FilterInvocation</code>
* or null if no match is foud * or null if no match is found
*/ */
public List<ConfigAttribute> lookupAttributes(String url, String method) { public final List<ConfigAttribute> lookupAttributes(String url, String method) {
if (stripQueryStringFromUrls) { if (stripQueryStringFromUrls) {
// Strip anything after a question mark symbol, as per SEC-161. See also SEC-321 // Strip anything after a question mark symbol, as per SEC-161. See also SEC-321
int firstQuestionMarkIndex = url.indexOf("?"); int firstQuestionMarkIndex = url.indexOf("?");
@ -199,23 +180,15 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
} }
} }
List<ConfigAttribute> attributes = null; // Obtain the map of request patterns to attributes for this method and lookup the url.
Map<Object, List<ConfigAttribute>> requestMap = httpMethodMap.get(method);
Map<Object, List<ConfigAttribute>> methodSpecificMap = httpMethodMap.get(method); // If no method-specific map, use the general one stored under the null key
if (requestMap == null) {
if (methodSpecificMap != null) { requestMap = httpMethodMap.get(null);
attributes = lookupUrlInMap(methodSpecificMap, url);
} }
if (attributes == null) { if (requestMap != null) {
attributes = lookupUrlInMap(requestMap, url);
}
return attributes;
}
private List<ConfigAttribute> lookupUrlInMap(Map<Object, List<ConfigAttribute>> requestMap, String url) {
for (Map.Entry<Object, List<ConfigAttribute>> entry : requestMap.entrySet()) { for (Map.Entry<Object, List<ConfigAttribute>> entry : requestMap.entrySet()) {
Object p = entry.getKey(); Object p = entry.getKey();
boolean matched = urlMatcher.pathMatchesUrl(entry.getKey(), url); boolean matched = urlMatcher.pathMatchesUrl(entry.getKey(), url);
@ -228,6 +201,7 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
return entry.getValue(); return entry.getValue();
} }
} }
}
return null; return null;
} }

View File

@ -1,6 +1,6 @@
package org.springframework.security.config; package org.springframework.security.config;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.*;
import java.util.List; import java.util.List;
@ -44,6 +44,7 @@ public class FilterInvocationDefinitionSourceParserTests {
"</filter-invocation-definition-source>"); "</filter-invocation-definition-source>");
DefaultFilterInvocationDefinitionSource fids = (DefaultFilterInvocationDefinitionSource) appContext.getBean("fids"); DefaultFilterInvocationDefinitionSource fids = (DefaultFilterInvocationDefinitionSource) appContext.getBean("fids");
List<? extends ConfigAttribute> cad = fids.getAttributes(createFilterInvocation("/anything", "GET")); List<? extends ConfigAttribute> cad = fids.getAttributes(createFilterInvocation("/anything", "GET"));
assertNotNull(cad);
assertTrue(cad.contains(new SecurityConfig("ROLE_A"))); assertTrue(cad.contains(new SecurityConfig("ROLE_A")));
} }

View File

@ -15,13 +15,11 @@
package org.springframework.security.intercept.web; package org.springframework.security.intercept.web;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
@ -37,29 +35,41 @@ import org.springframework.security.util.AntUrlPathMatcher;
* @author Ben Alex * @author Ben Alex
* @version $Id$ * @version $Id$
*/ */
@SuppressWarnings("unchecked")
public class DefaultFilterInvocationDefinitionSourceTests { public class DefaultFilterInvocationDefinitionSourceTests {
private DefaultFilterInvocationDefinitionSource map; private DefaultFilterInvocationDefinitionSource fids;
private List<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE"); private List<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE");
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
@Before private void createFids(String url, String method) {
public void createMap() { LinkedHashMap requestMap = new LinkedHashMap();
map = new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher()); requestMap.put(new RequestKey(url, method), def);
map.setStripQueryStringFromUrls(true); fids = new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(), requestMap);
fids.setStripQueryStringFromUrls(true);
}
private void createFids(String url, boolean convertToLowerCase) {
LinkedHashMap requestMap = new LinkedHashMap();
requestMap.put(new RequestKey(url), def);
fids = new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(convertToLowerCase), requestMap);
fids.setStripQueryStringFromUrls(true);
} }
@Test @Test
public void convertUrlToLowercaseIsTrueByDefault() { public void convertUrlToLowercaseIsTrueByDefault() {
assertTrue(map.isConvertUrlToLowercaseBeforeComparison()); LinkedHashMap requestMap = new LinkedHashMap();
requestMap.put(new RequestKey("/something"), def);
fids = new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(), requestMap);
assertTrue(fids.isConvertUrlToLowercaseBeforeComparison());
} }
@Test @Test
public void lookupNotRequiringExactMatchSuccessIfNotMatching() { public void lookupNotRequiringExactMatchSucceedsIfNotMatching() {
map.addSecureUrl("/secure/super/**", def); createFids("/secure/super/**", null);
FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null); FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null);
assertEquals(def, map.lookupAttributes(fi.getRequestUrl())); assertEquals(def, fids.lookupAttributes(fi.getRequestUrl(), null));
} }
/** /**
@ -67,81 +77,86 @@ public class DefaultFilterInvocationDefinitionSourceTests {
*/ */
@Test @Test
public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() { public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
map.addSecureUrl("/SeCuRE/super/**", def); createFids("/SeCuRE/super/**", null);
FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null); FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null);
List<? extends ConfigAttribute> response = map.lookupAttributes(fi.getRequestUrl()); List<? extends ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
assertEquals(def, response); assertEquals(def, response);
} }
@Test @Test
public void lookupRequiringExactMatchFailsIfNotMatching() { public void lookupRequiringExactMatchFailsIfNotMatching() {
map = new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(false)); createFids("/secure/super/**", false);
map.addSecureUrl("/secure/super/**", def);
FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null); FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null);
List<? extends ConfigAttribute> response = map.lookupAttributes(fi.getRequestUrl()); List<? extends ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
assertEquals(null, response); assertEquals(null, response);
} }
@Test @Test
public void lookupRequiringExactMatchIsSuccessful() { public void lookupRequiringExactMatchIsSuccessful() {
map = new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(false)); createFids("/SeCurE/super/**", false);
map.addSecureUrl("/SeCurE/super/**", def);
FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null); FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null);
List<? extends ConfigAttribute> response = map.lookupAttributes(fi.getRequestUrl()); List<? extends ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
assertEquals(def, response); assertEquals(def, response);
} }
@Test @Test
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() { public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
map.addSecureUrl("/someAdminPage.html**", def); createFids("/someAdminPage.html**", null);
FilterInvocation fi = createFilterInvocation("/someAdminPage.html?a=/test", null); FilterInvocation fi = createFilterInvocation("/someAdminPage.html?a=/test", null);
List<? extends ConfigAttribute> response = map.lookupAttributes(fi.getRequestUrl()); List<? extends ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
assertEquals(def, response); // see SEC-161 (it should truncate after ? sign) assertEquals(def, response); // see SEC-161 (it should truncate after ? sign)
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void unknownHttpMethodIsRejected() { public void unknownHttpMethodIsRejected() {
map.addSecureUrl("/someAdminPage.html**", "UNKNOWN", def); createFids("/someAdminPage.html**", "UNKNOWN");
} }
@Test @Test
public void httpMethodLookupSucceeds() { public void httpMethodLookupSucceeds() {
map.addSecureUrl("/somepage**", "GET", def); createFids("/somepage**", "GET");
FilterInvocation fi = createFilterInvocation("/somepage", "GET"); FilterInvocation fi = createFilterInvocation("/somepage", "GET");
List<? extends ConfigAttribute> attrs = map.getAttributes(fi); List<? extends ConfigAttribute> attrs = fids.getAttributes(fi);
assertEquals(def, attrs);
}
@Test
public void generalMatchIsUsedIfNoMethodSpecificMatchExists() {
createFids("/somepage**", null);
FilterInvocation fi = createFilterInvocation("/somepage", "GET");
List<? extends ConfigAttribute> attrs = fids.getAttributes(fi);
assertEquals(def, attrs); assertEquals(def, attrs);
} }
@Test @Test
public void requestWithDifferentHttpMethodDoesntMatch() { public void requestWithDifferentHttpMethodDoesntMatch() {
map.addSecureUrl("/somepage**", "GET", def); createFids("/somepage**", "GET");
FilterInvocation fi = createFilterInvocation("/somepage", null); FilterInvocation fi = createFilterInvocation("/somepage", null);
List<? extends ConfigAttribute> attrs = map.getAttributes(fi); List<? extends ConfigAttribute> attrs = fids.getAttributes(fi);
assertNull(attrs); assertNull(attrs);
} }
@Test @Test
public void httpMethodSpecificUrlTakesPrecedence() { public void httpMethodSpecificUrlTakesPrecedence() {
// Even though this is added before the method-specific def, the latter should match LinkedHashMap<RequestKey, List<ConfigAttribute>> requestMap = new LinkedHashMap<RequestKey, List<ConfigAttribute>>();
List<ConfigAttribute> allMethodDef = def; // Even though this is added before the Http method-specific def, the latter should match
map.addSecureUrl("/**", null, allMethodDef); requestMap.put(new RequestKey("/**"), def);
List<ConfigAttribute> postOnlyDef = SecurityConfig.createList("ROLE_TWO"); List<ConfigAttribute> postOnlyDef = SecurityConfig.createList("ROLE_TWO");
map.addSecureUrl("/somepage**", "POST", postOnlyDef); requestMap.put(new RequestKey("/somepage**", "POST"), postOnlyDef);
fids = new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(), requestMap);
FilterInvocation fi = createFilterInvocation("/somepage", "POST"); List<ConfigAttribute> attrs = fids.getAttributes(createFilterInvocation("/somepage", "POST"));
List<ConfigAttribute> attrs = map.getAttributes(fi);
assertEquals(postOnlyDef, attrs); assertEquals(postOnlyDef, attrs);
} }
@ -150,16 +165,16 @@ public class DefaultFilterInvocationDefinitionSourceTests {
*/ */
@Test @Test
public void extraQuestionMarkStillMatches() { public void extraQuestionMarkStillMatches() {
map.addSecureUrl("/someAdminPage.html*", def); createFids("/someAdminPage.html*", null);
FilterInvocation fi = createFilterInvocation("/someAdminPage.html?x=2/aa?y=3", null); FilterInvocation fi = createFilterInvocation("/someAdminPage.html?x=2/aa?y=3", null);
List<? extends ConfigAttribute> response = map.lookupAttributes(fi.getRequestUrl()); List<? extends ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
assertEquals(def, response); assertEquals(def, response);
fi = createFilterInvocation("/someAdminPage.html??", null); fi = createFilterInvocation("/someAdminPage.html??", null);
response = map.lookupAttributes(fi.getRequestUrl()); response = fids.lookupAttributes(fi.getRequestUrl(), null);
assertEquals(def, response); assertEquals(def, response);
} }