/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
import java.util.Arrays;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.StringUtils;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
/**
*
* @author Chris Berry
* http://opensource.atlassian.com/projects/spring/browse/SEC-531
*
*/
public class RESTfulPathBasedFilterInvocationDefinitionMap
implements FilterInvocationSecurityMetadataSource {
static private Log log = LogFactory.getLog(RESTfulPathBasedFilterInvocationDefinitionMap.class);
//~ Instance fields ================================================================================================
private Collection<EntryHolder> requestMap = new Vector<EntryHolder>();
private PathMatcher pathMatcher = new AntPathMatcher();
private boolean convertUrlToLowercaseBeforeComparison = false;
//~ Methods ========================================================================================================
public boolean supports(Class clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public void addSecureUrl(String antPath, String[] httpMethods, Collection<ConfigAttribute> attrs) {
requestMap.add( new EntryHolder(antPath, httpMethods, attrs) );
if (log.isDebugEnabled()) {
log.debug("Added Ant path: " + antPath + "; attributes: " + attrs + ", httpMethods: " + Arrays.toString(httpMethods));
}
}
public void addSecureUrl(String antPath, Collection<ConfigAttribute> attrs) {
throw new IllegalArgumentException( "addSecureUrl(String, Collection<ConfigAttribute> ) is INVALID for RESTfulDefinitionSource" );
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> set = new HashSet<ConfigAttribute>();
for (EntryHolder h : requestMap) {
set.addAll(h.getConfigAttributes());
}
return set;
//return set.iterator();
}
public int getMapSize() {
return this.requestMap.size();
}
public boolean isConvertUrlToLowercaseBeforeComparison() {
return convertUrlToLowercaseBeforeComparison;
}
public void setConvertUrlToLowercaseBeforeComparison(boolean convertUrlToLowercaseBeforeComparison) {
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
}
public Collection<ConfigAttribute> 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 this.lookupAttributes( url, method );
}
public Collection<ConfigAttribute> lookupAttributes( String url ) {
throw new IllegalArgumentException( "lookupAttributes(String url) is INVALID for RESTfulDefinitionSource" );
}
public Collection<ConfigAttribute> lookupAttributes( String url, String httpMethod ) {
// 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 (isConvertUrlToLowercaseBeforeComparison()) {
url = url.toLowerCase();
if (log.isDebugEnabled()) {
log.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url
+ "' and httpMethod= " + httpMethod );
}
}
Iterator iter = requestMap.iterator();
while (iter.hasNext()) {
EntryHolder entryHolder = (EntryHolder) iter.next();
String antPath = entryHolder.getAntPath();
String[] methodList = entryHolder.getHttpMethodList();
if (log.isDebugEnabled()) {
log.debug( "~~~~~~~~~~ antPath= " + antPath + " methodList= " + Arrays.toString(methodList) );
}
boolean matchedPath = pathMatcher.match( antPath, url );
boolean matchedMethods = true;
if ( methodList != null ) {
matchedMethods = false;
for( int ii=0; ii < methodList.length; ii++ ) {
if ( methodList[ii].equals( httpMethod ) ) {
matchedMethods = true;
break;
}
}
}
if ( log.isDebugEnabled() )
log.debug("Candidate is: '" + url + "'; antPath is " + antPath
+ "; matchedPath=" + matchedPath + "; matchedMethods=" + matchedMethods );
if ( matchedPath && matchedMethods ) {
log.debug("returning " + StringUtils.collectionToCommaDelimitedString(entryHolder.getConfigAttributes()));
return entryHolder.getConfigAttributes();
}
}
return null;
}
//~ Inner Classes ==================================================================================================
protected class EntryHolder {
private Collection <ConfigAttribute> configAttributes;
private String antPath;
private String[] httpMethodList;
public EntryHolder( String antPath, String[] httpMethodList, Collection<ConfigAttribute> attrs ) {
this.antPath = antPath;
this.configAttributes = attrs;
this.httpMethodList = httpMethodList;
}
protected EntryHolder() {
throw new IllegalArgumentException("Cannot use default constructor");
}
public String getAntPath() {
return antPath;
}
public String[] getHttpMethodList() {
return httpMethodList;
}
public Collection <ConfigAttribute> getConfigAttributes() {
return configAttributes;
}
}
}