/*
* #%L
* BroadleafCommerce Common Libraries
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* 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.
* #L%
*/
package org.broadleafcommerce.common.security.service;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.common.security.RandomGenerator;
import org.broadleafcommerce.common.util.BLCRequestUtils;
import org.owasp.validator.html.AntiSamy;
import org.owasp.validator.html.CleanResults;
import org.owasp.validator.html.Policy;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* @author jfischer
*/
@Service("blExploitProtectionService")
public class ExploitProtectionServiceImpl implements ExploitProtectionService {
private static final String CSRFTOKEN = "csrfToken";
private static final String CSRFTOKENPARAMETER = "csrfToken";
private static final Log LOG = LogFactory.getLog(ExploitProtectionServiceImpl.class);
private static class Handler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
URL resourceUrl = getClass().getClassLoader().getResource(u.getPath());
return resourceUrl.openConnection();
}
}
private static Policy getAntiSamyPolicy(String policyFileLocation) {
try {
URL url = new URL(null, policyFileLocation, new Handler());
return Policy.getInstance(url);
} catch (Exception e) {
throw new RuntimeException("Unable to create URL", e);
}
}
private static final String DEFAULTANTISAMYPOLICYFILELOCATION = "classpath:antisamy-myspace.xml";
protected String antiSamyPolicyFileLocation = DEFAULTANTISAMYPOLICYFILELOCATION;
//this is thread safe
private Policy antiSamyPolicy = getAntiSamyPolicy(antiSamyPolicyFileLocation);
//this is thread safe for the usage of scan()
private final AntiSamy as = new AntiSamy();
protected boolean xsrfProtectionEnabled = true;
protected boolean xssProtectionEnabled = true;
@Override
public String cleanString(String string) throws ServiceException {
if (!xssProtectionEnabled || StringUtils.isEmpty(string)) {
return string;
}
try {
CleanResults results = as.scan(string, antiSamyPolicy);
return results.getCleanHTML();
} catch (Exception e) {
LOG.error("Unable to clean the passed in entity values", e);
throw new ServiceException("Unable to clean the passed in entity values", e);
}
}
@Override
public String cleanStringWithResults(String string) throws ServiceException {
if (!xssProtectionEnabled || StringUtils.isEmpty(string)) {
return string;
}
try {
CleanResults results = as.scan(string, antiSamyPolicy);
if (results.getNumberOfErrors() > 0) {
throw new CleanStringException(results);
}
return results.getCleanHTML();
} catch (CleanStringException e) {
throw e;
} catch (Exception e) {
StringBuilder sb = new StringBuilder();
sb.append("Unable to clean the passed in entity values");
sb.append("\nNote - ");
sb.append(getAntiSamyPolicyFileLocation());
sb.append(" policy in effect. Set a new policy file to modify validation behavior/strictness.");
LOG.error(sb.toString(), e);
throw new ServiceException(sb.toString(), e);
}
}
@Override
public void compareToken(String passedToken) throws ServiceException {
if (xsrfProtectionEnabled) {
if (!getCSRFToken().equals(passedToken)) {
throw new ServiceException("XSRF token mismatch (" + passedToken + "). Session may be expired.");
} else {
LOG.debug("Validated CSRF token");
}
}
}
@Override
public String getCSRFToken() throws ServiceException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (BLCRequestUtils.isOKtoUseSession(new ServletWebRequest(request))) {
HttpSession session = request.getSession();
String token = (String) session.getAttribute(CSRFTOKEN);
if (StringUtils.isEmpty(token)) {
try {
token = RandomGenerator.generateRandomId("SHA1PRNG", 32);
} catch (NoSuchAlgorithmException e) {
LOG.error("Unable to generate random number", e);
throw new ServiceException("Unable to generate random number", e);
}
session.setAttribute(CSRFTOKEN, token);
}
return token;
}
return null;
}
@Override
public String getAntiSamyPolicyFileLocation() {
return antiSamyPolicyFileLocation;
}
@Override
public void setAntiSamyPolicyFileLocation(String antiSamyPolicyFileLocation) {
this.antiSamyPolicyFileLocation = antiSamyPolicyFileLocation;
antiSamyPolicy = getAntiSamyPolicy(antiSamyPolicyFileLocation);
}
public boolean isXsrfProtectionEnabled() {
return xsrfProtectionEnabled;
}
public void setXsrfProtectionEnabled(boolean xsrfProtectionEnabled) {
this.xsrfProtectionEnabled = xsrfProtectionEnabled;
}
public boolean isXssProtectionEnabled() {
return xssProtectionEnabled;
}
public void setXssProtectionEnabled(boolean xssProtectionEnabled) {
this.xssProtectionEnabled = xssProtectionEnabled;
}
@Override
public String getCsrfTokenParameter() {
return CSRFTOKENPARAMETER;
}
}