mirror of
https://github.com/apache/struts.git
synced 2026-09-12 09:05:04 +00:00
Merge branch 'feature/exclude-object-class' into develop
This adds new security mechanism to prevent access particular classes and packages
This commit is contained in:
@@ -281,4 +281,20 @@ public final class StrutsConstants {
|
||||
|
||||
/** Allows override default DispatcherErrorHandler **/
|
||||
public static final String STRUTS_DISPATCHER_ERROR_HANDLER = "struts.dispatcher.errorHandler";
|
||||
|
||||
/** Comma delimited set of excluded classes and package names which cannot be accessed via expressions **/
|
||||
public static final String STRUTS_EXCLUDED_CLASSES = "struts.excludedClasses";
|
||||
public static final String STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS = "struts.excludedPackageNamePatterns";
|
||||
|
||||
/** Dedicated services to check if passed string is excluded/accepted **/
|
||||
public static final String STRUTS_EXCLUDED_PATTERNS_CHECKER = "struts.excludedPatterns.checker";
|
||||
public static final String STRUTS_ACCEPTED_PATTERNS_CHECKER = "struts.acceptedPatterns.checker";
|
||||
|
||||
/** Constant is used to override framework's default excluded patterns **/
|
||||
public static final String STRUTS_OVERRIDE_EXCLUDED_PATTERNS = "struts.override.excludedPatterns";
|
||||
public static final String STRUTS_OVERRIDE_ACCEPTED_PATTERNS = "struts.override.acceptedPatterns";
|
||||
|
||||
public static final String STRUTS_ADDITIONAL_EXCLUDED_PATTERNS = "struts.additional.excludedPatterns";
|
||||
public static final String STRUTS_ADDITIONAL_ACCEPTED_PATTERNS = "struts.additional.acceptedPatterns";
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
package org.apache.struts2.config;
|
||||
|
||||
import com.opensymphony.xwork2.ActionProxyFactory;
|
||||
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.FileManager;
|
||||
import com.opensymphony.xwork2.FileManagerFactory;
|
||||
import com.opensymphony.xwork2.LocaleProvider;
|
||||
@@ -312,6 +314,12 @@ import java.util.StringTokenizer;
|
||||
* <td>Used to parse expressions like ${foo.bar} or %{bar.foo} but it is up tp the TextParser's
|
||||
* implementation what kind of opening char to use (#, $, %, etc)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>com.opensymphony.xwork2.ExcludedPatternsChecker</td>
|
||||
* <td>struts.excludedPatterns.checker</td>
|
||||
* <td>request</td>
|
||||
* <td>Used across different interceptors to check if given string matches one of the excluded patterns</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <!-- END SNIPPET: extensionPoints -->
|
||||
@@ -343,7 +351,7 @@ public class DefaultBeanSelectionProvider extends AbstractBeanSelectionProvider
|
||||
alias(ResultFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY_RESULTFACTORY, builder, props);
|
||||
alias(ConverterFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY_CONVERTERFACTORY, builder, props);
|
||||
alias(InterceptorFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY_INTERCEPTORFACTORY, builder, props);
|
||||
alias(ValidatorFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY_INTERCEPTORFACTORY, builder, props);
|
||||
alias(ValidatorFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY_VALIDATORFACTORY, builder, props);
|
||||
|
||||
alias(FileManagerFactory.class, StrutsConstants.STRUTS_FILE_MANAGER_FACTORY, builder, props, Scope.SINGLETON);
|
||||
|
||||
@@ -383,6 +391,10 @@ public class DefaultBeanSelectionProvider extends AbstractBeanSelectionProvider
|
||||
|
||||
alias(DispatcherErrorHandler.class, StrutsConstants.STRUTS_DISPATCHER_ERROR_HANDLER, builder, props);
|
||||
|
||||
/** Checker is used mostly in interceptors, so there be one instance of checker per interceptor with Scope.DEFAULT **/
|
||||
alias(ExcludedPatternsChecker.class, StrutsConstants.STRUTS_EXCLUDED_PATTERNS_CHECKER, builder, props, Scope.DEFAULT);
|
||||
alias(AcceptedPatternsChecker.class, StrutsConstants.STRUTS_ACCEPTED_PATTERNS_CHECKER, builder, props, Scope.DEFAULT);
|
||||
|
||||
switchDevMode(props);
|
||||
|
||||
// Convert Struts properties into XWork properties
|
||||
@@ -392,6 +404,14 @@ public class DefaultBeanSelectionProvider extends AbstractBeanSelectionProvider
|
||||
convertIfExist(props, StrutsConstants.STRUTS_ALLOW_STATIC_METHOD_ACCESS, XWorkConstants.ALLOW_STATIC_METHOD_ACCESS);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, XWorkConstants.RELOAD_XML_CONFIGURATION);
|
||||
|
||||
convertIfExist(props, StrutsConstants.STRUTS_EXCLUDED_CLASSES, XWorkConstants.OGNL_EXCLUDED_CLASSES);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS, XWorkConstants.OGNL_EXCLUDED_PACKAGE_NAME_PATTERNS);
|
||||
|
||||
convertIfExist(props, StrutsConstants.STRUTS_ADDITIONAL_EXCLUDED_PATTERNS, XWorkConstants.ADDITIONAL_EXCLUDED_PATTERNS);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_ADDITIONAL_ACCEPTED_PATTERNS, XWorkConstants.ADDITIONAL_ACCEPTED_PATTERNS);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_OVERRIDE_EXCLUDED_PATTERNS, XWorkConstants.OVERRIDE_EXCLUDED_PATTERNS);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_OVERRIDE_ACCEPTED_PATTERNS, XWorkConstants.OVERRIDE_ACCEPTED_PATTERNS);
|
||||
|
||||
LocalizedTextUtil.addDefaultResourceBundle("org/apache/struts2/struts-messages");
|
||||
loadCustomResourceBundles(props);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ package org.apache.struts2.interceptor;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
|
||||
import com.opensymphony.xwork2.ExcludedPatterns;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.util.TextParseUtil;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
@@ -33,7 +34,6 @@ import org.apache.struts2.ServletActionContext;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -176,12 +176,12 @@ public class CookieInterceptor extends AbstractInterceptor {
|
||||
|
||||
// Allowed names of cookies
|
||||
private Pattern acceptedPattern = Pattern.compile(ACCEPTED_PATTERN, Pattern.CASE_INSENSITIVE);
|
||||
private Set<Pattern> excludedPatterns = new HashSet<Pattern>();
|
||||
|
||||
public CookieInterceptor() {
|
||||
for (String pattern : ExcludedPatterns.EXCLUDED_PATTERNS) {
|
||||
excludedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
private ExcludedPatternsChecker excludedPatternsChecker;
|
||||
|
||||
@Inject
|
||||
public void setExcludedPatternsChecker(ExcludedPatternsChecker excludedPatternsChecker) {
|
||||
this.excludedPatternsChecker = excludedPatternsChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,16 +260,7 @@ public class CookieInterceptor extends AbstractInterceptor {
|
||||
* @return true|false
|
||||
*/
|
||||
protected boolean isAcceptableValue(String value) {
|
||||
for (Pattern excludedPattern : excludedPatterns) {
|
||||
boolean matches = !excludedPattern.matcher(value).matches();
|
||||
if (!matches) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Cookie value [#0] matches excludedPattern [#1]", value, excludedPattern.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return !isExcluded(value) && isAccepted(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +274,7 @@ public class CookieInterceptor extends AbstractInterceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if name of Cookie match {@link #acceptedPattern}
|
||||
* Checks if name/value of Cookie is acceptable
|
||||
*
|
||||
* @param name of Cookie
|
||||
* @return true|false
|
||||
@@ -303,24 +294,21 @@ public class CookieInterceptor extends AbstractInterceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if name of Cookie match {@link #excludedPatterns}
|
||||
* Checks if name/value of Cookie is excluded
|
||||
*
|
||||
* @param name of Cookie
|
||||
* @return true|false
|
||||
*/
|
||||
protected boolean isExcluded(String name) {
|
||||
for (Pattern excludedPattern : excludedPatterns) {
|
||||
boolean matches = excludedPattern.matcher(name).matches();
|
||||
if (matches) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Cookie [#0] matches excludedPattern [#1]", name, excludedPattern.toString());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Cookie [#0] doesn't match excludedPattern [#1]", name, excludedPattern.toString());
|
||||
}
|
||||
ExcludedPatternsChecker.IsExcluded excluded = excludedPatternsChecker.isExcluded(name);
|
||||
if (excluded.isExcluded()) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Cookie [#0] matches excludedPattern [#1]", name, excluded.getExcludedPattern());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Cookie [#0] doesn't match excludedPattern [#1]", name, excluded.getExcludedPattern());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,23 @@
|
||||
"http://struts.apache.org/dtds/struts-2.3.dtd">
|
||||
|
||||
<struts>
|
||||
|
||||
<constant name="struts.excludedClasses"
|
||||
value="
|
||||
java.lang.Object,
|
||||
java.lang.Runtime,
|
||||
java.lang.System,
|
||||
java.lang.Class,
|
||||
java.lang.ClassLoader,
|
||||
java.lang.Shutdown,
|
||||
ognl.OgnlContext,
|
||||
ognl.MemberAccess,
|
||||
ognl.ClassResolver,
|
||||
ognl.TypeConverter,
|
||||
com.opensymphony.xwork2.ActionContext" />
|
||||
<!-- this must be valid regex, each '.' in package name must be escaped! -->
|
||||
<constant name="struts.excludedPackageNamePatterns" value="^java\.lang\..*,^ognl.*,^javax.*" />
|
||||
|
||||
<bean class="com.opensymphony.xwork2.ObjectFactory" name="struts"/>
|
||||
<bean type="com.opensymphony.xwork2.factory.ResultFactory" name="struts" class="org.apache.struts2.factory.StrutsResultFactory" />
|
||||
<bean type="com.opensymphony.xwork2.factory.ActionFactory" name="struts" class="com.opensymphony.xwork2.factory.DefaultActionFactory" />
|
||||
@@ -141,6 +158,9 @@
|
||||
<bean type="ognl.PropertyAccessor" name="java.util.HashSet" class="com.opensymphony.xwork2.ognl.accessor.XWorkCollectionPropertyAccessor" />
|
||||
<bean type="ognl.PropertyAccessor" name="java.util.HashMap" class="com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor" />
|
||||
|
||||
<bean type="com.opensymphony.xwork2.security.ExcludedPatternsChecker" name="struts" class="com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker" />
|
||||
<bean type="com.opensymphony.xwork2.security.AcceptedPatternsChecker" name="struts" class="com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker" />
|
||||
|
||||
<package name="struts-default" abstract="true">
|
||||
<result-types>
|
||||
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
|
||||
|
||||
@@ -24,6 +24,8 @@ package org.apache.struts2;
|
||||
import com.opensymphony.xwork2.Action;
|
||||
import com.opensymphony.xwork2.ActionProxyFactory;
|
||||
import com.opensymphony.xwork2.DefaultActionProxyFactory;
|
||||
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.ObjectFactory;
|
||||
import com.opensymphony.xwork2.config.Configuration;
|
||||
import com.opensymphony.xwork2.config.ConfigurationException;
|
||||
@@ -164,5 +166,8 @@ public class TestConfigurationProvider implements ConfigurationProvider {
|
||||
if (!builder.contains(ActionProxyFactory.class)) {
|
||||
builder.factory(ActionProxyFactory.class, DefaultActionProxyFactory.class);
|
||||
}
|
||||
if (!builder.contains(ExcludedPatternsChecker.class)) {
|
||||
builder.factory(ExcludedPatternsChecker.class, DefaultExcludedPatternsChecker.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Map;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import org.easymock.MockControl;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
@@ -65,6 +66,8 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
// by default the interceptor doesn't accept any cookies
|
||||
CookieInterceptor interceptor = new CookieInterceptor();
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
|
||||
interceptor.intercept(invocation);
|
||||
|
||||
assertTrue(action.getCookiesMap().isEmpty());
|
||||
@@ -99,6 +102,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
actionInvocationControl.replay();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor();
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("*");
|
||||
interceptor.setCookiesValue("*");
|
||||
interceptor.intercept(invocation);
|
||||
@@ -140,6 +144,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
actionInvocationControl.replay();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor();
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("cookie1, cookie2, cookie3");
|
||||
interceptor.setCookiesValue("cookie1value, cookie2value, cookie3value");
|
||||
interceptor.intercept(invocation);
|
||||
@@ -180,6 +185,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
actionInvocationControl.replay();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor();
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("cookie1, cookie3");
|
||||
interceptor.setCookiesValue("cookie1value, cookie2value, cookie3value");
|
||||
interceptor.intercept(invocation);
|
||||
@@ -220,6 +226,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
actionInvocationControl.replay();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor();
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("cookie1, cookie3");
|
||||
interceptor.setCookiesValue("*");
|
||||
interceptor.intercept(invocation);
|
||||
@@ -260,6 +267,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
actionInvocationControl.replay();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor();
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("cookie1, cookie3");
|
||||
interceptor.setCookiesValue("");
|
||||
interceptor.intercept(invocation);
|
||||
@@ -301,6 +309,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
actionInvocationControl.replay();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor();
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("cookie1, cookie3");
|
||||
interceptor.setCookiesValue("cookie1value");
|
||||
interceptor.intercept(invocation);
|
||||
@@ -361,6 +370,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
return accepted;
|
||||
}
|
||||
};
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("*");
|
||||
|
||||
MockActionInvocation invocation = new MockActionInvocation();
|
||||
@@ -420,6 +430,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
return accepted;
|
||||
}
|
||||
};
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setCookiesName("*");
|
||||
|
||||
MockActionInvocation invocation = new MockActionInvocation();
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.opensymphony.xwork2.config.entities.ResultConfig;
|
||||
import com.opensymphony.xwork2.inject.ContainerBuilder;
|
||||
import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
|
||||
import com.opensymphony.xwork2.mock.MockResult;
|
||||
import com.opensymphony.xwork2.ognl.OgnlUtil;
|
||||
import com.opensymphony.xwork2.util.location.LocatableProperties;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
@@ -222,6 +223,7 @@ public class ExecuteAndWaitInterceptorTest extends StrutsInternalTestCase {
|
||||
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
|
||||
builder.factory(ObjectFactory.class);
|
||||
builder.factory(ActionProxyFactory.class, DefaultActionProxyFactory.class);
|
||||
builder.factory(OgnlUtil.class, OgnlUtil.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -180,11 +180,13 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
pageContext.setRequest(request);
|
||||
|
||||
// test
|
||||
{PropertyTag tag = new PropertyTag();
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("%{toString()}");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();}
|
||||
{
|
||||
PropertyTag tag = new PropertyTag();
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("%{formatTitle()}");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
}
|
||||
|
||||
// verify test
|
||||
request.verify();
|
||||
@@ -212,7 +214,7 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
tag.setEscape(false);
|
||||
tag.setEscapeJavaScript(true);
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("%{toString()}");
|
||||
tag.setValue("%{formatTitle()}");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();}
|
||||
|
||||
@@ -242,7 +244,7 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
tag.setEscape(false);
|
||||
tag.setEscapeXml(true);
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("%{toString()}");
|
||||
tag.setValue("%{formatTitle()}");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();}
|
||||
|
||||
@@ -272,7 +274,7 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
tag.setEscape(false);
|
||||
tag.setEscapeCsv(true);
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("%{toString()}");
|
||||
tag.setValue("%{formatTitle()}");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();}
|
||||
|
||||
@@ -300,7 +302,7 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
// test
|
||||
{PropertyTag tag = new PropertyTag();
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("toString()");
|
||||
tag.setValue("formatTitle()");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();}
|
||||
|
||||
@@ -328,7 +330,7 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
// test
|
||||
{PropertyTag tag = new PropertyTag();
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("toString()");
|
||||
tag.setValue("formatTitle()");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();}
|
||||
|
||||
@@ -356,7 +358,7 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
// test
|
||||
{PropertyTag tag = new PropertyTag();
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setValue("%{toString()}");
|
||||
tag.setValue("%{formatTitle()}");
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();}
|
||||
|
||||
@@ -385,8 +387,12 @@ public class PropertyTagTest extends StrutsInternalTestCase {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
public String formatTitle() {
|
||||
return "Foo is: " + title;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return formatTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@ public class SelectTest extends AbstractUITagTest {
|
||||
tag.setList("list2");
|
||||
tag.setListKey("id");
|
||||
tag.setListValue("name");
|
||||
tag.setValue("fooInt.toString()");
|
||||
tag.setValue("fooInt");
|
||||
|
||||
// header stuff
|
||||
tag.setHeaderKey("headerKey");
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.opensymphony.xwork2.config.entities.InterceptorMapping;
|
||||
import com.opensymphony.xwork2.config.entities.ResultConfig;
|
||||
import com.opensymphony.xwork2.mock.MockActionProxy;
|
||||
import com.opensymphony.xwork2.mock.MockInterceptor;
|
||||
import com.opensymphony.xwork2.ognl.OgnlUtil;
|
||||
import com.opensymphony.xwork2.util.XWorkTestCaseHelper;
|
||||
import junit.framework.TestCase;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
@@ -228,6 +229,7 @@ public class RestActionInvocationTest extends TestCase {
|
||||
|
||||
request.setMethod("GET");
|
||||
|
||||
restActionInvocation.setOgnlUtil(new OgnlUtil());
|
||||
restActionInvocation.invoke();
|
||||
|
||||
assertEquals(123, response.getStatus());
|
||||
|
||||
@@ -22,14 +22,14 @@ import com.opensymphony.xwork2.config.entities.ResultConfig;
|
||||
import com.opensymphony.xwork2.inject.Container;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.interceptor.PreResultListener;
|
||||
import com.opensymphony.xwork2.ognl.OgnlUtil;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import com.opensymphony.xwork2.util.ValueStackFactory;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
|
||||
import ognl.OgnlException;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -46,18 +46,8 @@ import java.util.Map;
|
||||
*/
|
||||
public class DefaultActionInvocation implements ActionInvocation {
|
||||
|
||||
private static final long serialVersionUID = -585293628862447329L;
|
||||
|
||||
//static {
|
||||
// if (ObjectFactory.getContinuationPackage() != null) {
|
||||
// continuationHandler = new ContinuationHandler();
|
||||
// }
|
||||
//}
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultActionInvocation.class);
|
||||
|
||||
private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
|
||||
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
|
||||
|
||||
protected Object action;
|
||||
protected ActionProxy proxy;
|
||||
protected List<PreResultListener> preResultListeners;
|
||||
@@ -75,6 +65,7 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
protected ValueStackFactory valueStackFactory;
|
||||
protected Container container;
|
||||
protected UnknownHandlerManager unknownHandlerManager;
|
||||
protected OgnlUtil ognlUtil;
|
||||
|
||||
public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) {
|
||||
this.extraContext = extraContext;
|
||||
@@ -106,6 +97,11 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
this.actionEventListener = listener;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setOgnlUtil(OgnlUtil ognlUtil) {
|
||||
this.ognlUtil = ognlUtil;
|
||||
}
|
||||
|
||||
public Object getAction() {
|
||||
return action;
|
||||
}
|
||||
@@ -420,22 +416,19 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
try {
|
||||
UtilTimerStack.push(timerKey);
|
||||
|
||||
boolean methodCalled = false;
|
||||
Object methodResult = null;
|
||||
Method method = null;
|
||||
Object methodResult;
|
||||
try {
|
||||
method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY);
|
||||
} catch (NoSuchMethodException e) {
|
||||
methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action);
|
||||
} catch (OgnlException e) {
|
||||
// hmm -- OK, try doXxx instead
|
||||
try {
|
||||
String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
|
||||
method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY);
|
||||
} catch (NoSuchMethodException e1) {
|
||||
String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()";
|
||||
methodResult = ognlUtil.getValue(altMethodName, ActionContext.getContext().getContextMap(), action);
|
||||
} catch (OgnlException e1) {
|
||||
// well, give the unknown handler a shot
|
||||
if (unknownHandlerManager.hasUnknownHandlers()) {
|
||||
try {
|
||||
methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
|
||||
methodCalled = true;
|
||||
} catch (NoSuchMethodException e2) {
|
||||
// throw the original one
|
||||
throw e;
|
||||
@@ -445,29 +438,18 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!methodCalled) {
|
||||
methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);
|
||||
}
|
||||
|
||||
return saveResult(actionConfig, methodResult);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
|
||||
} catch (InvocationTargetException e) {
|
||||
} catch (OgnlException e) {
|
||||
// We try to return the source exception.
|
||||
Throwable t = e.getTargetException();
|
||||
//Throwable t = e.getTargetException();
|
||||
|
||||
if (actionEventListener != null) {
|
||||
String result = actionEventListener.handleException(t, getStack());
|
||||
String result = actionEventListener.handleException(e, getStack());
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (t instanceof Exception) {
|
||||
throw (Exception) t;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
UtilTimerStack.pop(timerKey);
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.opensymphony.xwork2;
|
||||
|
||||
/**
|
||||
* ExcludedPatterns contains hard-coded patterns that must be rejected by {@link com.opensymphony.xwork2.interceptor.ParametersInterceptor}
|
||||
* and partially in CookInterceptor
|
||||
*/
|
||||
public class ExcludedPatterns {
|
||||
|
||||
public static final String CLASS_ACCESS_PATTERN = "(.*\\.|^|.*|\\[('|\"))class(\\.|('|\")]|\\[).*";
|
||||
|
||||
public static final String[] EXCLUDED_PATTERNS = {
|
||||
CLASS_ACCESS_PATTERN,
|
||||
"^dojo\\..*",
|
||||
"^struts\\..*",
|
||||
"^session\\..*",
|
||||
"^request\\..*",
|
||||
"^application\\..*",
|
||||
"^servlet(Request|Response)\\..*",
|
||||
"^parameters\\..*"
|
||||
};
|
||||
|
||||
}
|
||||
@@ -17,4 +17,14 @@ public final class XWorkConstants {
|
||||
public static final String RELOAD_XML_CONFIGURATION = "reloadXmlConfiguration";
|
||||
public static final String ALLOW_STATIC_METHOD_ACCESS = "allowStaticMethodAccess";
|
||||
public static final String XWORK_LOGGER_FACTORY = "xwork.loggerFactory";
|
||||
|
||||
public static final String OGNL_EXCLUDED_CLASSES = "ognlExcludedClasses";
|
||||
public static final String OGNL_EXCLUDED_PACKAGE_NAME_PATTERNS = "ognlExcludedPackageNamePatterns";
|
||||
|
||||
public static final String ADDITIONAL_EXCLUDED_PATTERNS = "additionalExcludedPatterns";
|
||||
public static final String ADDITIONAL_ACCEPTED_PATTERNS = "additionalAcceptedPatterns";
|
||||
|
||||
public static final String OVERRIDE_EXCLUDED_PATTERNS = "overrideExcludedPatterns";
|
||||
public static final String OVERRIDE_ACCEPTED_PATTERNS = "overrideAcceptedPatterns";
|
||||
|
||||
}
|
||||
|
||||
+10
-1
@@ -2,9 +2,13 @@ package com.opensymphony.xwork2.config.providers;
|
||||
|
||||
import com.opensymphony.xwork2.ActionProxyFactory;
|
||||
import com.opensymphony.xwork2.DefaultActionProxyFactory;
|
||||
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.DefaultLocaleProvider;
|
||||
import com.opensymphony.xwork2.DefaultTextProvider;
|
||||
import com.opensymphony.xwork2.DefaultUnknownHandlerManager;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.FileManager;
|
||||
import com.opensymphony.xwork2.FileManagerFactory;
|
||||
import com.opensymphony.xwork2.LocaleProvider;
|
||||
@@ -168,7 +172,12 @@ public class XWorkConfigurationProvider implements ConfigurationProvider {
|
||||
.factory(ArrayConverter.class, Scope.SINGLETON)
|
||||
.factory(DateConverter.class, Scope.SINGLETON)
|
||||
.factory(NumberConverter.class, Scope.SINGLETON)
|
||||
.factory(StringConverter.class, Scope.SINGLETON);
|
||||
.factory(StringConverter.class, Scope.SINGLETON)
|
||||
|
||||
.factory(ExcludedPatternsChecker.class, DefaultExcludedPatternsChecker.class, Scope.DEFAULT)
|
||||
.factory(AcceptedPatternsChecker.class, DefaultAcceptedPatternsChecker.class, Scope.DEFAULT)
|
||||
;
|
||||
|
||||
props.setProperty(XWorkConstants.DEV_MODE, Boolean.FALSE.toString());
|
||||
props.setProperty(XWorkConstants.LOG_MISSING_PROPERTIES, Boolean.FALSE.toString());
|
||||
props.setProperty(XWorkConstants.ENABLE_OGNL_EXPRESSION_CACHE, Boolean.TRUE.toString());
|
||||
|
||||
+63
-75
@@ -17,7 +17,8 @@ package com.opensymphony.xwork2.interceptor;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.ExcludedPatterns;
|
||||
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.ValidationAware;
|
||||
import com.opensymphony.xwork2.XWorkConstants;
|
||||
import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler;
|
||||
@@ -142,26 +143,17 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ParametersInterceptor.class);
|
||||
|
||||
public static final String ACCEPTED_PARAM_NAMES = "\\w+((\\.\\w+)|(\\[\\d+\\])|(\\(\\d+\\))|(\\['(\\w|[\\u4e00-\\u9fa5])+'\\])|(\\('(\\w|[\\u4e00-\\u9fa5])+'\\)))*";
|
||||
|
||||
protected static final int PARAM_NAME_MAX_LENGTH = 100;
|
||||
|
||||
private ExcludedPatternsChecker excludedPatterns;
|
||||
|
||||
private int paramNameMaxLength = PARAM_NAME_MAX_LENGTH;
|
||||
|
||||
protected boolean ordered = false;
|
||||
protected Set<Pattern> excludeParams;
|
||||
protected Set<Pattern> acceptParams = Collections.emptySet();
|
||||
|
||||
private boolean devMode = false;
|
||||
|
||||
// Allowed names of parameters
|
||||
private Pattern acceptedPattern = Pattern.compile(ACCEPTED_PARAM_NAMES, Pattern.CASE_INSENSITIVE);
|
||||
protected boolean ordered = false;
|
||||
|
||||
private ValueStackFactory valueStackFactory;
|
||||
|
||||
public ParametersInterceptor() {
|
||||
initializeHardCodedExcludePatterns();
|
||||
}
|
||||
private AcceptedPatternsChecker acceptedPatterns;
|
||||
|
||||
@Inject
|
||||
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
|
||||
@@ -173,23 +165,14 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
devMode = "true".equalsIgnoreCase(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comma-delimited list of regular expressions to match
|
||||
* parameters that are allowed in the parameter map (aka whitelist).
|
||||
* <p/>
|
||||
* Don't change the default unless you know what you are doing in terms
|
||||
* of security implications.
|
||||
*
|
||||
* @param commaDelim A comma-delimited list of regular expressions
|
||||
*/
|
||||
public void setAcceptParamNames(String commaDelim) {
|
||||
Collection<String> acceptPatterns = ArrayUtils.asCollection(commaDelim);
|
||||
if (acceptPatterns != null) {
|
||||
acceptParams = new HashSet<Pattern>();
|
||||
for (String pattern : acceptPatterns) {
|
||||
acceptParams.add(Pattern.compile(pattern));
|
||||
}
|
||||
}
|
||||
@Inject
|
||||
public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
|
||||
this.excludedPatterns = excludedPatterns;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
|
||||
this.acceptedPatterns = acceptedPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,7 +273,8 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
if (isAcceptableParameter(name, action)) {
|
||||
Object value = entry.getValue();
|
||||
if (isAcceptableParameter(name, action) && isAcceptableValue(value)) {
|
||||
acceptableParameters.put(name, entry.getValue());
|
||||
}
|
||||
}
|
||||
@@ -315,8 +299,8 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
//block or allow access to properties
|
||||
//see WW-2761 for more details
|
||||
MemberAccessValueStack accessValueStack = (MemberAccessValueStack) newStack;
|
||||
accessValueStack.setAcceptProperties(acceptParams);
|
||||
accessValueStack.setExcludeProperties(excludeParams);
|
||||
accessValueStack.setAcceptProperties(acceptedPatterns.getAcceptedPatterns());
|
||||
accessValueStack.setExcludeProperties(excludedPatterns.getExcludedPatterns());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : acceptableParameters.entrySet()) {
|
||||
@@ -365,6 +349,33 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
return acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given value doesn't match global excluded patterns to avoid passing malicious code
|
||||
*
|
||||
* @param value incoming parameter's value
|
||||
* @return true if value is safe
|
||||
*
|
||||
* FIXME: can be removed when parameters won't be represented as simple Strings
|
||||
*/
|
||||
protected boolean isAcceptableValue(Object value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
Object[] values;
|
||||
if (value.getClass().isArray()) {
|
||||
values = (Object[]) value;
|
||||
} else {
|
||||
values = new Object[] { value };
|
||||
}
|
||||
boolean result = true;
|
||||
for (Object obj : values) {
|
||||
if (isExcluded(obj.toString())) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of the comparator to use for the ordered sorting. Override this
|
||||
* method to customize the ordering of the parameters as they are set to the
|
||||
@@ -422,33 +433,19 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
}
|
||||
|
||||
protected boolean isAccepted(String paramName) {
|
||||
if (!this.acceptParams.isEmpty()) {
|
||||
for (Pattern pattern : acceptParams) {
|
||||
Matcher matcher = pattern.matcher(paramName);
|
||||
if (matcher.matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
notifyDeveloper("Parameter [#0] didn't match acceptParams list of patterns!", paramName);
|
||||
return false;
|
||||
} else {
|
||||
boolean matches = acceptedPattern.matcher(paramName).matches();
|
||||
if (!matches) {
|
||||
notifyDeveloper("Parameter [#0] didn't match acceptedPattern pattern!", paramName);
|
||||
}
|
||||
return matches;
|
||||
AcceptedPatternsChecker.IsAccepted result = acceptedPatterns.isAccepted(paramName);
|
||||
if (result.isAccepted()) {
|
||||
return true;
|
||||
}
|
||||
notifyDeveloper("Parameter [#0] didn't match accepted pattern [#1]!", paramName, String.valueOf(result.getAcceptedPattern()));
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isExcluded(String paramName) {
|
||||
if (!this.excludeParams.isEmpty()) {
|
||||
for (Pattern pattern : excludeParams) {
|
||||
Matcher matcher = pattern.matcher(paramName);
|
||||
if (matcher.matches()) {
|
||||
notifyDeveloper("Parameter [#0] is on the excludeParams list of patterns!", paramName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
|
||||
if (result.isExcluded()) {
|
||||
notifyDeveloper("Parameter [#0] matches excluded pattern [#1]!", paramName, String.valueOf(result.getExcludedPattern()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -482,20 +479,16 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a set of regular expressions of parameters to remove
|
||||
* from the parameter map
|
||||
* Sets a comma-delimited list of regular expressions to match
|
||||
* parameters that are allowed in the parameter map (aka whitelist).
|
||||
* <p/>
|
||||
* Don't change the default unless you know what you are doing in terms
|
||||
* of security implications.
|
||||
*
|
||||
* @return A set of compiled regular expression patterns
|
||||
* @param commaDelim A comma-delimited list of regular expressions
|
||||
*/
|
||||
protected Set getExcludeParamsSet() {
|
||||
return excludeParams;
|
||||
}
|
||||
|
||||
protected void initializeHardCodedExcludePatterns() {
|
||||
excludeParams = new HashSet<Pattern>();
|
||||
for (String pattern : ExcludedPatterns.EXCLUDED_PATTERNS) {
|
||||
excludeParams.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
public void setAcceptParamNames(String commaDelim) {
|
||||
acceptedPatterns.addAcceptedPatterns(commaDelim);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,12 +498,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
* @param commaDelim A comma-delimited list of regular expressions
|
||||
*/
|
||||
public void setExcludeParams(String commaDelim) {
|
||||
Collection<String> excludePatterns = ArrayUtils.asCollection(commaDelim);
|
||||
if (excludePatterns != null) {
|
||||
for (String pattern : excludePatterns) {
|
||||
excludeParams.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
excludedPatterns.addExcludedPatterns(commaDelim);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,12 +16,17 @@
|
||||
package com.opensymphony.xwork2.ognl;
|
||||
|
||||
import com.opensymphony.xwork2.XWorkConstants;
|
||||
import com.opensymphony.xwork2.config.ConfigurationException;
|
||||
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
|
||||
import com.opensymphony.xwork2.inject.Container;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
|
||||
import com.opensymphony.xwork2.util.CompoundRoot;
|
||||
import com.opensymphony.xwork2.util.TextParseUtil;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import com.opensymphony.xwork2.util.reflection.ReflectionException;
|
||||
import ognl.ClassResolver;
|
||||
import ognl.Ognl;
|
||||
import ognl.OgnlContext;
|
||||
import ognl.OgnlException;
|
||||
@@ -36,9 +41,12 @@ import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
@@ -58,6 +66,12 @@ public class OgnlUtil {
|
||||
private boolean enableExpressionCache = true;
|
||||
private boolean enableEvalExpression;
|
||||
|
||||
private Set<Class<?>> excludedClasses = new HashSet<Class<?>>();
|
||||
private Set<Pattern> excludedPackageNamePatterns = new HashSet<Pattern>();
|
||||
|
||||
private Container container;
|
||||
private boolean allowStaticMethodAccess;
|
||||
|
||||
@Inject
|
||||
public void setXWorkConverter(XWorkConverter conv) {
|
||||
this.defaultConverter = new OgnlTypeConverterWrapper(conv);
|
||||
@@ -82,6 +96,44 @@ public class OgnlUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(value = XWorkConstants.OGNL_EXCLUDED_CLASSES, required = false)
|
||||
public void setExcludedClasses(String commaDelimitedClasses) {
|
||||
Set<String> classes = TextParseUtil.commaDelimitedStringToSet(commaDelimitedClasses);
|
||||
for (String className : classes) {
|
||||
try {
|
||||
excludedClasses.add(Class.forName(className));
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ConfigurationException("Cannot load excluded class: " + className, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(value = XWorkConstants.OGNL_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
|
||||
public void setExcludedPackageName(String commaDelimitedPackagePatterns) {
|
||||
Set<String> packagePatterns = TextParseUtil.commaDelimitedStringToSet(commaDelimitedPackagePatterns);
|
||||
for (String pattern : packagePatterns) {
|
||||
excludedPackageNamePatterns.add(Pattern.compile(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Class<?>> getExcludedClasses() {
|
||||
return excludedClasses;
|
||||
}
|
||||
|
||||
public Set<Pattern> getExcludedPackageNamePatterns() {
|
||||
return excludedPackageNamePatterns;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setContainer(Container container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
@Inject(value = XWorkConstants.ALLOW_STATIC_METHOD_ACCESS, required = false)
|
||||
public void setAllowStaticMethodAccess(String allowStaticMethodAccess) {
|
||||
this.allowStaticMethodAccess = Boolean.parseBoolean(allowStaticMethodAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object's properties using the default type converter, defaulting to not throw
|
||||
* exceptions for problems setting the properties.
|
||||
@@ -141,7 +193,7 @@ public class OgnlUtil {
|
||||
* problems setting the properties
|
||||
*/
|
||||
public void setProperties(Map<String, ?> properties, Object o, boolean throwPropertyExceptions) {
|
||||
Map context = Ognl.createDefaultContext(o);
|
||||
Map context = createDefaultContext(o, null);
|
||||
setProperties(properties, o, context, throwPropertyExceptions);
|
||||
}
|
||||
|
||||
@@ -329,9 +381,9 @@ public class OgnlUtil {
|
||||
}
|
||||
|
||||
TypeConverter conv = getTypeConverterFromContext(context);
|
||||
final Map contextFrom = Ognl.createDefaultContext(from);
|
||||
final Map contextFrom = createDefaultContext(from, null);
|
||||
Ognl.setTypeConverter(contextFrom, conv);
|
||||
final Map contextTo = Ognl.createDefaultContext(to);
|
||||
final Map contextTo = createDefaultContext(to, null);
|
||||
Ognl.setTypeConverter(contextTo, conv);
|
||||
|
||||
PropertyDescriptor[] fromPds;
|
||||
@@ -440,7 +492,7 @@ public class OgnlUtil {
|
||||
*/
|
||||
public Map<String, Object> getBeanMap(final Object source) throws IntrospectionException, OgnlException {
|
||||
Map<String, Object> beanMap = new HashMap<String, Object>();
|
||||
final Map sourceMap = Ognl.createDefaultContext(source);
|
||||
final Map sourceMap = createDefaultContext(source, null);
|
||||
PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(source);
|
||||
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
|
||||
final String propertyName = propertyDescriptor.getDisplayName();
|
||||
@@ -518,6 +570,23 @@ public class OgnlUtil {
|
||||
return defaultConverter;
|
||||
}
|
||||
|
||||
protected Map createDefaultContext(Object root) {
|
||||
return createDefaultContext(root, null);
|
||||
}
|
||||
|
||||
protected Map createDefaultContext(Object root, ClassResolver classResolver) {
|
||||
ClassResolver resolver = classResolver;
|
||||
if (resolver == null) {
|
||||
resolver = container.getInstance(CompoundRootAccessor.class);
|
||||
}
|
||||
|
||||
SecurityMemberAccess memberAccess = new SecurityMemberAccess(allowStaticMethodAccess);
|
||||
memberAccess.setExcludedClasses(excludedClasses);
|
||||
memberAccess.setExcludedPackageNamePatterns(excludedPackageNamePatterns);
|
||||
|
||||
return Ognl.createDefaultContext(root, resolver, defaultConverter, memberAccess);
|
||||
}
|
||||
|
||||
private interface OgnlTask<T> {
|
||||
T execute(Object tree) throws OgnlException;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
|
||||
@Inject
|
||||
public void setOgnlUtil(OgnlUtil ognlUtil) {
|
||||
this.ognlUtil = ognlUtil;
|
||||
securityMemberAccess.setExcludedClasses(ognlUtil.getExcludedClasses());
|
||||
securityMemberAccess.setExcludedPackageNamePatterns(ognlUtil.getExcludedPackageNamePatterns());
|
||||
}
|
||||
|
||||
protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot,
|
||||
@@ -197,7 +199,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
|
||||
throw new XWorkException(message, re);
|
||||
} else {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Error setting value", re);
|
||||
LOG.warn("Error setting value [#0] with expression [#1]", re, value.toString(), expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,7 +448,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
|
||||
XWorkConverter xworkConverter = cont.getInstance(XWorkConverter.class);
|
||||
CompoundRootAccessor accessor = (CompoundRootAccessor) cont.getInstance(PropertyAccessor.class, CompoundRoot.class.getName());
|
||||
TextProvider prov = cont.getInstance(TextProvider.class, "system");
|
||||
boolean allow = "true".equals(cont.getInstance(String.class, "allowStaticMethodAccess"));
|
||||
boolean allow = "true".equals(cont.getInstance(String.class, XWorkConstants.ALLOW_STATIC_METHOD_ACCESS));
|
||||
OgnlValueStack aStack = new OgnlValueStack(xworkConverter, accessor, prov, allow);
|
||||
aStack.setOgnlUtil(cont.getInstance(OgnlUtil.class));
|
||||
aStack.setRoot(xworkConverter, accessor, this.root, allow);
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.opensymphony.xwork2.ognl;
|
||||
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import ognl.DefaultMemberAccess;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
@@ -32,9 +34,13 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class SecurityMemberAccess extends DefaultMemberAccess {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SecurityMemberAccess.class);
|
||||
|
||||
private final boolean allowStaticMethodAccess;
|
||||
private Set<Pattern> excludeProperties = Collections.emptySet();
|
||||
private Set<Pattern> acceptProperties = Collections.emptySet();
|
||||
private Set<Class<?>> excludedClasses = Collections.emptySet();
|
||||
private Set<Pattern> excludedPackageNamePatterns = Collections.emptySet();
|
||||
|
||||
public SecurityMemberAccess(boolean method) {
|
||||
super(false);
|
||||
@@ -46,8 +52,20 @@ public class SecurityMemberAccess extends DefaultMemberAccess {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessible(Map context, Object target, Member member,
|
||||
String propertyName) {
|
||||
public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
|
||||
if (isPackageExcluded(target.getClass().getPackage(), member.getDeclaringClass().getPackage())) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Package of target [#0] or package of member [#1] are excluded!", target, member);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isClassExcluded(target.getClass(), member.getDeclaringClass())) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Target class [#0] or declaring class of member type [#1] are excluded!", target, member);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean allow = true;
|
||||
int modifiers = member.getModifiers();
|
||||
@@ -74,6 +92,27 @@ public class SecurityMemberAccess extends DefaultMemberAccess {
|
||||
return isAcceptableProperty(propertyName);
|
||||
}
|
||||
|
||||
protected boolean isPackageExcluded(Package targetPackage, Package memberPackage) {
|
||||
for (Pattern pattern : excludedPackageNamePatterns) {
|
||||
if (pattern.matcher(targetPackage.getName()).matches() || pattern.matcher(memberPackage.getName()).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isClassExcluded(Class<?> targetClass, Class<?> declaringClass) {
|
||||
if (targetClass == Object.class || declaringClass == Object.class) {
|
||||
return true;
|
||||
}
|
||||
for (Class<?> excludedClass : excludedClasses) {
|
||||
if (targetClass.isAssignableFrom(excludedClass) || declaringClass.isAssignableFrom(excludedClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isAcceptableProperty(String name) {
|
||||
return name == null || ((!isExcluded(name)) && isAccepted(name));
|
||||
}
|
||||
@@ -115,4 +154,11 @@ public class SecurityMemberAccess extends DefaultMemberAccess {
|
||||
this.acceptProperties = acceptedProperties;
|
||||
}
|
||||
|
||||
public void setExcludedClasses(Set<Class<?>> excludedClasses) {
|
||||
this.excludedClasses = excludedClasses;
|
||||
}
|
||||
|
||||
public void setExcludedPackageNamePatterns(Set<Pattern> excludedPackageNamePatterns) {
|
||||
this.excludedPackageNamePatterns = excludedPackageNamePatterns;
|
||||
}
|
||||
}
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.opensymphony.xwork2.security;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Used across different interceptors to check if given string matches one of the excluded patterns.
|
||||
*/
|
||||
public interface AcceptedPatternsChecker {
|
||||
|
||||
/**
|
||||
* Checks if value matches any of patterns on exclude list
|
||||
*
|
||||
* @param value to check
|
||||
* @return object containing result of matched pattern and pattern itself
|
||||
*/
|
||||
public IsAccepted isAccepted(String value);
|
||||
|
||||
/**
|
||||
* Allows add additional excluded patterns during runtime
|
||||
*
|
||||
* @param commaDelimitedPatterns comma delimited string with patterns
|
||||
*/
|
||||
public void addAcceptedPatterns(String commaDelimitedPatterns);
|
||||
|
||||
/**
|
||||
* Allows add additional excluded patterns during runtime
|
||||
*
|
||||
* @param additionalPatterns array of additional excluded patterns
|
||||
*/
|
||||
public void addAcceptedPatterns(String[] additionalPatterns);
|
||||
|
||||
/**
|
||||
* Allows add additional excluded patterns during runtime
|
||||
*
|
||||
* @param additionalPatterns set of additional patterns
|
||||
*/
|
||||
public void addAcceptedPatterns(Set<String> additionalPatterns);
|
||||
|
||||
/**
|
||||
* Allow access list of all defined excluded patterns
|
||||
*
|
||||
* @return set of excluded patterns
|
||||
*/
|
||||
public Set<Pattern> getAcceptedPatterns();
|
||||
|
||||
public final static class IsAccepted {
|
||||
|
||||
private final boolean accepted;
|
||||
private final Pattern acceptedPattern;
|
||||
|
||||
public static IsAccepted yes(Pattern acceptedPattern) {
|
||||
return new IsAccepted(true, acceptedPattern);
|
||||
}
|
||||
|
||||
public static IsAccepted no() {
|
||||
return new IsAccepted(false, null);
|
||||
}
|
||||
|
||||
private IsAccepted(boolean accepted, Pattern acceptedPattern) {
|
||||
this.accepted = accepted;
|
||||
this.acceptedPattern = acceptedPattern;
|
||||
}
|
||||
|
||||
public boolean isAccepted() {
|
||||
return accepted;
|
||||
}
|
||||
|
||||
public Pattern getAcceptedPattern() {
|
||||
return acceptedPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IsAccepted {" +
|
||||
"accepted=" + accepted +
|
||||
", acceptedPattern=" + acceptedPattern +
|
||||
" }";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package com.opensymphony.xwork2.security;
|
||||
|
||||
import com.opensymphony.xwork2.XWorkConstants;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.TextParseUtil;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DefaultAcceptedPatternsChecker implements AcceptedPatternsChecker {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultAcceptedPatternsChecker.class);
|
||||
|
||||
public static final String[] ACCEPTED_PATTERNS = {
|
||||
"\\w+((\\.\\w+)|(\\[\\d+\\])|(\\(\\d+\\))|(\\['(\\w|[\\u4e00-\\u9fa5])+'\\])|(\\('(\\w|[\\u4e00-\\u9fa5])+'\\)))*"
|
||||
};
|
||||
|
||||
private Set<Pattern> acceptedPatterns;
|
||||
|
||||
public DefaultAcceptedPatternsChecker() {
|
||||
acceptedPatterns = new HashSet<Pattern>();
|
||||
for (String pattern : ACCEPTED_PATTERNS) {
|
||||
acceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(value = XWorkConstants.OVERRIDE_ACCEPTED_PATTERNS, required = false)
|
||||
public void setOverrideAcceptedPatterns(String acceptablePatterns) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Overriding [#0] with [#1], be aware that this can affect safety of your application!",
|
||||
XWorkConstants.OVERRIDE_ACCEPTED_PATTERNS, acceptablePatterns);
|
||||
}
|
||||
acceptedPatterns = new HashSet<Pattern>();
|
||||
for (String pattern : TextParseUtil.commaDelimitedStringToSet(acceptablePatterns)) {
|
||||
acceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(value = XWorkConstants.ADDITIONAL_ACCEPTED_PATTERNS, required = false)
|
||||
public void setAdditionalAcceptedPatterns(String acceptablePatterns) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.warn("Adding additional patterns [#0] to accepted patterns!", acceptablePatterns);
|
||||
}
|
||||
for (String pattern : TextParseUtil.commaDelimitedStringToSet(acceptablePatterns)) {
|
||||
acceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
|
||||
public void addAcceptedPatterns(String commaDelimitedPatterns) {
|
||||
addAcceptedPatterns(TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns));
|
||||
}
|
||||
|
||||
public void addAcceptedPatterns(String[] additionalPatterns) {
|
||||
addAcceptedPatterns(new HashSet<String>(Arrays.asList(additionalPatterns)));
|
||||
}
|
||||
|
||||
public void addAcceptedPatterns(Set<String> additionalPatterns) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Adding additional excluded patterns [#0]", additionalPatterns);
|
||||
}
|
||||
for (String pattern : additionalPatterns) {
|
||||
acceptedPatterns.add(Pattern.compile(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
public IsAccepted isAccepted(String value) {
|
||||
for (Pattern acceptedPattern : acceptedPatterns) {
|
||||
if (acceptedPattern.matcher(value).matches()) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("[#0] matches accepted pattern [#1]", value, acceptedPattern);
|
||||
}
|
||||
return IsAccepted.yes(acceptedPattern);
|
||||
}
|
||||
}
|
||||
return IsAccepted.no();
|
||||
}
|
||||
|
||||
public Set<Pattern> getAcceptedPatterns() {
|
||||
return acceptedPatterns;
|
||||
}
|
||||
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package com.opensymphony.xwork2.security;
|
||||
|
||||
import com.opensymphony.xwork2.*;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.TextParseUtil;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DefaultExcludedPatternsChecker implements ExcludedPatternsChecker {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultExcludedPatternsChecker.class);
|
||||
|
||||
public static final String[] EXCLUDED_PATTERNS = {
|
||||
"(.*\\.|^|.*|\\[('|\"))class(\\.|('|\")]|\\[).*",
|
||||
"(^|.*#)dojo(\\.|\\[).*",
|
||||
"(^|.*#)struts(\\.|\\[).*",
|
||||
"(^|.*#)session(\\.|\\[).*",
|
||||
"(^|.*#)request(\\.|\\[).*",
|
||||
"(^|.*#)application(\\.|\\[).*",
|
||||
"(^|.*#)servlet(Request|Response)(\\.|\\[).*",
|
||||
"(^|.*#)parameters(\\.|\\[).*",
|
||||
"(^|.*#)context(\\.|\\[).*",
|
||||
"(^|.*#)_memberAccess(\\.|\\[).*"
|
||||
};
|
||||
|
||||
private Set<Pattern> excludedPatterns;
|
||||
|
||||
public DefaultExcludedPatternsChecker() {
|
||||
excludedPatterns = new HashSet<Pattern>();
|
||||
for (String pattern : EXCLUDED_PATTERNS) {
|
||||
excludedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(value = XWorkConstants.OVERRIDE_EXCLUDED_PATTERNS, required = false)
|
||||
public void setOverrideExcludePatterns(String excludePatterns) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Overriding [#0] with [#1], be aware that this can affect safety of your application!",
|
||||
XWorkConstants.OVERRIDE_EXCLUDED_PATTERNS, excludePatterns);
|
||||
}
|
||||
excludedPatterns = new HashSet<Pattern>();
|
||||
for (String pattern : TextParseUtil.commaDelimitedStringToSet(excludePatterns)) {
|
||||
excludedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(value = XWorkConstants.ADDITIONAL_EXCLUDED_PATTERNS, required = false)
|
||||
public void setAdditionalExcludePatterns(String excludePatterns) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Adding additional patterns [#0] to excluded patterns!", excludePatterns);
|
||||
}
|
||||
for (String pattern : TextParseUtil.commaDelimitedStringToSet(excludePatterns)) {
|
||||
excludedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
|
||||
public void addExcludedPatterns(String commaDelimitedPatterns) {
|
||||
addExcludedPatterns(TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns));
|
||||
}
|
||||
|
||||
public void addExcludedPatterns(String[] additionalPatterns) {
|
||||
addExcludedPatterns(new HashSet<String>(Arrays.asList(additionalPatterns)));
|
||||
}
|
||||
|
||||
public void addExcludedPatterns(Set<String> additionalPatterns) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Adding additional excluded patterns [#0]", additionalPatterns);
|
||||
}
|
||||
for (String pattern : additionalPatterns) {
|
||||
excludedPatterns.add(Pattern.compile(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
public IsExcluded isExcluded(String value) {
|
||||
for (Pattern excludedPattern : excludedPatterns) {
|
||||
if (excludedPattern.matcher(value).matches()) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("[#0] matches excluded pattern [#1]", value, excludedPattern);
|
||||
}
|
||||
return IsExcluded.yes(excludedPattern);
|
||||
}
|
||||
}
|
||||
return IsExcluded.no();
|
||||
}
|
||||
|
||||
public Set<Pattern> getExcludedPatterns() {
|
||||
return excludedPatterns;
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.opensymphony.xwork2.security;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Used across different interceptors to check if given string matches one of the excluded patterns.
|
||||
*/
|
||||
public interface ExcludedPatternsChecker {
|
||||
|
||||
/**
|
||||
* Checks if value matches any of patterns on exclude list
|
||||
*
|
||||
* @param value to check
|
||||
* @return object containing result of matched pattern and pattern itself
|
||||
*/
|
||||
public IsExcluded isExcluded(String value);
|
||||
|
||||
/**
|
||||
* Allows add additional excluded patterns during runtime
|
||||
*
|
||||
* @param commaDelimitedPatterns comma delimited string with patterns
|
||||
*/
|
||||
public void addExcludedPatterns(String commaDelimitedPatterns);
|
||||
|
||||
/**
|
||||
* Allows add additional excluded patterns during runtime
|
||||
*
|
||||
* @param additionalPatterns array of additional excluded patterns
|
||||
*/
|
||||
public void addExcludedPatterns(String[] additionalPatterns);
|
||||
|
||||
/**
|
||||
* Allows add additional excluded patterns during runtime
|
||||
*
|
||||
* @param additionalPatterns set of additional patterns
|
||||
*/
|
||||
public void addExcludedPatterns(Set<String> additionalPatterns);
|
||||
|
||||
/**
|
||||
* Allow access list of all defined excluded patterns
|
||||
*
|
||||
* @return set of excluded patterns
|
||||
*/
|
||||
public Set<Pattern> getExcludedPatterns();
|
||||
|
||||
public final static class IsExcluded {
|
||||
|
||||
private final boolean excluded;
|
||||
private final Pattern excludedPattern;
|
||||
|
||||
public static IsExcluded yes(Pattern excludedPattern) {
|
||||
return new IsExcluded(true, excludedPattern);
|
||||
}
|
||||
|
||||
public static IsExcluded no() {
|
||||
return new IsExcluded(false, null);
|
||||
}
|
||||
|
||||
private IsExcluded(boolean excluded, Pattern excludedPattern) {
|
||||
this.excluded = excluded;
|
||||
this.excludedPattern = excludedPattern;
|
||||
}
|
||||
|
||||
public boolean isExcluded() {
|
||||
return excluded;
|
||||
}
|
||||
|
||||
public Pattern getExcludedPattern() {
|
||||
return excludedPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IsExcluded { " +
|
||||
"excluded=" + excluded +
|
||||
", excludedPattern=" + excludedPattern +
|
||||
" }";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
package com.opensymphony.xwork2;
|
||||
|
||||
import com.mockobjects.dynamic.Mock;
|
||||
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
|
||||
import com.opensymphony.xwork2.mock.MockActionProxy;
|
||||
import com.opensymphony.xwork2.mock.MockContainer;
|
||||
import com.opensymphony.xwork2.mock.MockInterceptor;
|
||||
import com.opensymphony.xwork2.ognl.OgnlUtil;
|
||||
import com.opensymphony.xwork2.util.ValueStackFactory;
|
||||
import org.easymock.EasyMock;
|
||||
import org.easymock.IMocksControl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -39,6 +44,9 @@ public class DefaultActionInvocationTest extends XWorkTestCase {
|
||||
mockInterceptor3.setExpectedFoo("test3");
|
||||
|
||||
DefaultActionInvocation defaultActionInvocation = new DefaultActionInvocationTester(interceptorMappings);
|
||||
container.inject(defaultActionInvocation);
|
||||
defaultActionInvocation.stack = container.getInstance(ValueStackFactory.class).createValueStack();
|
||||
|
||||
defaultActionInvocation.invoke();
|
||||
assertTrue(mockInterceptor1.isExecuted());
|
||||
assertTrue(mockInterceptor2.isExecuted());
|
||||
|
||||
+5
-5
@@ -374,8 +374,8 @@ public class AnnotationXWorkConverterTest extends XWorkTestCase {
|
||||
stack.setValue("genericMap[456.12]", "42");
|
||||
|
||||
assertEquals(2, gb.getGenericMap().size());
|
||||
assertEquals(Integer.class, stack.findValue("genericMap.get(123.12).class"));
|
||||
assertEquals(Integer.class, stack.findValue("genericMap.get(456.12).class"));
|
||||
assertEquals("66", stack.findValue("genericMap.get(123.12).toString()"));
|
||||
assertEquals("42", stack.findValue("genericMap.get(456.12).toString()"));
|
||||
assertEquals(66, stack.findValue("genericMap.get(123.12)"));
|
||||
assertEquals(42, stack.findValue("genericMap.get(456.12)"));
|
||||
assertEquals(true, stack.findValue("genericMap.containsValue(66)"));
|
||||
@@ -393,8 +393,8 @@ public class AnnotationXWorkConverterTest extends XWorkTestCase {
|
||||
stack.setValue("genericMap[456.12]", "42");
|
||||
|
||||
assertEquals(2, gb.getGenericMap().size());
|
||||
assertEquals(Integer.class, stack.findValue("genericMap.get(123.12).class"));
|
||||
assertEquals(Integer.class, stack.findValue("genericMap.get(456.12).class"));
|
||||
assertEquals("66", stack.findValue("genericMap.get(123.12).toString()"));
|
||||
assertEquals("42", stack.findValue("genericMap.get(456.12).toString()"));
|
||||
assertEquals(66, stack.findValue("genericMap.get(123.12)"));
|
||||
assertEquals(42, stack.findValue("genericMap.get(456.12)"));
|
||||
assertEquals(true, stack.findValue("genericMap.containsValue(66)"));
|
||||
@@ -409,7 +409,7 @@ public class AnnotationXWorkConverterTest extends XWorkTestCase {
|
||||
stack.push(gb);
|
||||
|
||||
assertEquals(1, gb.getGetterList().size());
|
||||
assertEquals(Double.class, stack.findValue("getterList.get(0).class"));
|
||||
assertEquals("42.42", stack.findValue("getterList.get(0).toString()"));
|
||||
assertEquals(new Double(42.42), stack.findValue("getterList.get(0)"));
|
||||
assertEquals(new Double(42.42), gb.getGetterList().get(0));
|
||||
|
||||
|
||||
+16
-31
@@ -18,7 +18,6 @@ package com.opensymphony.xwork2.interceptor;
|
||||
import com.opensymphony.xwork2.Action;
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionProxy;
|
||||
import com.opensymphony.xwork2.ExcludedPatterns;
|
||||
import com.opensymphony.xwork2.ModelDrivenAction;
|
||||
import com.opensymphony.xwork2.SimpleAction;
|
||||
import com.opensymphony.xwork2.TestBean;
|
||||
@@ -33,11 +32,13 @@ import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import com.opensymphony.xwork2.ognl.OgnlValueStack;
|
||||
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
|
||||
import com.opensymphony.xwork2.ognl.SecurityMemberAccess;
|
||||
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
|
||||
import com.opensymphony.xwork2.util.CompoundRoot;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import com.opensymphony.xwork2.util.ValueStackFactory;
|
||||
import junit.framework.Assert;
|
||||
import ognl.OgnlContext;
|
||||
import ognl.PropertyAccessor;
|
||||
|
||||
import java.io.File;
|
||||
@@ -45,12 +46,10 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
@@ -111,13 +110,11 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
pi.setParameters(action, vs, params);
|
||||
|
||||
// then
|
||||
assertEquals(2, action.getActionMessages().size());
|
||||
assertEquals(1, action.getActionMessages().size());
|
||||
|
||||
String msg1 = action.getActionMessage(0);
|
||||
String msg2 = action.getActionMessage(1);
|
||||
|
||||
assertTrue(msg1.contains("Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'"));
|
||||
assertTrue(msg2.contains("Error setting expression 'top['name'](0)' with value 'true'"));
|
||||
assertTrue(msg1.contains("Error setting expression 'top['name'](0)' with value 'true'"));
|
||||
assertNull(action.getName());
|
||||
}
|
||||
|
||||
@@ -146,7 +143,6 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
|
||||
};
|
||||
|
||||
pi.setExcludeParams("(.*\\.|^)class\\..*");
|
||||
container.inject(pi);
|
||||
ValueStack vs = ActionContext.getContext().getValueStack();
|
||||
|
||||
@@ -164,12 +160,14 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
// given
|
||||
final String pollution1 = "class.classLoader.jarPath";
|
||||
final String pollution2 = "model.class.classLoader.jarPath";
|
||||
final String pollution3 = "class.classLoader.defaultAssertionStatus";
|
||||
|
||||
loadConfigurationProviders(new XWorkConfigurationProvider(), new XmlConfigurationProvider("xwork-param-test.xml"));
|
||||
loadConfigurationProviders(new XWorkConfigurationProvider(), new XmlConfigurationProvider("xwork-class-param-test.xml"));
|
||||
final Map<String, Object> params = new HashMap<String, Object>() {
|
||||
{
|
||||
put(pollution1, "bad");
|
||||
put(pollution2, "very bad");
|
||||
put(pollution3, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -183,10 +181,6 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeHardCodedExcludePatterns() {
|
||||
excludeParams = new HashSet<Pattern>();
|
||||
}
|
||||
};
|
||||
|
||||
container.inject(pi);
|
||||
@@ -197,16 +191,19 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
pi.setParameters(action, vs, params);
|
||||
|
||||
// then
|
||||
assertEquals(2, action.getActionMessages().size());
|
||||
assertEquals(3, action.getActionMessages().size());
|
||||
|
||||
String msg1 = action.getActionMessage(0);
|
||||
String msg2 = action.getActionMessage(1);
|
||||
String msg3 = action.getActionMessage(2);
|
||||
|
||||
assertEquals("Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg1);
|
||||
assertEquals("Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg2);
|
||||
assertEquals("Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
|
||||
assertEquals("Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
|
||||
assertEquals("Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
|
||||
|
||||
assertFalse(excluded.get(pollution1));
|
||||
assertFalse(excluded.get(pollution2));
|
||||
assertFalse(excluded.get(pollution3));
|
||||
}
|
||||
|
||||
public void testDoesNotAllowMethodInvocations() throws Exception {
|
||||
@@ -299,11 +296,6 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
final Map<String, Boolean> excluded = new HashMap<String, Boolean>();
|
||||
ParametersInterceptor pi = new ParametersInterceptor() {
|
||||
|
||||
@Override
|
||||
protected void initializeHardCodedExcludePatterns() {
|
||||
this.excludeParams = new HashSet<Pattern>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExcluded(String paramName) {
|
||||
boolean result = super.isExcluded(paramName);
|
||||
@@ -313,7 +305,6 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
|
||||
};
|
||||
|
||||
pi.setExcludeParams("(.*\\.|^|.*|\\[('|\"))class(\\.|('|\")]|\\[).*");
|
||||
container.inject(pi);
|
||||
ValueStack vs = ActionContext.getContext().getValueStack();
|
||||
|
||||
@@ -351,9 +342,8 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
|
||||
//then
|
||||
assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah());
|
||||
Object allowMethodAccess = stack.findValue("\u0023_memberAccess['allowStaticMethodAccess']");
|
||||
assertNotNull(allowMethodAccess);
|
||||
assertEquals(Boolean.FALSE, allowMethodAccess);
|
||||
boolean allowMethodAccess = ((SecurityMemberAccess) ((OgnlContext) stack.getContext()).getMemberAccess()).getAllowStaticMethodAccess();
|
||||
assertFalse(allowMethodAccess);
|
||||
}
|
||||
|
||||
public void testParameters() throws Exception {
|
||||
@@ -487,7 +477,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
proxy.execute();
|
||||
|
||||
SimpleAction action = (SimpleAction) proxy.getAction();
|
||||
assertNull(action.getName());
|
||||
assertEquals("try_1", action.getName());
|
||||
assertEquals("This is blah", (action).getBlah());
|
||||
assertEquals(123, action.getBaz());
|
||||
}
|
||||
@@ -731,11 +721,6 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
public void testExcludedPatternsGetInitialized() throws Exception {
|
||||
ParametersInterceptor parametersInterceptor = new ParametersInterceptor();
|
||||
assertEquals(ExcludedPatterns.EXCLUDED_PATTERNS.length, parametersInterceptor.excludeParams.size());
|
||||
}
|
||||
|
||||
private ValueStack injectValueStack(Map<String, Object> actual) {
|
||||
ValueStack stack = createStubValueStack(actual);
|
||||
container.inject(stack);
|
||||
|
||||
@@ -82,7 +82,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
});
|
||||
|
||||
Owner owner = new Owner();
|
||||
Map context = Ognl.createDefaultContext(owner);
|
||||
Map context = ognlUtil.createDefaultContext(owner);
|
||||
Map props = new HashMap();
|
||||
props.put("dog.name", dogName);
|
||||
|
||||
@@ -107,7 +107,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
public void testCanSetDependentObjectArray() {
|
||||
EmailAction action = new EmailAction();
|
||||
Map<String, Object> context = Ognl.createDefaultContext(action);
|
||||
Map<String, Object> context = ognlUtil.createDefaultContext(action);
|
||||
|
||||
Map<String, Object> props = new HashMap<String, Object>();
|
||||
props.put("email[0].address", "addr1");
|
||||
@@ -125,7 +125,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
Foo foo1 = new Foo();
|
||||
Foo foo2 = new Foo();
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo1);
|
||||
Map context = ognlUtil.createDefaultContext(foo1);
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.clear();
|
||||
@@ -171,7 +171,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
foo2.setTitle("foo2 title");
|
||||
foo2.setNumber(2);
|
||||
|
||||
Map<String, Object> context = Ognl.createDefaultContext(foo1);
|
||||
Map<String, Object> context = ognlUtil.createDefaultContext(foo1);
|
||||
|
||||
List<String> excludes = new ArrayList<String>();
|
||||
excludes.add("title");
|
||||
@@ -200,7 +200,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
b2.setTitle("");
|
||||
b2.setId(new Long(2));
|
||||
|
||||
context = Ognl.createDefaultContext(b1);
|
||||
context = ognlUtil.createDefaultContext(b1);
|
||||
List<String> includes = new ArrayList<String>();
|
||||
includes.add("title");
|
||||
includes.add("somethingElse");
|
||||
@@ -220,7 +220,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
Foo foo = new Foo();
|
||||
Bar bar = new Bar();
|
||||
|
||||
Map<String, Object> context = Ognl.createDefaultContext(foo);
|
||||
Map<String, Object> context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.clear();
|
||||
@@ -244,7 +244,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
Foo foo = new Foo();
|
||||
foo.setBar(new Bar());
|
||||
|
||||
Map<String, Object> context = Ognl.createDefaultContext(foo);
|
||||
Map<String, Object> context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map<String, Object> props = new HashMap();
|
||||
props.put("bar.title", "i am barbaz");
|
||||
@@ -280,7 +280,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
public void testOgnlHandlesCrapAtTheEndOfANumber() {
|
||||
Foo foo = new Foo();
|
||||
Map<String, Object> context = Ognl.createDefaultContext(foo);
|
||||
Map<String, Object> context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map<String, Object> props = new HashMap<String, Object>();
|
||||
props.put("aLong", "123a");
|
||||
@@ -317,7 +317,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
public void testSetPropertiesBoolean() {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map props = new HashMap();
|
||||
props.put("useful", "true");
|
||||
@@ -338,7 +338,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
Foo foo = new Foo();
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map props = new HashMap();
|
||||
props.put("birthday", "02/12/1982");
|
||||
@@ -408,7 +408,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
public void testSetPropertiesInt() {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map props = new HashMap();
|
||||
props.put("number", "2");
|
||||
@@ -420,7 +420,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
public void testSetPropertiesLongArray() {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map props = new HashMap();
|
||||
props.put("points", new String[]{"1", "2"});
|
||||
@@ -435,7 +435,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
public void testSetPropertiesString() {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map props = new HashMap();
|
||||
props.put("title", "this is a title");
|
||||
@@ -446,7 +446,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
public void testSetProperty() {
|
||||
Foo foo = new Foo();
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
assertFalse(123456 == foo.getNumber());
|
||||
ognlUtil.setProperty("number", "123456", foo, context);
|
||||
assertEquals(123456, foo.getNumber());
|
||||
@@ -457,7 +457,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
ChainingInterceptor foo = new ChainingInterceptor();
|
||||
ChainingInterceptor foo2 = new ChainingInterceptor();
|
||||
|
||||
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
|
||||
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(null);
|
||||
SimpleNode expression = (SimpleNode) Ognl.parseExpression("{'a','ruby','b','tom'}");
|
||||
|
||||
|
||||
@@ -499,7 +499,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
public void testStringToLong() {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
Map props = new HashMap();
|
||||
props.put("aLong", "123");
|
||||
@@ -518,7 +518,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
Foo foo = new Foo();
|
||||
foo.setALong(88);
|
||||
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
ognlUtil.setProperties(null, foo, context);
|
||||
assertEquals(88, foo.getALong());
|
||||
@@ -531,7 +531,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
public void testCopyNull() {
|
||||
Foo foo = new Foo();
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
ognlUtil.copy(null, null, context);
|
||||
|
||||
ognlUtil.copy(foo, null, context);
|
||||
@@ -540,7 +540,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
public void testGetTopTarget() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
Map context = Ognl.createDefaultContext(foo);
|
||||
Map context = ognlUtil.createDefaultContext(foo);
|
||||
|
||||
CompoundRoot root = new CompoundRoot();
|
||||
Object top = ognlUtil.getRealTarget("top", context, root);
|
||||
@@ -630,7 +630,131 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
stack.setValue("1114778947765", foo);
|
||||
stack.setValue("1234", foo);
|
||||
}
|
||||
|
||||
|
||||
public void testAvoidCallingMethodsOnObjectClass() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
ognlUtil.setValue("class.classLoader.defaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(NoSuchPropertyException.class, expected.getClass());
|
||||
assertEquals("com.opensymphony.xwork2.util.Foo.class", expected.getMessage());
|
||||
}
|
||||
|
||||
public void testAvoidCallingMethodsOnObjectClassUpperCased() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
ognlUtil.setValue("Class.ClassLoader.DefaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(NoSuchPropertyException.class, expected.getClass());
|
||||
assertEquals("com.opensymphony.xwork2.util.Foo.Class", expected.getMessage());
|
||||
}
|
||||
|
||||
public void testAvoidCallingMethodsOnObjectClassAsMap() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
ognlUtil.setValue("class['classLoader']['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(NoSuchPropertyException.class, expected.getClass());
|
||||
assertEquals("com.opensymphony.xwork2.util.Foo.class", expected.getMessage());
|
||||
}
|
||||
|
||||
public void testAvoidCallingMethodsOnObjectClassAsMap2() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setValue("foo['class']['classLoader']['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(NoSuchPropertyException.class, expected.getClass());
|
||||
assertEquals("com.opensymphony.xwork2.util.Foo.foo", expected.getMessage());
|
||||
}
|
||||
|
||||
public void testAvoidCallingMethodsOnObjectClassAsMapWithQuotes() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
ognlUtil.setValue("class[\"classLoader\"]['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(NoSuchPropertyException.class, expected.getClass());
|
||||
assertEquals("com.opensymphony.xwork2.util.Foo.class", expected.getMessage());
|
||||
}
|
||||
|
||||
public void testAvoidCallingToString() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setValue("toString", ognlUtil.createDefaultContext(foo), foo, null);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(OgnlException.class, expected.getClass());
|
||||
assertEquals("toString", expected.getMessage());
|
||||
}
|
||||
|
||||
public void testAvoidCallingMethodsWithBraces() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setValue("toString()", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(InappropriateExpressionException.class, expected.getClass());
|
||||
assertEquals(expected.getMessage(), "Inappropriate OGNL expression: toString()");
|
||||
}
|
||||
|
||||
public void testAvoidCallingSomeClasses() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Runtime.class.getName());
|
||||
ognlUtil.setValue("@java.lang.Runtime@getRuntime().exec('mate')", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
}
|
||||
assertNotNull(expected);
|
||||
assertSame(MethodFailedException.class, expected.getClass());
|
||||
assertEquals(expected.getMessage(), "Method \"getRuntime\" failed for object class java.lang.Runtime");
|
||||
}
|
||||
|
||||
public static class Email {
|
||||
String address;
|
||||
|
||||
@@ -58,6 +58,7 @@ public class OgnlValueStackTest extends XWorkTestCase {
|
||||
(CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()),
|
||||
container.getInstance(TextProvider.class, "system"), allowStaticMethodAccess);
|
||||
container.inject(stack);
|
||||
ognlUtil.setAllowStaticMethodAccess(Boolean.toString(allowStaticMethodAccess));
|
||||
return stack;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.opensymphony.xwork2.ognl;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SecurityMemberAccessTest extends TestCase {
|
||||
|
||||
private Map context;
|
||||
private FooBar target;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
context = new HashMap();
|
||||
target = new FooBar();
|
||||
}
|
||||
|
||||
public void testWithoutClassExclusion() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "stringField";
|
||||
Member member = FooBar.class.getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1));
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertTrue(accessible);
|
||||
}
|
||||
|
||||
public void testClassExclusion() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "stringField";
|
||||
Member member = FooBar.class.getDeclaredMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1));
|
||||
|
||||
Set<Class<?>> excluded = new HashSet<Class<?>>();
|
||||
excluded.add(FooBar.class);
|
||||
sma.setExcludedClasses(excluded);
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertFalse(accessible);
|
||||
}
|
||||
|
||||
public void testObjectClassExclusion() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "toString";
|
||||
Member member = FooBar.class.getMethod(propertyName);
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertFalse("toString() from Object is accessible!!!", accessible);
|
||||
}
|
||||
|
||||
public void testObjectOverwrittenMethodsExclusion() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "hashCode";
|
||||
Member member = FooBar.class.getMethod(propertyName);
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertTrue("hashCode() from FooBar isn't accessible!!!", accessible);
|
||||
}
|
||||
|
||||
public void testInterfaceInheritanceExclusion() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "barLogic";
|
||||
Member member = BarInterface.class.getMethod(propertyName);
|
||||
|
||||
Set<Class<?>> excluded = new HashSet<Class<?>>();
|
||||
excluded.add(BarInterface.class);
|
||||
sma.setExcludedClasses(excluded);
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertFalse("barLogic() from BarInterface is accessible!!!", accessible);
|
||||
}
|
||||
|
||||
public void testMiddleOfInheritanceExclusion1() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "fooLogic";
|
||||
Member member = FooBar.class.getMethod(propertyName);
|
||||
|
||||
Set<Class<?>> excluded = new HashSet<Class<?>>();
|
||||
excluded.add(BarInterface.class);
|
||||
sma.setExcludedClasses(excluded);
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertTrue("fooLogic() from FooInterface isn't accessible!!!", accessible);
|
||||
}
|
||||
|
||||
public void testMiddleOfInheritanceExclusion2() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "barLogic";
|
||||
Member member = BarInterface.class.getMethod(propertyName);
|
||||
|
||||
Set<Class<?>> excluded = new HashSet<Class<?>>();
|
||||
excluded.add(BarInterface.class);
|
||||
sma.setExcludedClasses(excluded);
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertFalse("barLogic() from BarInterface is accessible!!!", accessible);
|
||||
}
|
||||
|
||||
public void testMiddleOfInheritanceExclusion3() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "barLogic";
|
||||
Member member = BarInterface.class.getMethod(propertyName);
|
||||
|
||||
/*
|
||||
Set<Class<?>> excluded = new HashSet<Class<?>>();
|
||||
excluded.add(BarInterface.class);
|
||||
sma.setExcludedClasses(excluded);
|
||||
*/
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertTrue("barLogic() from BarInterface isn't accessible!!!", accessible);
|
||||
}
|
||||
|
||||
public void testMiddleOfInheritanceExclusion4() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
String propertyName = "barLogic";
|
||||
Member member = BarInterface.class.getMethod(propertyName);
|
||||
|
||||
Set<Class<?>> excluded = new HashSet<Class<?>>();
|
||||
excluded.add(FooBarInterface.class);
|
||||
sma.setExcludedClasses(excluded);
|
||||
|
||||
// when
|
||||
boolean accessible = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertFalse("barLogic() from BarInterface is accessible!!!", accessible);
|
||||
}
|
||||
|
||||
public void testPackageExclusion() throws Exception {
|
||||
// given
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(false);
|
||||
|
||||
Set<Pattern> excluded = new HashSet<Pattern>();
|
||||
excluded.add(Pattern.compile("^" + FooBar.class.getPackage().getName().replaceAll("\\.", "\\\\.") + ".*"));
|
||||
sma.setExcludedPackageNamePatterns(excluded);
|
||||
|
||||
String propertyName = "stringField";
|
||||
Member member = FooBar.class.getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1));
|
||||
|
||||
// when
|
||||
boolean actual = sma.isAccessible(context, target, member, propertyName);
|
||||
|
||||
// then
|
||||
assertFalse("stringField is accessible!", actual);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FooBar implements FooBarInterface {
|
||||
|
||||
private String stringField;
|
||||
|
||||
public String getStringField() {
|
||||
return stringField;
|
||||
}
|
||||
|
||||
public void setStringField(String stringField) {
|
||||
this.stringField = stringField;
|
||||
}
|
||||
|
||||
public String fooLogic() {
|
||||
return "fooLogic";
|
||||
}
|
||||
|
||||
public String barLogic() {
|
||||
return "barLogic";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface FooInterface {
|
||||
|
||||
String fooLogic();
|
||||
|
||||
}
|
||||
|
||||
interface BarInterface {
|
||||
|
||||
String barLogic();
|
||||
|
||||
}
|
||||
|
||||
interface FooBarInterface extends FooInterface, BarInterface {
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.opensymphony.xwork2.security;
|
||||
|
||||
import com.opensymphony.xwork2.XWorkTestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DefaultAcceptedPatternsCheckerTest extends XWorkTestCase {
|
||||
|
||||
public void testHardcodedAcceptedPatterns() throws Exception {
|
||||
// given
|
||||
List<String> params = new ArrayList<String>() {
|
||||
{
|
||||
add("%{#application['test']}");
|
||||
add("%{#application.test}");
|
||||
add("%{#Application['test']}");
|
||||
add("%{#Application.test}");
|
||||
add("%{#session['test']}");
|
||||
add("%{#session.test}");
|
||||
add("%{#Session['test']}");
|
||||
add("%{#Session.test}");
|
||||
add("%{#struts['test']}");
|
||||
add("%{#struts.test}");
|
||||
add("%{#Struts['test']}");
|
||||
add("%{#Struts.test}");
|
||||
add("%{#request['test']}");
|
||||
add("%{#request.test}");
|
||||
add("%{#Request['test']}");
|
||||
add("%{#Request.test}");
|
||||
add("%{#servletRequest['test']}");
|
||||
add("%{#servletRequest.test}");
|
||||
add("%{#ServletRequest['test']}");
|
||||
add("%{#ServletRequest.test}");
|
||||
add("%{#servletResponse['test']}");
|
||||
add("%{#servletResponse.test}");
|
||||
add("%{#ServletResponse['test']}");
|
||||
add("%{#ServletResponse.test}");
|
||||
add("%{#parameters['test']}");
|
||||
add("%{#parameters.test}");
|
||||
add("%{#Parameters['test']}");
|
||||
add("%{#Parameters.test}");
|
||||
}
|
||||
};
|
||||
|
||||
AcceptedPatternsChecker checker = new DefaultAcceptedPatternsChecker();
|
||||
|
||||
for (String param : params) {
|
||||
// when
|
||||
AcceptedPatternsChecker.IsAccepted actual = checker.isAccepted(param);
|
||||
|
||||
// then
|
||||
assertFalse("Access to " + param + " is possible!", actual.isAccepted());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.opensymphony.xwork2.security;
|
||||
|
||||
import com.opensymphony.xwork2.XWorkTestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DefaultExcludedPatternsCheckerTest extends XWorkTestCase {
|
||||
|
||||
public void testHardcodedPatterns() throws Exception {
|
||||
// given
|
||||
List<String> params = new ArrayList<String>() {
|
||||
{
|
||||
add("%{#application['test']}");
|
||||
add("%{#application.test}");
|
||||
add("%{#Application['test']}");
|
||||
add("%{#Application.test}");
|
||||
add("%{#session['test']}");
|
||||
add("%{#session.test}");
|
||||
add("%{#Session['test']}");
|
||||
add("%{#Session.test}");
|
||||
add("%{#struts['test']}");
|
||||
add("%{#struts.test}");
|
||||
add("%{#Struts['test']}");
|
||||
add("%{#Struts.test}");
|
||||
add("%{#request['test']}");
|
||||
add("%{#request.test}");
|
||||
add("%{#Request['test']}");
|
||||
add("%{#Request.test}");
|
||||
add("%{#servletRequest['test']}");
|
||||
add("%{#servletRequest.test}");
|
||||
add("%{#ServletRequest['test']}");
|
||||
add("%{#ServletRequest.test}");
|
||||
add("%{#servletResponse['test']}");
|
||||
add("%{#servletResponse.test}");
|
||||
add("%{#ServletResponse['test']}");
|
||||
add("%{#ServletResponse.test}");
|
||||
add("%{#parameters['test']}");
|
||||
add("%{#parameters.test}");
|
||||
add("%{#Parameters['test']}");
|
||||
add("%{#Parameters.test}");
|
||||
add("#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse')");
|
||||
add("%{#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse')}");
|
||||
add("#_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true)");
|
||||
add("%{#_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true)}");
|
||||
}
|
||||
};
|
||||
|
||||
ExcludedPatternsChecker checker = new DefaultExcludedPatternsChecker();
|
||||
|
||||
for (String param : params) {
|
||||
// when
|
||||
ExcludedPatternsChecker.IsExcluded actual = checker.isExcluded(param);
|
||||
|
||||
// then
|
||||
assertTrue("Access to " + param + " is possible!", actual.isExcluded());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE xwork PUBLIC
|
||||
"-//Apache Struts//XWork 2.0//EN"
|
||||
"http://struts.apache.org/dtds/xwork-2.0.dtd">
|
||||
|
||||
<xwork>
|
||||
|
||||
<constant name="devMode" value="true" />
|
||||
<constant name="ognlExcludedClasses" value="java.lang.Object,java.lang.Runtime" />
|
||||
<constant name="overrideExcludedPatterns" value="not-existing-pattern"/>
|
||||
|
||||
</xwork>
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
<xwork>
|
||||
<constant name="devMode" value="true" />
|
||||
<constant name="ognlExcludedClasses" value="java.lang.Object,java.lang.Runtime" />
|
||||
</xwork>
|
||||
@@ -3,25 +3,7 @@
|
||||
"http://struts.apache.org/dtds/xwork-2.0.dtd">
|
||||
|
||||
<xwork>
|
||||
<!--
|
||||
<bean class="com.opensymphony.xwork2.ObjectFactory" name="default" />
|
||||
<bean type="com.opensymphony.xwork2.ActionProxyFactory" name="default" class="com.opensymphony.xwork2.DefaultActionProxyFactory"/>
|
||||
<constant name="devMode" value="false" />
|
||||
|
||||
<bean type="com.opensymphony.xwork2.util.ValueStackFactory"
|
||||
class="com.opensymphony.xwork2.ognl.OgnlValueStackFactory" />
|
||||
<bean type="com.opensymphony.xwork2.util.reflection.ReflectionProvider"
|
||||
class="com.opensymphony.xwork2.ognl.OgnlReflectionProvider" />
|
||||
<bean type="com.opensymphony.xwork2.util.reflection.ReflectionContextFactory"
|
||||
class="com.opensymphony.xwork2.ognl.OgnlReflectionContextFactory" />
|
||||
<bean class="com.opensymphony.xwork2.conversion.impl.XWorkConverter" />
|
||||
<bean type="com.opensymphony.xwork2.conversion.ObjectTypeDeterminer"
|
||||
class="com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer" />
|
||||
-->
|
||||
<!-- static injections -->
|
||||
<!--
|
||||
<bean class="com.opensymphony.xwork2.ognl.OgnlValueStack" static="true"/>
|
||||
<bean class="com.opensymphony.xwork2.conversion.impl.XWorkConverter" static="true"/>
|
||||
<bean class="com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory" static="true" />
|
||||
-->
|
||||
|
||||
<constant name="ognlExcludedClasses" value="java.lang.Object,java.lang.Runtime,ognl.OgnlContext,ognl.MemberAccess,ognl.ClassResolver,ognl.TypeConverter,com.opensymphony.xwork2.ognl.SecurityMemberAccess" />
|
||||
|
||||
</xwork>
|
||||
Reference in New Issue
Block a user