SEC-531: Provide support for HTTP methods in FilterInvocationDefinitionSource. Path/Regex versions of FIDS are now deprecated and in favour of using their (no longer abstract) parent class with a UrlPathMatcher strategy.

This commit is contained in:
Luke Taylor 2008-01-24 14:39:47 +00:00
parent 342677fabc
commit d10450cfb7
16 changed files with 519 additions and 303 deletions

View File

@ -18,11 +18,8 @@ import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.ConfigAttributeEditor;
import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
import org.springframework.security.intercept.web.AbstractFilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterInvocationDefinitionMap;
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.intercept.web.PathBasedFilterInvocationDefinitionMap;
import org.springframework.security.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
import org.springframework.security.securechannel.ChannelDecisionManagerImpl;
import org.springframework.security.securechannel.ChannelProcessingFilter;
import org.springframework.security.securechannel.InsecureChannelProcessor;
@ -33,6 +30,7 @@ import org.springframework.security.ui.ExceptionTranslationFilter;
import org.springframework.security.util.FilterChainProxy;
import org.springframework.security.util.RegexUrlPathMatcher;
import org.springframework.security.util.AntUrlPathMatcher;
import org.springframework.security.util.UrlMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@ -65,6 +63,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
static final String OPT_REQUIRES_HTTPS = "https";
static final String OPT_ANY_CHANNEL = "any";
static final String ATT_HTTP_METHOD = "method";
static final String ATT_CREATE_SESSION = "create-session";
static final String DEF_CREATE_SESSION_IF_REQUIRED = "ifRequired";
static final String OPT_CREATE_SESSION_ALWAYS = "always";
@ -118,8 +118,13 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
patternType = DEF_PATH_TYPE_ANT;
}
FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
FilterInvocationDefinitionMap channelFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
boolean useRegex = patternType.equals(OPT_PATH_TYPE_REGEX);
UrlMatcher matcher = new AntUrlPathMatcher();
if (useRegex) {
matcher = new RegexUrlPathMatcher();
}
// Deal with lowercase conversion requests
String lowercaseComparisons = element.getAttribute(ATT_LOWERCASE_COMPARISONS);
@ -127,33 +132,27 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
lowercaseComparisons = null;
}
// Only change from the defaults if the attribute has been set
if ("true".equals(lowercaseComparisons)) {
interceptorFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(true);
channelFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(true);
} else if ("false".equals(lowercaseComparisons)) {
interceptorFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(false);
channelFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(false);
}
if (patternType.equals(OPT_PATH_TYPE_REGEX)) {
RegexUrlPathMatcher matcher = new RegexUrlPathMatcher();
if (lowercaseComparisons != null) {
matcher.setRequiresLowerCaseUrl("true".equals(lowercaseComparisons));
if (useRegex) {
((RegexUrlPathMatcher)matcher).setRequiresLowerCaseUrl(true);
}
filterChainProxy.getPropertyValues().addPropertyValue("matcher", matcher);
interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
channelFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
} else if (lowercaseComparisons != null) {
AntUrlPathMatcher matcher = new AntUrlPathMatcher();
matcher.setRequiresLowerCaseUrl("true".equals(lowercaseComparisons));
filterChainProxy.getPropertyValues().addPropertyValue("matcher", matcher);
// Default for ant is already to force lower case
} else if ("false".equals(lowercaseComparisons)) {
if (!useRegex) {
((AntUrlPathMatcher)matcher).setRequiresLowerCaseUrl(false);
}
// Default for regex is no change
}
DefaultFilterInvocationDefinitionSource interceptorFilterInvDefSource =
new DefaultFilterInvocationDefinitionSource(matcher);
DefaultFilterInvocationDefinitionSource channelFilterInvDefSource =
new DefaultFilterInvocationDefinitionSource(matcher);
filterChainProxy.getPropertyValues().addPropertyValue("matcher", matcher);
// Add servlet-api integration filter if required
String provideServletApi = element.getAttribute(ATT_SERVLET_API_PROVISION);
if (!StringUtils.hasText(provideServletApi)) {
@ -181,11 +180,16 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
filterSecurityInterceptorBuilder.addPropertyValue("authenticationManager",
ConfigUtils.registerProviderManagerIfNecessary(parserContext));
// SEC-501 - should paths stored in request maps be converted to lower case
// true if Ant path and using lower case
boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
filterChainMap, interceptorFilterInvDefSource, channelFilterInvDefSource, parserContext);
filterChainMap, interceptorFilterInvDefSource, channelFilterInvDefSource,
convertPathsToLowerCase, parserContext);
// Check if we need to register the channel processing beans
if (((AbstractFilterInvocationDefinitionSource)channelFilterInvDefSource).getMapSize() > 0) {
if (((DefaultFilterInvocationDefinitionSource)channelFilterInvDefSource).getMapSize() > 0) {
// At least one channel requirement has been specified
RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class);
channelFilter.getPropertyValues().addPropertyValue("channelDecisionManager",
@ -268,8 +272,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
* FilterInvocationDefinitionSource used in FilterSecurityInterceptor.
*/
private void parseInterceptUrls(List urlElts, Map filterChainMap,
FilterInvocationDefinitionMap interceptorFilterInvDefSource,
FilterInvocationDefinitionMap channelFilterInvDefSource, ParserContext parserContext) {
DefaultFilterInvocationDefinitionSource interceptorFilterInvDefSource,
DefaultFilterInvocationDefinitionSource channelFilterInvDefSource,
boolean useLowerCasePaths, ParserContext parserContext) {
Iterator urlEltsIterator = urlElts.iterator();
@ -279,6 +284,14 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
Element urlElt = (Element) urlEltsIterator.next();
String path = urlElt.getAttribute(ATT_PATH_PATTERN);
if (useLowerCasePaths) {
path = path.toLowerCase();
}
String method = urlElt.getAttribute(ATT_HTTP_METHOD);
if (!StringUtils.hasText(method)) {
method = null;
}
Assert.hasText(path, "path attribute cannot be empty or null");
@ -287,7 +300,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
// Convert the comma-separated list of access attributes to a ConfigAttributeDefinition
if (StringUtils.hasText(access)) {
editor.setAsText(access);
interceptorFilterInvDefSource.addSecureUrl(path, (ConfigAttributeDefinition) editor.getValue());
interceptorFilterInvDefSource.addSecureUrl(path, method, (ConfigAttributeDefinition) editor.getValue());
}
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);

View File

@ -1,152 +0,0 @@
/* Copyright 2004, 2005, 2006 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 org.springframework.security.intercept.web;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.util.UrlMatcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Iterator;
/**
* Abstract implementation of <Code>FilterInvocationDefinitionSource</code>.
* <p>
* Stores an ordered map of compiled URL paths to <tt>ConfigAttributeDefinition</tt>s and provides URL matching
* against the items stored in this map using the confgured <tt>UrlMatcher</tt>.
* <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.
*
* @author Ben Alex
* @author Luke Taylor
* @version $Id$
*/
public abstract class AbstractFilterInvocationDefinitionSource implements FilterInvocationDefinitionSource {
protected final Log logger = LogFactory.getLog(getClass());
private Map requestMap = new LinkedHashMap();
private UrlMatcher urlMatcher;
protected AbstractFilterInvocationDefinitionSource(UrlMatcher urlMatcher) {
this.urlMatcher = urlMatcher;
}
//~ Methods ========================================================================================================
/**
* Adds a URL-ConfigAttributeDefinition 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
* 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.
*/
public void addSecureUrl(String pattern, ConfigAttributeDefinition attr) {
requestMap.put(urlMatcher.compile(pattern), attr);
if (logger.isDebugEnabled()) {
logger.debug("Added URL pattern: " + pattern + "; attributes: " + attr);
}
}
public Iterator getConfigAttributeDefinitions() {
return getRequestMap().values().iterator();
}
public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException {
if ((object == null) || !this.supports(object.getClass())) {
throw new IllegalArgumentException("Object must be a FilterInvocation");
}
String url = ((FilterInvocation) object).getRequestUrl();
return lookupAttributes(url);
}
/**
* Performs the actual lookup of the relevant <code>ConfigAttributeDefinition</code> for the specified
* <code>FilterInvocation</code>.
* <p>
* By default, iterates through the stored URL map and calls the
* {@link UrlMatcher#pathMatchesUrl(Object path, String url)} method until a match is found.
* <p>
* Subclasses can override if required to perform any modifications to the URL.
* <p>
* Public visiblity so that tablibs or other view helper classes can access the
* <code>ConfigAttributeDefinition</code> applying to a given URI pattern without needing to construct a mock
* <code>FilterInvocation</code> and retrieving the attibutes via the {@link #getAttributes(Object)} method.
*
* @param url the URI to retrieve configuration attributes for
*
* @return the <code>ConfigAttributeDefinition</code> that applies to the specified <code>FilterInvocation</code>
* or null if no match is foud
*/
public ConfigAttributeDefinition lookupAttributes(String url) {
if (urlMatcher.requiresLowerCaseUrl()) {
url = url.toLowerCase();
if (logger.isDebugEnabled()) {
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
}
}
Iterator entries = requestMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
Object p = entry.getKey();
boolean matched = urlMatcher.pathMatchesUrl(p, url);
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " + p + "; matched=" + matched);
}
if (matched) {
return (ConfigAttributeDefinition) entry.getValue();
}
}
return null;
}
public boolean supports(Class clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public int getMapSize() {
return this.requestMap.size();
}
Map getRequestMap() {
return requestMap;
}
protected UrlMatcher getUrlMatcher() {
return urlMatcher;
}
public boolean isConvertUrlToLowercaseBeforeComparison() {
return urlMatcher.requiresLowerCaseUrl();
}
}

View File

@ -0,0 +1,263 @@
/* Copyright 2004, 2005, 2006 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 org.springframework.security.intercept.web;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.SecurityConfig;
import org.springframework.security.util.UrlMatcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.List;
/**
* Default implementation of <tt>FilterInvocationDefinitionSource</tt>.
* <p>
* Stores an ordered map of compiled URL paths to <tt>ConfigAttributeDefinition</tt>s and provides URL matching
* against the items stored in this map using the configured <tt>UrlMatcher</tt>.
* <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>
* If URLs are registered for a particular HTTP method using
* {@link #addSecureUrl(String, String, ConfigAttributeDefinition)}, then the method-specific matches will take
* precedence over any URLs which are registered without an HTTP method.
*
* @author Ben Alex
* @author Luke Taylor
* @version $Id$
*/
public class DefaultFilterInvocationDefinitionSource implements FilterInvocationDefinitionSource {
private static final Set HTTP_METHODS = new HashSet(Arrays.asList(new String[]{ "GET", "PUT", "DELETE", "POST" }));
protected final Log logger = LogFactory.getLog(getClass());
/**
* Non method-specific map of URL patterns to <tt>ConfigAttributeDefinition</tt>s
* TODO: Store in the httpMethod map with null key.
*/
private Map requestMap = new LinkedHashMap();
/** Stores request maps keyed by specific HTTP methods */
private Map httpMethodMap = new HashMap();
private UrlMatcher urlMatcher;
private boolean stripQueryStringFromUrls;
/**
* Creates a FilterInvocationDefinitionSource with the supplied URL matching strategy.
* @param urlMatcher
*/
public DefaultFilterInvocationDefinitionSource(UrlMatcher urlMatcher) {
this.urlMatcher = urlMatcher;
}
//~ Methods ========================================================================================================
public void addSecureUrl(String pattern, ConfigAttributeDefinition attr) {
addSecureUrl(pattern, null, attr);
}
/**
* Adds a URL-ConfigAttributeDefinition 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
* 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.
*/
public void addSecureUrl(String pattern, String method, ConfigAttributeDefinition attr) {
Map mapToUse = getRequestMapForHttpMethod(method);
mapToUse.put(urlMatcher.compile(pattern), attr);
if (logger.isDebugEnabled()) {
logger.debug("Added URL pattern: " + pattern + "; attributes: " + attr +
(method == null ? "" : " for HTTP method '" + method + "'"));
}
}
/**
* Return the HTTP method specific request map, creating it if it doesn't already exist.
* @param method GET, POST etc
* @return map of URL patterns to <tt>ConfigAttributeDefinition</tt>s for this method.
*/
private Map getRequestMapForHttpMethod(String method) {
if (method == null) {
return requestMap;
}
if (!HTTP_METHODS.contains(method)) {
throw new IllegalArgumentException("Unrecognised HTTP method: '" + method + "'");
}
Map methodRequestmap = (Map) httpMethodMap.get(method);
if (methodRequestmap == null) {
methodRequestmap = new LinkedHashMap();
httpMethodMap.put(method, methodRequestmap);
}
return methodRequestmap;
}
public Iterator getConfigAttributeDefinitions() {
return getRequestMap().values().iterator();
}
public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException {
if ((object == null) || !this.supports(object.getClass())) {
throw new IllegalArgumentException("Object must be a FilterInvocation");
}
String url = ((FilterInvocation) object).getRequestUrl();
String method = ((FilterInvocation) object).getHttpRequest().getMethod();
return lookupAttributes(url, method);
}
protected ConfigAttributeDefinition lookupAttributes(String url) {
return lookupAttributes(url, null);
}
/**
* Performs the actual lookup of the relevant <code>ConfigAttributeDefinition</code> for the specified
* <code>FilterInvocation</code>.
* <p>
* By default, iterates through the stored URL map and calls the
* {@link UrlMatcher#pathMatchesUrl(Object path, String url)} method until a match is found.
* <p>
* Subclasses can override if required to perform any modifications to the URL.
*
* @param url the URI to retrieve configuration attributes for
* @param method the HTTP method (GET, POST, DELETE...).
*
* @return the <code>ConfigAttributeDefinition</code> that applies to the specified <code>FilterInvocation</code>
* or null if no match is foud
*/
protected ConfigAttributeDefinition lookupAttributes(String url, String method) {
if (stripQueryStringFromUrls) {
// Strip anything after a question mark symbol, as per SEC-161. See also SEC-321
int firstQuestionMarkIndex = url.indexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
}
if (urlMatcher.requiresLowerCaseUrl()) {
url = url.toLowerCase();
if (logger.isDebugEnabled()) {
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
}
}
ConfigAttributeDefinition attributes = null;
Map methodSpecificMap = (Map) httpMethodMap.get(method);
if (methodSpecificMap != null) {
attributes = lookupUrlInMap(methodSpecificMap, url);
}
if (attributes == null) {
attributes = lookupUrlInMap(requestMap, url);
}
return attributes;
}
private ConfigAttributeDefinition lookupUrlInMap(Map requestMap, String url) {
Iterator entries = requestMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
Object p = entry.getKey();
boolean matched = urlMatcher.pathMatchesUrl(p, url);
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " + p + "; matched=" + matched);
}
if (matched) {
return (ConfigAttributeDefinition) entry.getValue();
}
}
return null;
}
/**
* Allows or easier configuration using {@link FilterInvocationDefinitionSourceMapping}.
*
* @param mappings
* {@link java.util.List} of
* {@link FilterInvocationDefinitionSourceMapping} objects.
*/
void setMappings(List mappings) {
Iterator it = mappings.iterator();
while (it.hasNext()) {
FilterInvocationDefinitionSourceMapping mapping = (FilterInvocationDefinitionSourceMapping) it.next();
ConfigAttributeDefinition configDefinition = new ConfigAttributeDefinition();
Iterator configAttributesIt = mapping.getConfigAttributes().iterator();
while (configAttributesIt.hasNext()) {
String s = (String) configAttributesIt.next();
configDefinition.addConfigAttribute(new SecurityConfig(s));
}
addSecureUrl(mapping.getUrl(), configDefinition);
}
}
public boolean supports(Class clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public int getMapSize() {
return this.requestMap.size();
}
Map getRequestMap() {
return requestMap;
}
protected UrlMatcher getUrlMatcher() {
return urlMatcher;
}
public boolean isConvertUrlToLowercaseBeforeComparison() {
return urlMatcher.requiresLowerCaseUrl();
}
protected void setStripQueryStringFromUrls(boolean stripQueryStringFromUrls) {
this.stripQueryStringFromUrls = stripQueryStringFromUrls;
}
}

