16 changed files with 515 additions and 299 deletions
@ -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(); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue