/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.server.security.extender;
import java.util.regex.Pattern;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RegexRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.web.context.WebApplicationContext;
import eu.esdihumboldt.util.reflection.ReflectionHelper;
/**
* <p>
* A {@link RequestMatcher} that is also aware of the current context path and
* is able to use it for matching. After evaluating the context path this
* matcher delegates the call to another request matcher.
* </p>
* <p>
* Context paths begin with a double slash and end with a single one. For
* example
*
* <pre>
* <code>//hale/version</code>
* </pre>
*
* yields to the URL <code>/version</code> in the context path
* <code>/hale</code>.
* </p>
* <p>
* Currently, this class only handles delegates of type
* {@link AntPathRequestMatcher} and {@link RegexRequestMatcher}, because these
* are the only ones that match against the servlet path. All others currently
* implemented in Spring match against some other attribute (such as IP address
* for example).
* </p>
*
* @author Michel Kraemer
*/
public class DelegatingContextPathUrlMatcher implements RequestMatcher {
/**
* The delegate object
*/
private final RequestMatcher _delegate;
/**
* The context path of the current web application
*/
private final String _contextPath;
/**
* Wraps arounds {@link HttpServletRequest} and returns a servlet path that
* is augmented with the current application's context path
*/
private class PathRequestWrapper extends HttpServletRequestWrapper {
/**
* @see HttpServletRequestWrapper#HttpServletRequestWrapper(HttpServletRequest)
*/
public PathRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getServletPath() {
String p = super.getServletPath();
if (!p.isEmpty() && !p.startsWith("/")) {
p = "/" + p;
}
return "//" + _contextPath + p;
}
}
/**
* Constructs a new request matcher that delegates to the given one
*
* @param delegate the delegate matcher
* @param contextPath the context path of the current web application
*/
private DelegatingContextPathUrlMatcher(RequestMatcher delegate, String contextPath) {
_delegate = delegate;
_contextPath = contextPath;
}
/**
* Checks if this class should be used to intercept calls to the given
* request matcher and if so returns the wrapped object. Otherwise returns
* the original object.
*
* @param delegate the request matcher to wrap
* @param ctx the current web application context
* @return a new request matcher if this class supports the given one and if
* calls to it should be intercepted, the original request matcher
* otherwise
*/
public static RequestMatcher wrapIfNecessary(RequestMatcher delegate, WebApplicationContext ctx) {
String pattern = getPattern(delegate);
if (pattern == null) {
// request matcher is unsupported
return delegate;
}
if (!pattern.startsWith("//")) {
// pattern does not start with a context path
return delegate;
}
// find context path in pattern
int nextSlash = pattern.indexOf('/', 2);
String patternPath;
if (nextSlash != -1) {
patternPath = pattern.substring(2, nextSlash);
}
else {
patternPath = pattern.substring(2);
}
// get current context path
ServletContext sc = ctx.getServletContext();
String currentPath = sc.getContextPath();
if (!currentPath.isEmpty() && currentPath.charAt(0) == '/') {
currentPath = currentPath.substring(1);
}
if (!patternPath.equals(currentPath)) {
// pattern points to another context path
return delegate;
}
// wrap it!
return new DelegatingContextPathUrlMatcher(delegate, currentPath);
}
/**
* Retrieves the pattern from a supported request matcher
*
* @param delegate the request matcher
* @return the pattern the request matcher uses to match against request
* paths or null if the request matcher is unsupported
*/
private static String getPattern(RequestMatcher delegate) {
if (delegate instanceof AntPathRequestMatcher) {
AntPathRequestMatcher aprm = (AntPathRequestMatcher) delegate;
return aprm.getPattern();
}
else if (delegate instanceof RegexRequestMatcher) {
try {
Pattern p = ReflectionHelper.getDeepPropertyOrField(delegate, "pattern",
Pattern.class);
if (p != null) {
return p.pattern();
}
return null;
} catch (Exception e) {
// unsupported request matcher
throw new RuntimeException("Request matcher is of type RegexRequestMatcher "
+ "but its pattern could not be retrieved", e);
}
}
return null;
}
@Override
public boolean matches(HttpServletRequest request) {
HttpServletRequestWrapper wrapper = new PathRequestWrapper(request);
return _delegate.matches(wrapper);
}
}