Skip to content
Permalink
Browse files

Improved: Refactor ControlFilter

(OFBIZ-10449)
various improvements:
* Inheriting from HttpFilter instead of implementing Filter
* Using streams when getting the allowed paths
No functional change

Thanks to Mathieu Lirzin for this refactoring

git-svn-id: https://svn.apache.org/repos/asf/ofbiz/ofbiz-framework/trunk@1850171 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
nmalin committed Jan 2, 2019
1 parent becdc69 commit 95f4d4b6eb6441fab72411eecd80dd5c06a5e88b
@@ -19,21 +19,25 @@
package org.apache.ofbiz.webapp.control;

import java.io.IOException;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.ofbiz.base.util.Debug;

/*
/**
* A Filter used to specify a whitelist of allowed paths to the OFBiz application.
* Requests that do not match any of the paths listed in allowedPaths are redirected to redirectPath, or an error code
* is returned (the error code can be set in errorCode, the default value is 403).
@@ -58,108 +62,121 @@
* with name _FORWARDED_FROM_SERVLET_ is present; this attribute is typically set by the ControlServlet to indicate
* that the request path is safe and should not be checked again
*/


public class ControlFilter implements Filter {
@SuppressWarnings("serial")
public class ControlFilter extends HttpFilter {
public static final String FORWARDED_FROM_SERVLET = "_FORWARDED_FROM_SERVLET_";

public static final int DEFAULT_HTTP_ERROR_CODE = 403;
private static final String module = ControlFilter.class.getName();

/** The path used for redirection. */
private String redirectPath;
/** True when all traffic must be redirected to {@code redirectPath}. */
private boolean redirectAll;
/** True when redirectPath is an absolute URI. */
private boolean redirectPathIsUrl;
private String redirectPath;
protected int errorCode;
private Set<String> allowedPaths = new HashSet<>();
/** The error code used when current path is not allowed and {@code redirectPath} is null. */
private int errorCode;
/** The list of all path prefixes that are allowed. */
private Set<String> allowedPaths;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
redirectPath = filterConfig.getInitParameter("redirectPath");
redirectPathIsUrl = (redirectPath != null && redirectPath.toLowerCase().startsWith("http"));
String redirectAllString = filterConfig.getInitParameter("forceRedirectAll");
redirectAll = (redirectPath != null && redirectAllString != null && "Y".equalsIgnoreCase(redirectAllString));
String errorCodeString = filterConfig.getInitParameter("errorCode");
errorCode = 403;
if (errorCodeString != null) {
try {
errorCode = Integer.parseInt(errorCodeString);
} catch (NumberFormatException nfe) {
Debug.logWarning(nfe, "Error code specified would not parse to Integer: " + errorCodeString, module);
Debug.logWarning(nfe, "The default error code will be used: " + errorCode, module);
}
public void init(FilterConfig conf) throws ServletException {
redirectPath = conf.getInitParameter("redirectPath");
redirectPathIsUrl = UrlValidator.getInstance().isValid(redirectPath);
errorCode = readErrorCode(conf.getInitParameter("errorCode"));
allowedPaths = readAllowedPaths(conf.getInitParameter("allowedPaths"));
redirectAll = (redirectPath != null)
&& BooleanUtils.toBoolean(conf.getInitParameter("forceRedirectAll"));

// Ensure that the path used for local redirections is allowed.
if (redirectPath != null && !redirectPathIsUrl) {
allowedPaths.add(redirectPath);
}
String allowedPathsString = filterConfig.getInitParameter("allowedPaths");
if (allowedPathsString != null) {
String[] result = allowedPathsString.split(":");
for (int x = 0; x < result.length; x++) {
allowedPaths.add(result[x]);
}
// if an URI is specified in the redirectPath parameter, it is added to the allowed list
if (redirectPath != null && !redirectPathIsUrl) {
allowedPaths.add(redirectPath);
}
}

/**
* Converts {@code code} string to an integer. If conversion fails, Return
* {@code DEFAULT_HTTP_ERROR_STATUS} instead.
*
* @param code an arbitrary string which can be {@code null}
* @return the integer matching {@code code}
*/
private static int readErrorCode(String code) {
try {
return (code == null) ? DEFAULT_HTTP_ERROR_CODE : Integer.parseInt(code);
} catch (NumberFormatException err) {
Debug.logWarning(err, "Error code specified would not parse to Integer: " + code, module);
Debug.logWarning(err, "The default error code will be used: " + DEFAULT_HTTP_ERROR_CODE, module);
return DEFAULT_HTTP_ERROR_CODE;
}
}

/**
* Splits the paths defined by {@code paths}.
*
* @param paths a string which can be either {@code null} or a list of
* paths separated by ':'.
* @return a set of string
*/
private static Set<String> readAllowedPaths(String paths) {
return (paths == null) ? Collections.emptySet()
: Arrays.stream(paths.split(":")).collect(Collectors.toSet());
}

/**
* Makes allowed paths pass through while redirecting the others to a fix location.
*
* @see Filter#doFilter
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
public void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
throws IOException, ServletException {
String context = req.getContextPath();
HttpSession session = req.getSession();

// check if we are told to redirect everything
// Check if we are told to redirect everything.
if (redirectAll) {
// little trick here so we don't loop on ourselves
if (httpRequest.getSession().getAttribute("_FORCE_REDIRECT_") == null) {
httpRequest.getSession().setAttribute("_FORCE_REDIRECT_", "true");
if (session.getAttribute("_FORCE_REDIRECT_") == null) {
session.setAttribute("_FORCE_REDIRECT_", "true");
Debug.logWarning("Redirecting user to: " + redirectPath, module);
if (redirectPathIsUrl) {
httpResponse.sendRedirect(redirectPath);
} else {
httpResponse.sendRedirect(httpRequest.getContextPath() + redirectPath);
}
return;
redirect(resp, context);
} else {
httpRequest.getSession().removeAttribute("_FORCE_REDIRECT_");
chain.doFilter(httpRequest, httpResponse);
return;
session.removeAttribute("_FORCE_REDIRECT_");
chain.doFilter(req, resp);
}
}
} else if (req.getAttribute(FORWARDED_FROM_SERVLET) == null
&& !allowedPaths.isEmpty()) {
// Get the request URI without the webapp mount point.
String uriWithContext = req.getRequestURI();
String uri = uriWithContext.substring(context.length());

if (httpRequest.getAttribute(FORWARDED_FROM_SERVLET) == null && !allowedPaths.isEmpty()) {
// check to make sure the requested url is allowed
// get the request URI without the webapp mount point
String requestUri = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
int offset = requestUri.indexOf("/", 1);
if (offset == -1) {
offset = requestUri.length();
}
while (!allowedPaths.contains(requestUri.substring(0, offset))) {
offset = requestUri.indexOf("/", offset + 1);
if (offset == -1) {
if (allowedPaths.contains(requestUri)) {
break;
}
// path not allowed
if (redirectPath == null) {
httpResponse.sendError(errorCode, httpRequest.getRequestURI());
} else {
if (redirectPathIsUrl) {
httpResponse.sendRedirect(redirectPath);
} else {
httpResponse.sendRedirect(httpRequest.getContextPath() + redirectPath);
}
}
if (Debug.infoOn()) {
Debug.logInfo("[Filtered request]: " + httpRequest.getRequestURI() + " --> " + (redirectPath == null? errorCode: redirectPath), module);
}
return;
// Check if the requested URI is allowed.
if (allowedPaths.stream().anyMatch(uri::startsWith)) {
chain.doFilter(req, resp);
} else {
if (redirectPath == null) {
resp.sendError(errorCode, uriWithContext);
} else {
redirect(resp, context);
}
if (Debug.infoOn()) {
Debug.logInfo("[Filtered request]: " + uriWithContext + " --> "
+ (redirectPath == null ? errorCode : redirectPath), module);
}
}
chain.doFilter(request, httpResponse);
}
}

@Override
public void destroy() {

/**
* Sends an HTTP response redirecting to {@code redirectPath}.
*
* @param resp The response to send
* @param contextPath the prefix to add to the redirection when
* {@code redirectPath} is a relative URI.
* @throws IOException when redirection has not been properly sent.
*/
private void redirect(HttpServletResponse resp, String contextPath) throws IOException {
resp.sendRedirect(redirectPathIsUrl ? redirectPath : (contextPath + redirectPath));
}
}

0 comments on commit 95f4d4b

Please sign in to comment.
You can’t perform that action at this time.