View File

@ -29,13 +29,11 @@ public class FIDSToFilterChainMapConverter {
// 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,
Assert.isTrue(source instanceof DefaultFilterInvocationDefinitionSource,
"Can't handle FilterInvocationDefinitionSource type " + source.getClass());
AbstractFilterInvocationDefinitionSource fids = (AbstractFilterInvocationDefinitionSource)source;
DefaultFilterInvocationDefinitionSource fids = (DefaultFilterInvocationDefinitionSource)source;
Map requestMap = fids.getRequestMap();
Iterator paths = requestMap.keySet().iterator();

View File

@ -23,20 +23,31 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.security.util.StringSplitUtils;
import org.springframework.security.util.RegexUrlPathMatcher;
import org.springframework.security.util.UrlMatcher;
import org.springframework.security.util.AntUrlPathMatcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
/**
* Property editor to assist with the setup of a {@link FilterInvocationDefinitionSource}.<p>The class creates and
* populates a {@link RegExpBasedFilterInvocationDefinitionMap} or {@link PathBasedFilterInvocationDefinitionMap}
* (depending on the type of patterns presented).</p>
* <p>By default the class treats presented patterns as regular expressions. If the keyword
* Property editor to assist with the setup of a {@link FilterInvocationDefinitionSource}.
* <p>
* Note that from version 2.0, the use of property-editor based configuration is deprecated in favour of namespace
* configuration options.
* <p>
* The class creates and populates a
* {@link org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource}
* using either an Ant or Regular Expression URL matching strategy depending on the type of patterns presented.
* <p>
* By default the class treats presented patterns as regular expressions. If the keyword
* <code>PATTERN_TYPE_APACHE_ANT</code> is present (case sensitive), patterns will be treated as Apache Ant paths
* rather than regular expressions.</p>
* rather than regular expressions.
*
* @author Ben Alex
* @deprecated Use namespace configuration instead. May be removed in future versions.
* @version $Id$
*/
public class FilterInvocationDefinitionSourceEditor extends PropertyEditorSupport {
@ -50,38 +61,50 @@ public class FilterInvocationDefinitionSourceEditor extends PropertyEditorSuppor
//~ Methods ========================================================================================================
public void setAsText(String s) throws IllegalArgumentException {
FilterInvocationDefinitionDecorator source = new FilterInvocationDefinitionDecorator();
//FilterInvocationDefinitionDecorator source = new FilterInvocationDefinitionDecorator();
if ((s == null) || "".equals(s)) {
// Leave target object empty
setValue(new RegExpBasedFilterInvocationDefinitionMap());
setValue(new DefaultFilterInvocationDefinitionSource(new RegexUrlPathMatcher()));
return;
}
// Check if we need to override the default definition map
if (s.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) {
source.setDecorated(new PathBasedFilterInvocationDefinitionMap());
boolean useAnt = s.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1;
boolean converUrlToLowerCase = s.lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1;
if (logger.isDebugEnabled()) {
if (logger.isDebugEnabled()) {
if (useAnt) {
logger.debug(("Detected " + DIRECTIVE_PATTERN_TYPE_APACHE_ANT
+ " directive; using Apache Ant style path expressions"));
}
} else {
source.setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
}
if (s.lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1) {
if (logger.isDebugEnabled()) {
logger.debug("Detected " + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+ " directive; Instructing mapper to convert URLs to lowercase before comparison");
if (converUrlToLowerCase) {
if (logger.isDebugEnabled()) {
logger.debug("Detected " + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+ " directive; Instructing mapper to convert URLs to lowercase before comparison");
}
}
source.setConvertUrlToLowercaseBeforeComparison(true);
} else {
source.setConvertUrlToLowercaseBeforeComparison(false);
}
UrlMatcher matcher;
if (useAnt) {
matcher = new AntUrlPathMatcher();
((AntUrlPathMatcher)matcher).setRequiresLowerCaseUrl(converUrlToLowerCase);
} else {
matcher = new RegexUrlPathMatcher();
((RegexUrlPathMatcher)matcher).setRequiresLowerCaseUrl(converUrlToLowerCase);
}
DefaultFilterInvocationDefinitionSource fids = new DefaultFilterInvocationDefinitionSource(matcher);
if (useAnt) {
fids.setStripQueryStringFromUrls(true);
}
BufferedReader br = new BufferedReader(new StringReader(s));
int counter = 0;
String line;
@ -147,8 +170,7 @@ public class FilterInvocationDefinitionSourceEditor extends PropertyEditorSuppor
}
// Attempt to detect malformed lines (as per SEC-204)
if (source.isConvertUrlToLowercaseBeforeComparison()
&& source.getDecorated() instanceof PathBasedFilterInvocationDefinitionMap) {
if (converUrlToLowerCase && useAnt) {
// Should all be lowercase; check each character
// We only do this for Ant (regexp have control chars)
for (int i = 0; i < name.length(); i++) {
@ -174,9 +196,8 @@ public class FilterInvocationDefinitionSourceEditor extends PropertyEditorSuppor
mappings.add(mapping);
}
source.setMappings(mappings);
fids.setMappings(mappings);
setValue(source.getDecorated());
setValue(fids);
}
}

View File

@ -18,11 +18,8 @@ package org.springframework.security.intercept.web;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.util.AntUrlPathMatcher;
import java.util.Iterator;
/**
* Extends AbstractFilterInvocationDefinitionSource, configuring it with a {@link AntUrlPathMatcher} to match URLs
* Extends DefaultFilterInvocationDefinitionSource, configuring it with a {@link AntUrlPathMatcher} to match URLs
* using Apache Ant path-based patterns.
* <p>
* Apache Ant path expressions are used to match a HTTP request URL against a <code>ConfigAttributeDefinition</code>.
@ -39,42 +36,29 @@ import java.util.Iterator;
*
* @author Ben Alex
* @author Luke taylor
* @deprecated DefaultFilterInvocationDefinitionSource should now be used with an AntUrlPathMatcher instead.
* @version $Id$
*/
public class PathBasedFilterInvocationDefinitionMap extends AbstractFilterInvocationDefinitionSource
public class PathBasedFilterInvocationDefinitionMap extends DefaultFilterInvocationDefinitionSource
implements FilterInvocationDefinition {
//~ Constructors ===================================================================================================
public PathBasedFilterInvocationDefinitionMap() {
super(new AntUrlPathMatcher());
setStripQueryStringFromUrls(true);
}
//~ Methods ========================================================================================================
public void addSecureUrl(String antPath, ConfigAttributeDefinition attr) {
public void addSecureUrl(String antPath, String method, ConfigAttributeDefinition attr) {
// SEC-501: If using lower case comparison, we should convert the paths to lower case
// as any upper case characters included by mistake will prevent the URL from ever being matched.
if (getUrlMatcher().requiresLowerCaseUrl()) {
antPath = antPath.toLowerCase();
}
super.addSecureUrl(antPath, attr);
}
public Iterator getConfigAttributeDefinitions() {
return getRequestMap().values().iterator();
}
public ConfigAttributeDefinition lookupAttributes(String url) {
// Strip anything after a question mark symbol, as per SEC-161. See also SEC-321
int firstQuestionMarkIndex = url.indexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
return super.lookupAttributes(url);
super.addSecureUrl(antPath, method, attr);
}
public void setConvertUrlToLowercaseBeforeComparison(boolean bool) {

View File

@ -16,18 +16,18 @@
package org.springframework.security.intercept.web;
import org.springframework.security.util.RegexUrlPathMatcher;
import org.springframework.security.util.AntUrlPathMatcher;
/**
* Configures an {@link AbstractFilterInvocationDefinitionSource} with a regular expression URL matching strategy
* Configures an {@link DefaultFilterInvocationDefinitionSource} with a regular expression URL matching strategy
* {@link RegexUrlPathMatcher}.
*
* @author Ben Alex
* @author Luke Taylor
* @deprecated
* @version $Id$
*/
public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvocationDefinitionSource
public class RegExpBasedFilterInvocationDefinitionMap extends DefaultFilterInvocationDefinitionSource
implements FilterInvocationDefinition {
//~ Constructors ===================================================================================================

View File

@ -48,7 +48,7 @@ public final class FilterInvocationUtils {
/**
* Creates a <code>FilterInvocation</code> for the specified <code>contextPath</code> and <code>Uri</code>.
* Note the normal subclasses of <code>AbstractFilterInvocationDefinitionSource</code> disregard the
* Note the normal subclasses of <code>DefaultFilterInvocationDefinitionSource</code> disregard the
* <code>contextPath</code> when evaluating which secure object metadata applies to a given
* <code>FilterInvocation</code>, so generally the <code>contextPath</code> is unimportant unless you are using a
* custom <code>FilterInvocationDefinitionSource</code>.

View File

@ -132,6 +132,10 @@ intercept-url.attlist &=
intercept-url.attlist &=
## The access configuration attributes that apply for the configured path.
attribute access {xsd:string}?
intercept-url.attlist &=
## The HTTP Method for which the access configuration attributes should apply. If not specified, the attributes will apply to any method.
attribute method {"GET" | "PUT" | "POST" | "DELETE"}?
intercept-url.attlist &=
## The filter list for the path. Currently can be set to "none" to remove a path from having any filters applied. The full filter stack (consisting of all defined filters, will be applied to any other paths).
attribute filters {"none"}?

View File

@ -355,6 +355,19 @@
<xs:documentation>The access configuration attributes that apply for the configured path.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="method">
<xs:annotation>
<xs:documentation>The HTTP Method for which the access configuration attributes should apply. If not specified, the attributes will apply to any method.</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="GET"/>
<xs:enumeration value="PUT"/>
<xs:enumeration value="POST"/>
<xs:enumeration value="DELETE"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="filters">
<xs:annotation>
<xs:documentation>The filter list for the path. Currently can be set to "none" to remove a path from having any filters applied. The full filter stack (consisting of all defined filters, will be applied to any other paths).</xs:documentation>

View File

@ -132,15 +132,32 @@ public class HttpSecurityBeanDefinitionParserTests {
FilterSecurityInterceptor fis = (FilterSecurityInterceptor) appContext.getBean(BeanIds.FILTER_SECURITY_INTERCEPTOR);
FilterInvocationDefinitionSource fids = fis.getObjectDefinitionSource();
ConfigAttributeDefinition attrs = fids.getAttributes(createFilterinvocation("/Secure"));
ConfigAttributeDefinition attrs = fids.getAttributes(createFilterinvocation("/Secure", null));
assertEquals(2, attrs.size());
assertTrue(attrs.contains(new SecurityConfig("ROLE_A")));
assertTrue(attrs.contains(new SecurityConfig("ROLE_B")));
attrs = fids.getAttributes(createFilterinvocation("/secure"));
attrs = fids.getAttributes(createFilterinvocation("/secure", null));
assertEquals(1, attrs.size());
assertTrue(attrs.contains(new SecurityConfig("ROLE_C")));
}
@Test
public void httpMethodMatchIsSupported() throws Exception {
setContext(
" <http auto-config='true'>" +
" <intercept-url pattern='/**' access='ROLE_C' />" +
" <intercept-url pattern='/secure*' method='DELETE' access='ROLE_SUPERVISOR' />" +
" <intercept-url pattern='/secure*' method='POST' access='ROLE_A,ROLE_B' />" +
" </http>" + AUTH_PROVIDER_XML);
FilterSecurityInterceptor fis = (FilterSecurityInterceptor) appContext.getBean(BeanIds.FILTER_SECURITY_INTERCEPTOR);
FilterInvocationDefinitionSource fids = fis.getObjectDefinitionSource();
ConfigAttributeDefinition attrs = fids.getAttributes(createFilterinvocation("/secure", "POST"));
assertEquals(2, attrs.size());
assertTrue(attrs.contains(new SecurityConfig("ROLE_A")));
assertTrue(attrs.contains(new SecurityConfig("ROLE_B")));
}
@Test
public void minimalConfigurationParses() {
setContext("<http><http-basic /></http>" + AUTH_PROVIDER_XML);
@ -213,8 +230,9 @@ public class HttpSecurityBeanDefinitionParserTests {
return (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
}
private FilterInvocation createFilterinvocation(String path) {
private FilterInvocation createFilterinvocation(String path, String method) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod(method);
request.setRequestURI(null);
request.setServletPath(path);

View File

@ -29,7 +29,7 @@ import javax.servlet.ServletResponse;
/**
* Tests {@link AbstractFilterInvocationDefinitionSource}.
* Tests {@link DefaultFilterInvocationDefinitionSource}.
*
* @author Ben Alex
* @version $Id$

View File

@ -20,6 +20,7 @@ import junit.framework.TestCase;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.MockFilterChain;
import org.springframework.security.SecurityConfig;
import org.springframework.security.util.RegexUrlPathMatcher;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@ -51,7 +52,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
}
@ -72,7 +73,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
editor.setAsText(
"CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON\r\n\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
}
@ -80,8 +81,8 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor.getValue();
assertTrue(map instanceof RegExpBasedFilterInvocationDefinitionMap);
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertTrue(map.getUrlMatcher() instanceof RegexUrlPathMatcher);
}
public void testDetectsDuplicateDirectivesOnSameLineSituation1() {
@ -124,7 +125,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertEquals(0, map.getMapSize());
}
@ -142,7 +143,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
Iterator iter = map.getConfigAttributeDefinitions();
int counter = 0;
@ -158,7 +159,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
httpRequest.setServletPath("/totally/different/path/index.html");
@ -173,7 +174,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertEquals(2, map.getMapSize());
}
@ -181,7 +182,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText(null);
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertEquals(0, map.getMapSize());
}
@ -190,7 +191,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
editor.setAsText(
"\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
// Test ensures we match the first entry, not the second
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
@ -211,7 +212,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
editor.setAsText(
"\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER\r\n\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
httpRequest.setServletPath("/secure/super/very_secret.html");
@ -230,7 +231,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
httpRequest.setServletPath("/secure/super/very_secret.html");
@ -249,7 +250,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("PATTERN_TYPE_APACHE_ANT\r\n/secure/super/**=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
httpRequest.setServletPath("/secure/super/very_secret.html");
@ -269,7 +270,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
editor.setAsText(
" \\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE \r\n \r\n \r\n // comment line \r\n \\A/testing.*\\Z=ROLE_TEST \r\n");
RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertEquals(2, map.getMapSize());
}
}

View File

@ -20,6 +20,7 @@ import junit.framework.TestCase;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.MockFilterChain;
import org.springframework.security.SecurityConfig;
import org.springframework.security.util.AntUrlPathMatcher;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@ -52,8 +53,8 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
"PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor.getValue();
assertTrue(map instanceof PathBasedFilterInvocationDefinitionMap);
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertTrue(map.getUrlMatcher() instanceof AntUrlPathMatcher);
}
public void testConvertUrlToLowercaseDefaultSettingUnchangedByEditor() {
@ -61,8 +62,8 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
"PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertFalse(map.getUrlMatcher().requiresLowerCaseUrl());
}
public void testConvertUrlToLowercaseSettingApplied() {
@ -70,8 +71,8 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
"CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON\r\nPATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertTrue(map.getUrlMatcher().requiresLowerCaseUrl());
}
public void testInvalidNameValueFailsToParse() {
@ -89,7 +90,7 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
"PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
Iterator iter = map.getConfigAttributeDefinitions();
int counter = 0;
@ -105,7 +106,7 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
httpRequest.setServletPath("/totally/different/path/index.html");
@ -121,7 +122,7 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
"PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertEquals(2, map.getMapSize());
}
@ -130,7 +131,7 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
"PATTERN_TYPE_APACHE_ANT\r\n/secure/super/**=ROLE_WE_DONT_HAVE,ANOTHER_ROLE\r\n/secure/**=ROLE_SUPERVISOR,ROLE_TELLER");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
// Test ensures we match the first entry, not the second
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
@ -151,7 +152,7 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
"PATTERN_TYPE_APACHE_ANT\r\n/secure/**=ROLE_SUPERVISOR,ROLE_TELLER\r\n/secure/super/**=ROLE_WE_DONT_HAVE");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
httpRequest.setServletPath("/secure/super/very_secret.html");
@ -170,7 +171,7 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
editor.setAsText("PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
MockHttpServletRequest httpRequest = new MockHttpServletRequest(null, null);
httpRequest.setServletPath("/secure/super/very_secret.html");
@ -190,7 +191,7 @@ public class FilterInvocationDefinitionSourceEditorWithPathsTests extends TestCa
editor.setAsText(
" PATTERN_TYPE_APACHE_ANT\r\n /secure/super/*=ROLE_WE_DONT_HAVE\r\n /secure/*=ROLE_SUPERVISOR,ROLE_TELLER \r\n \r\n \r\n // comment line \r\n \r\n");
PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor.getValue();
DefaultFilterInvocationDefinitionSource map = (DefaultFilterInvocationDefinitionSource) editor.getValue();
assertEquals(2, map.getMapSize());
}
}

View File

@ -30,7 +30,7 @@ import java.util.Vector;
* @author Ben Alex
* @version $Id$
*/
public class MockFilterInvocationDefinitionSource extends AbstractFilterInvocationDefinitionSource {
public class MockFilterInvocationDefinitionSource extends DefaultFilterInvocationDefinitionSource {
//~ Instance fields ================================================================================================
private List list;
@ -77,7 +77,7 @@ public class MockFilterInvocationDefinitionSource extends AbstractFilterInvocati
}
}
public ConfigAttributeDefinition lookupAttributes(String url) {
public ConfigAttributeDefinition lookupAttributes(String url, String method) {
throw new UnsupportedOperationException("mock method not implemented");
}
}

View File

@ -15,8 +15,6 @@
package org.springframework.security.intercept.web;
import junit.framework.TestCase;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.MockFilterChain;
import org.springframework.security.SecurityConfig;
@ -24,6 +22,9 @@ import org.springframework.security.SecurityConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* Tests parts of {@link PathBasedFilterInvocationDefinitionMap} not tested by {@link
@ -32,30 +33,25 @@ import org.springframework.mock.web.MockHttpServletResponse;
* @author Ben Alex
* @version $Id$
*/
public class PathBasedFilterDefinitionMapTests extends TestCase {
//~ Constructors ===================================================================================================
public PathBasedFilterDefinitionMapTests() {
}
public PathBasedFilterDefinitionMapTests(String arg0) {
super(arg0);
}
public class PathBasedFilterInvocationDefinitionMapTests {
//~ Methods ========================================================================================================
public void testConvertUrlToLowercaseIsTrueByDefault() {
@Test
public void convertUrlToLowercaseIsTrueByDefault() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
}
public void testConvertUrlToLowercaseSetterRespected() {
@Test
public void convertUrlToLowercaseSetterRespected() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
map.setConvertUrlToLowercaseBeforeComparison(false);
assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
}
public void testLookupNotRequiringExactMatchSuccessIfNotMatching() {
@Test
public void lookupNotRequiringExactMatchSuccessIfNotMatching() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
map.setConvertUrlToLowercaseBeforeComparison(true);
@ -63,90 +59,146 @@ public class PathBasedFilterDefinitionMapTests extends TestCase {
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/secure/super/**", def);
FilterInvocation fi = createFilterinvocation("/SeCuRE/super/somefile.html");
FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null);
ConfigAttributeDefinition response = map.lookupAttributes(fi.getRequestUrl());
assertEquals(def, response);
}
/**
* SEC-501. Not that as of 2.0, lower case comparisons are the default for this class.
* SEC-501. Note that as of 2.0, lower case comparisons are the default for this class.
*/
public void testLookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
@Test
public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/SeCuRE/super/**", def);
FilterInvocation fi = createFilterinvocation("/secure/super/somefile.html");
FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null);
ConfigAttributeDefinition response = map.lookupAttributes(fi.getRequestUrl());
assertEquals(def, response);
}
public void testLookupRequiringExactMatchFailsIfNotMatching() {
@Test
public void lookupRequiringExactMatchFailsIfNotMatching() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
map.setConvertUrlToLowercaseBeforeComparison(false);
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/secure/super/**", def);
FilterInvocation fi = createFilterinvocation("/SeCuRE/super/somefile.html");
FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null);
ConfigAttributeDefinition response = map.lookupAttributes(fi.getRequestUrl());
assertEquals(null, response);
}
public void testLookupRequiringExactMatchIsSuccessful() {
@Test
public void lookupRequiringExactMatchIsSuccessful() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
map.setConvertUrlToLowercaseBeforeComparison(false);
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/SeCurE/super/**", def);
FilterInvocation fi = createFilterinvocation("/SeCurE/super/somefile.html");
FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null);
ConfigAttributeDefinition response = map.lookupAttributes(fi.getRequestUrl());
assertEquals(def, response);
}
public void testLookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
@Test
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/someAdminPage.html**", def);
FilterInvocation fi = createFilterinvocation("/someAdminPage.html?a=/test");
FilterInvocation fi = createFilterInvocation("/someAdminPage.html?a=/test", null);
ConfigAttributeDefinition response = map.lookupAttributes(fi.getRequestUrl());
assertEquals(def, response); // see SEC-161 (it should truncate after ? sign)
}
@Test(expected = IllegalArgumentException.class)
public void unknownHttpMethodIsRejected() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/someAdminPage.html**", "UNKNOWN", def);
}
@Test
public void httpMethodLookupSucceeds() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/somepage**", "GET", def);
FilterInvocation fi = createFilterInvocation("/somepage", "GET");
ConfigAttributeDefinition attrs = map.getAttributes(fi);
assertEquals(def, attrs);
}
@Test
public void requestWithDifferentHttpMethodDoesntMatch() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/somepage**", "GET", def);
FilterInvocation fi = createFilterInvocation("/somepage", null);
ConfigAttributeDefinition attrs = map.getAttributes(fi);
assertNull(attrs);
}
@Test
public void httpMethodSpecificUrlTakesPrecedence() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
// Even though this is added before the method-specific def, the latter should match
ConfigAttributeDefinition allMethodDef = new ConfigAttributeDefinition();
allMethodDef.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/**", null, allMethodDef);
ConfigAttributeDefinition postOnlyDef = new ConfigAttributeDefinition();
postOnlyDef.addConfigAttribute(new SecurityConfig("ROLE_TWO"));
map.addSecureUrl("/somepage**", "POST", postOnlyDef);
FilterInvocation fi = createFilterInvocation("/somepage", "POST");
ConfigAttributeDefinition attrs = map.getAttributes(fi);
assertEquals(postOnlyDef, attrs);
}
/**
* Check fixes for SEC-321
*/
public void testExtraQuestionMarkStillMatches() {
@Test
public void extraQuestionMarkStillMatches() {
PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
ConfigAttributeDefinition def = new ConfigAttributeDefinition();
def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
map.addSecureUrl("/someAdminPage.html*", def);
FilterInvocation fi = createFilterinvocation("/someAdminPage.html?x=2/aa?y=3");
FilterInvocation fi = createFilterInvocation("/someAdminPage.html?x=2/aa?y=3", null);
ConfigAttributeDefinition response = map.lookupAttributes(fi.getRequestUrl());
assertEquals(def, response);
fi = createFilterinvocation("/someAdminPage.html??");
fi = createFilterInvocation("/someAdminPage.html??", null);
response = map.lookupAttributes(fi.getRequestUrl());
assertEquals(def, response);
}
private FilterInvocation createFilterinvocation(String path) {
private FilterInvocation createFilterInvocation(String path, String method) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI(null);
request.setMethod(method);
request.setServletPath(path);