package com.digitalsanctuary.atg.servlet.pipeline;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import atg.nucleus.ServiceException;
import atg.service.perfmonitor.PerformanceMonitor;
import atg.servlet.DynamoHttpServletRequest;
import atg.servlet.DynamoHttpServletResponse;
import atg.servlet.pipeline.InsertableServletImpl;
/**
* @author Devon Hillard
*
* Incoming requests which have passed through one or more proxies will have a remoteAddr property of the request set to
* the IP of the last proxy which the request passed through. Proxies should maintain the original request IP, as well
* as any previous proxy IP addresses within an extended request header called X-FORWARDED-FOR. This class looks to see
* if that header exists and is populated, and if so, takes the left-most IP address (which should be the user's source
* IP address) and places it in the request's remoteAddr property, replacing the proxy IP current in there. We currently
* don't care about the proxy's IP address, and the address of the user is more useful for auditing, session security,
* and all other processes currently in place. Since all incoming requests are typically proxied by Akamai servers, we
* need this functionality in order to use ATG Dynamo's built-in session security mechanism which verifies request IP
* addresses against the IP address which spawned the session. This pipeline servlet has to go in front of the
* SessionManager
*
*/
public class ProxyIPFixerServlet extends InsertableServletImpl {
/**
* The request header name that holds the forwarded chain information.
*/
private static final String FORWARDED_FOR_HEADER_NAME = "X-FORWARDED-FOR";
/**
* The string representing the regular expression to match valid IP addresses. This is set from a properties file.
*/
private String mIPAddressPatternString;
/**
* The actual regular expression compiled Pattern object. This is created at startup, but the doStartService()
* method.
*/
private Pattern mIPAddressPattern;
/**
* This method handles the component setup.
*
* @see atg.servlet.pipeline.InsertableServletImpl#doStartService()
* @throws ServiceException
* if do start service fails.
*/
public void doStartService() throws ServiceException {
if (isLoggingInfo()) {
logInfo("ProxyIPFixerServlet.doStartService: Starting component...");
}
super.doStartService();
this.mIPAddressPattern = Pattern.compile(getIPAddressPatternString());
}
/**
* This method handles the component tear-down.
*
* @see atg.servlet.pipeline.InsertableServletImpl#doStopService()
* @throws ServiceException
* if do stop service fails.
*/
public void doStopService() throws ServiceException {
if (isLoggingInfo()) {
logInfo("ProxyIPFixerServlet.doStartService: Stopping component...");
}
super.doStopService();
}
/**
* This method takes in the request and response object, as part of the Dynamo servlet pipeline. All processing on
* those request and response objects takes place here.
*
* @param pRequest
* the Dynamo http request object.
* @param pResponse
* the Dynamo http repsonse object.
*
* @see atg.servlet.pipeline.PipelineableServletImpl#service(atg.servlet.DynamoHttpServletRequest,
* atg.servlet.DynamoHttpServletResponse)
* @throws IOException
* on error
* @throws ServletException
* on error
*/
public void service(final DynamoHttpServletRequest pRequest, final DynamoHttpServletResponse pResponse)
throws IOException, ServletException {
PerformanceMonitor.startOperation(getAbsoluteName(), "service()");
// Is this request proxied?
if (isProxiedRequest(pRequest)) {
if (isLoggingDebug()) {
logDebug("ProxyIPFixerServlet.service: this request appears to have been proxied.");
}
// Get the original IP address for the request
try {
final String originalRequestIP = getOriginatingIP(pRequest);
if (isLoggingDebug()) {
logDebug("ProxyIPFixerServlet.service: replacing the request's remoteAddr " + "with current value:"
+ pRequest.getRemoteAddr() + " with the IP from the forwarded for headers with value:"
+ originalRequestIP + ".");
}
// Replace the latest proxy's IP address with the originating IP
// address for the request in the request's remoteAddr property
pRequest.setRemoteAddr(originalRequestIP);
} catch (final UnknownHostException uhe) {
if (isLoggingError()) {
logError("ProxyIPFixerServlet.service:" + "The value in the forwarded for request header "
+ "was not parseable into an IP address.", uhe);
}
}
}
PerformanceMonitor.endOperation(getAbsoluteName(), "service()");
// Call super to pass the request on to the next servlet
super.service(pRequest, pResponse);
}
/**
* This method looks in the passed in request to see if a forwarding header, identified by the static constant
* FORWARDED_FOR_HEADER_NAME exists, and is populated.
*
* @param pRequest
* the http request to examine
* @return true if the request has been proxied, false, if it has not been (to the best of our ability to determine)
*/
private boolean isProxiedRequest(final DynamoHttpServletRequest pRequest) {
// Get the header from the request
final String forwardedForHeader = pRequest.getHeader(FORWARDED_FOR_HEADER_NAME);
// Check if we got anything and if we did, check to make sure it isn't zero length
return (forwardedForHeader != null && forwardedForHeader.length() > 0);
}
/**
* This method pulls the Originating IP out of the header and returns it as a string.
*
* @param pRequest
* the http request to examine.
* @return the originating IP of the request as a String.
* @throws UnknownHostException
* if the header field cannot be parsed as an IP address.
*/
private String getOriginatingIP(final DynamoHttpServletRequest pRequest) throws UnknownHostException {
// Get the header from the request
final String forwardedForHeader = pRequest.getHeader(FORWARDED_FOR_HEADER_NAME);
// Check if we got anything
if (forwardedForHeader != null) {
// Get the leftmost address if there are more than one
String originatingAddress = null;
final int commaIndex = forwardedForHeader.indexOf(',');
if (commaIndex > -1) {
if (isLoggingDebug()) {
logDebug("ProxyIPFixerServlet.getOriginatingIP:"
+ "there are many IPs in the header, getting the first one.");
}
originatingAddress = forwardedForHeader.substring(0, commaIndex);
} else {
if (isLoggingDebug()) {
logDebug("ProxyIPFixerServlet.getOriginatingIP:" + "there is only one ip in the header, using it.");
}
originatingAddress = forwardedForHeader;
}
// Verify that the value matches what we expect, a quad-IP. This
// regex is looking for: 1 to
// 3 digits followed by a period, repeating 3 times, and
// followed by another set of 1 to 3 digits
if (this.mIPAddressPattern.matcher(originatingAddress).matches()) {
if (isLoggingDebug()) {
logDebug("ProxyIPFixerServlet.getOriginatingIP:" + "The string appears to be a valid IP address:"
+ originatingAddress);
}
return originatingAddress;
}
}
final String msg = "ProxyIPFixerServlet.getOriginatingIP:"
+ "parsing attempt failed. Here is what we were working with:" + " header:" + forwardedForHeader
+ ".";
if (isLoggingDebug()) {
logDebug(msg);
}
// If any of the above checks failed, we were unable to obtain a useable
// IP address from the request's forwarded ip header list.
throw new UnknownHostException(msg);
}
/**
* @return the iPAddressPattern
*/
public String getIPAddressPatternString() {
return this.mIPAddressPatternString;
}
/**
* @param pAddressPattern
* the iPAddressPattern to set
*/
public void setIPAddressPatternString(final String pAddressPattern) {
this.mIPAddressPatternString = pAddressPattern;
}
}
HoMEŮëԸͼƬENTER NUMBET 007