allowlistPackageNames = emptySet();
+
private boolean disallowProxyObjectAccess = false;
private boolean disallowProxyMemberAccess = false;
private boolean disallowDefaultPackageAccess = false;
@@ -209,12 +223,28 @@ public class SecurityMemberAccess implements MemberAccess {
* @return {@code true} if member access is allowed
*/
protected boolean checkAllowlist(Object target, Member member) {
- Class> memberClass = member.getDeclaringClass();
if (!enforceAllowlistEnabled) {
+ logAllowlistDisabled();
return true;
}
+
+ if (!disallowProxyObjectAccess && target != null && ProxyUtil.isProxy(target)) {
+ // If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying
+ // classes/members. This allows the allowlist capability to continue working and offer some level of
+ // protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate
+ // entities. This is preferred to having to disable the allowlist capability entirely.
+ Object newTarget = ProxyUtil.getHibernateProxyTarget(target);
+ if (newTarget != target) {
+ logAllowlistHibernateEntity(target, newTarget);
+ target = newTarget;
+ member = ProxyUtil.resolveTargetMember(member, newTarget);
+ }
+ }
+
+ Class> memberClass = member.getDeclaringClass();
if (!isClassAllowlisted(memberClass)) {
- LOG.warn(format("Declaring class [{0}] of member type [{1}] is not allowlisted!", memberClass, member));
+ LOG.warn("Declaring class [{}] of member type [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
+ memberClass, member, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
return false;
}
if (target == null || target.getClass() == memberClass) {
@@ -222,12 +252,42 @@ public class SecurityMemberAccess implements MemberAccess {
}
Class> targetClass = target.getClass();
if (!isClassAllowlisted(targetClass)) {
- LOG.warn(format("Target class [{0}] of target [{1}] is not allowlisted!", targetClass, target));
+ LOG.warn("Target class [{}] of target [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
+ targetClass, target, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
return false;
}
return true;
}
+ private void logAllowlistDisabled() {
+ if (!isDevMode && !LOG.isDebugEnabled()) {
+ return;
+ }
+ String msg = "OGNL allowlist is disabled!" +
+ " We strongly recommend keeping it enabled to protect against critical vulnerabilities." +
+ " Set the configuration `{0}=true` to enable it.";
+ Object[] args = {StrutsConstants.STRUTS_ALLOWLIST_ENABLE};
+ if (isDevMode) {
+ LOG.warn(msg, args);
+ } else {
+ LOG.debug(msg, args);
+ }
+ }
+
+ private void logAllowlistHibernateEntity(Object original, Object resolved) {
+ if (!isDevMode && !LOG.isDebugEnabled()) {
+ return;
+ }
+ String msg = "Hibernate entity [{}] resolved to [{}] for purpose of OGNL allowlisting." +
+ " We don't recommend executing OGNL expressions against Hibernate entities, you may disallow this behaviour using the configuration `{}=true`.";
+ Object[] args = {original, resolved, StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS};
+ if (isDevMode) {
+ LOG.warn(msg, args);
+ } else {
+ LOG.debug(msg, args);
+ }
+ }
+
protected boolean isClassAllowlisted(Class> clazz) {
return allowlistClasses.contains(clazz)
|| ALLOWLIST_REQUIRED_CLASSES.contains(clazz)
@@ -241,6 +301,7 @@ public class SecurityMemberAccess implements MemberAccess {
* @return {@code true} if member access is allowed
*/
protected boolean checkExclusionList(Object target, Member member) {
+ useDevModeConfiguration();
Class> memberClass = member.getDeclaringClass();
if (isClassExcluded(memberClass)) {
LOG.warn("Declaring class of member type [{}] is excluded!", memberClass);
@@ -436,12 +497,12 @@ public class SecurityMemberAccess implements MemberAccess {
this.enforceAllowlistEnabled = BooleanUtils.toBoolean(enforceAllowlistEnabled);
}
- @Inject(value = StrutsConstants.STRUTS_ALLOWLIST_CLASSES, required = false)
+ @Inject(value = STRUTS_ALLOWLIST_CLASSES, required = false)
public void useAllowlistClasses(String commaDelimitedClasses) {
this.allowlistClasses = toClassObjectsSet(commaDelimitedClasses);
}
- @Inject(value = StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
+ @Inject(value = STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
public void useAllowlistPackageNames(String commaDelimitedPackageNames) {
this.allowlistPackageNames = toPackageNamesSet(commaDelimitedPackageNames);
}
@@ -460,4 +521,41 @@ public class SecurityMemberAccess implements MemberAccess {
public void useDisallowDefaultPackageAccess(String disallowDefaultPackageAccess) {
this.disallowDefaultPackageAccess = BooleanUtils.toBoolean(disallowDefaultPackageAccess);
}
+
+ @Inject(StrutsConstants.STRUTS_DEVMODE)
+ protected void useDevMode(String devMode) {
+ this.isDevMode = BooleanUtils.toBoolean(devMode);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false)
+ public void useDevModeExcludedClasses(String commaDelimitedClasses) {
+ this.devModeExcludedClasses = toNewClassesSet(devModeExcludedClasses, commaDelimitedClasses);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
+ public void useDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
+ this.devModeExcludedPackageNamePatterns = toNewPatternsSet(devModeExcludedPackageNamePatterns, commaDelimitedPackagePatterns);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false)
+ public void useDevModeExcludedPackageNames(String commaDelimitedPackageNames) {
+ this.devModeExcludedPackageNames = toNewPackageNamesSet(devModeExcludedPackageNames, commaDelimitedPackageNames);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
+ public void useDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
+ this.devModeExcludedPackageExemptClasses = toClassesSet(commaDelimitedClasses);
+ }
+
+ private void useDevModeConfiguration() {
+ if (!isDevMode || isDevModeInit) {
+ return;
+ }
+ isDevModeInit = true;
+ LOG.warn("Working in devMode, using devMode excluded classes and packages!");
+ excludedClasses = devModeExcludedClasses;
+ excludedPackageNamePatterns = devModeExcludedPackageNamePatterns;
+ excludedPackageNames = devModeExcludedPackageNames;
+ excludedPackageExemptClasses = devModeExcludedPackageExemptClasses;
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DebugUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/DebugUtils.java
new file mode 100644
index 000000000..3fdf8b0a7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/DebugUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.TextProvider;
+import com.opensymphony.xwork2.interceptor.ValidationAware;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * @since 6.5.0
+ */
+public final class DebugUtils {
+
+ public static void notifyDeveloperOfError(Logger log, Object action, String message) {
+ if (action instanceof TextProvider) {
+ TextProvider tp = (TextProvider) action;
+ message = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{message});
+ }
+ log.error(message);
+ if (action instanceof ValidationAware) {
+ ValidationAware validationAware = (ValidationAware) action;
+ validationAware.addActionError(message);
+ }
+ }
+
+}
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
index c169af20b..895cfb7ee 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
@@ -24,6 +24,7 @@ import com.opensymphony.xwork2.ognl.OgnlCacheFactory;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
+import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import java.lang.reflect.Constructor;
@@ -33,6 +34,8 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
+import static java.lang.reflect.Modifier.isPublic;
+
/**
* ProxyUtil
*
@@ -255,4 +258,34 @@ public class ProxyUtil {
return false;
}
+
+ /**
+ * @return the target instance of the given object if it is a Hibernate proxy object, otherwise the given object
+ */
+ public static Object getHibernateProxyTarget(Object object) {
+ try {
+ return Hibernate.unproxy(object);
+ } catch (NoClassDefFoundError ignored) {
+ return object;
+ }
+ }
+
+ /**
+ * @return matching member on target object if one exists, otherwise the same member
+ */
+ public static Member resolveTargetMember(Member proxyMember, Object target) {
+ int mod = proxyMember.getModifiers();
+ if (proxyMember instanceof Method) {
+ if (isPublic(mod)) {
+ return MethodUtils.getMatchingAccessibleMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
+ } else {
+ return MethodUtils.getMatchingMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
+ }
+ } else if (proxyMember instanceof Field) {
+ return FieldUtils.getField(target.getClass(), proxyMember.getName(), isPublic(mod));
+ } else if (proxyMember instanceof Constructor && isPublic(mod)) {
+ return ConstructorUtils.getMatchingAccessibleConstructor(target.getClass(), ((Constructor>) proxyMember).getParameterTypes());
+ }
+ return proxyMember;
+ }
}
diff --git a/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java b/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java
index 23b3b8f3b..fcca60538 100644
--- a/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java
+++ b/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java
@@ -103,9 +103,9 @@ public class ServletUrlRenderer implements UrlRenderer {
}
result = urlHelper.buildUrl(_value, urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
}
- String anchor = urlComponent.getAnchor();
- if (StringUtils.isNotEmpty(anchor)) {
- result += '#' + urlComponent.findString(anchor);
+ if (StringUtils.isNotEmpty(urlComponent.getAnchor())) {
+ String anchor = urlComponent.findString(urlComponent.getAnchor());
+ result += '#' + anchor;
}
if (urlComponent.isPutInContext()) {
@@ -292,7 +292,7 @@ public class ServletUrlRenderer implements UrlRenderer {
private void includeGetParameters(UrlProvider urlComponent) {
String query = extractQueryString(urlComponent);
QueryStringParser.Result result = queryStringParser.parse(query);
- mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
+ result = mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
if (!result.getQueryFragment().isEmpty()) {
urlComponent.setAnchor(result.getQueryFragment());
}
@@ -331,10 +331,11 @@ public class ServletUrlRenderer implements UrlRenderer {
* @param value the value attribute (URL to be generated by this component)
* @param parameters component parameters
* @param contextParameters request parameters
+ * @return {@link QueryStringParser.Result} of value's ?query-string or empty()
*/
- protected void mergeRequestParameters(String value, Map parameters, Map contextParameters) {
-
+ protected QueryStringParser.Result mergeRequestParameters(String value, Map parameters, Map contextParameters) {
Map mergedParams = new LinkedHashMap<>(contextParameters);
+ QueryStringParser.Result result = queryStringParser.empty();
// Merge contextParameters (from current request) with parameters specified in value attribute
// eg. value="someAction.action?id=someId&venue=someVenue"
@@ -343,7 +344,8 @@ public class ServletUrlRenderer implements UrlRenderer {
if (StringUtils.contains(value, "?")) {
String queryString = value.substring(value.indexOf('?') + 1);
- mergedParams = new LinkedHashMap<>(queryStringParser.parse(queryString).getQueryParams());
+ result = queryStringParser.parse(queryString);
+ mergedParams = new LinkedHashMap<>(result.getQueryParams());
for (Map.Entry entry : contextParameters.entrySet()) {
if (!mergedParams.containsKey(entry.getKey())) {
mergedParams.put(entry.getKey(), entry.getValue());
@@ -362,6 +364,8 @@ public class ServletUrlRenderer implements UrlRenderer {
parameters.put(entry.getKey(), entry.getValue());
}
}
+
+ return result;
}
}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java
index e9215e533..239bc6d6c 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java
@@ -20,10 +20,8 @@ package org.apache.struts2.interceptor.parameter;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
-import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
-import com.opensymphony.xwork2.interceptor.ValidationAware;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
@@ -56,7 +54,6 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
@@ -67,6 +64,8 @@ import java.util.regex.Pattern;
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS;
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS_STR;
+import static com.opensymphony.xwork2.util.DebugUtils.notifyDeveloperOfError;
+import static java.lang.String.format;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang3.StringUtils.indexOfAny;
@@ -317,19 +316,8 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
}
protected void notifyDeveloperParameterException(Object action, String property, String message) {
- String logMsg = "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message;
- if (action instanceof TextProvider) {
- TextProvider tp = (TextProvider) action;
- logMsg = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{logMsg});
- }
- LOG.error(logMsg);
-
- if (action instanceof ValidationAware) {
- ValidationAware validationAware = (ValidationAware) action;
- Collection messages = validationAware.getActionMessages();
- messages.add(message);
- validationAware.setActionMessages(messages);
- }
+ String logMsg = format("Unexpected Exception caught setting '%s' on '%s: %s", property, action.getClass(), message);
+ notifyDeveloperOfError(LOG, action, logMsg);
}
/**
@@ -388,23 +376,37 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
return hasValidAnnotatedField(action, rootProperty, paramDepth);
}
- if (hasValidAnnotatedPropertyDescriptor(propDescOpt.get(), paramDepth)) {
+ if (hasValidAnnotatedPropertyDescriptor(action, propDescOpt.get(), paramDepth)) {
return true;
}
return hasValidAnnotatedField(action, rootProperty, paramDepth);
}
+ /**
+ * @deprecated since 6.5.0, use {@link #hasValidAnnotatedPropertyDescriptor(Object, PropertyDescriptor, long)}
+ * instead.
+ */
+ @Deprecated
protected boolean hasValidAnnotatedPropertyDescriptor(PropertyDescriptor propDesc, long paramDepth) {
+ return hasValidAnnotatedPropertyDescriptor(null, propDesc, paramDepth);
+ }
+
+ protected boolean hasValidAnnotatedPropertyDescriptor(Object action, PropertyDescriptor propDesc, long paramDepth) {
Method relevantMethod = paramDepth == 0 ? propDesc.getWriteMethod() : propDesc.getReadMethod();
if (relevantMethod == null) {
return false;
}
if (getPermittedInjectionDepth(relevantMethod) < paramDepth) {
- LOG.debug(
- "Parameter injection for method [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
+ String logMessage = format(
+ "Parameter injection for method [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
relevantMethod.getName(),
relevantMethod.getDeclaringClass().getName());
+ if (devMode) {
+ notifyDeveloperOfError(LOG, action, logMessage);
+ } else {
+ LOG.debug(logMessage);
+ }
return false;
}
if (paramDepth >= 1) {
@@ -455,10 +457,15 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
return false;
}
if (getPermittedInjectionDepth(field) < paramDepth) {
- LOG.debug(
- "Parameter injection for field [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
+ String logMessage = format(
+ "Parameter injection for field [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
fieldName,
action.getClass().getName());
+ if (devMode) {
+ notifyDeveloperOfError(LOG, action, logMessage);
+ } else {
+ LOG.debug(logMessage);
+ }
return false;
}
if (paramDepth >= 1) {
@@ -533,7 +540,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
return "NONE";
}
return parameters.entrySet().stream()
- .map(entry -> String.format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
+ .map(entry -> format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
.collect(joining());
}
diff --git a/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java b/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java
index 567417174..fe178257d 100644
--- a/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java
+++ b/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java
@@ -118,9 +118,6 @@ public class FreemarkerManager {
public static final String INITPARAM_DEBUG = "Debug";
public static final String KEY_REQUEST = "Request";
- public static final String KEY_INCLUDE = "include_page";
- public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__";
- public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
public static final String KEY_SESSION = "Session";
public static final String KEY_APPLICATION = "Application";
public static final String KEY_APPLICATION_PRIVATE = "__FreeMarkerServlet.Application__";
@@ -138,10 +135,29 @@ public class FreemarkerManager {
// for Struts
public static final String KEY_REQUEST_PARAMETERS_STRUTS = "Parameters";
- public static final String KEY_HASHMODEL_PRIVATE = "__FreeMarkerManager.Request__";
-
public static final String EXPIRATION_DATE;
+ /**
+ * @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
+ */
+ @Deprecated
+ public static final String KEY_INCLUDE = "include_page";
+ /**
+ * @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
+ */
+ @Deprecated
+ public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__";
+ /**
+ * @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
+ */
+ @Deprecated
+ public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
+ /**
+ * @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
+ */
+ @Deprecated
+ public static final String KEY_HASHMODEL_PRIVATE = "__FreeMarkerManager.Request__";
+
/**
* Adds individual settings.
*
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java
index b8e100ee9..27a0d0f33 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java
@@ -80,6 +80,11 @@ public class OgnlUtilTest extends XWorkTestCase {
ognlUtil = container.getInstance(OgnlUtil.class);
}
+ private void resetOgnlUtil(Map properties) {
+ loadButSet(properties);
+ ognlUtil = container.getInstance(OgnlUtil.class);
+ }
+
public void testCanSetADependentObject() {
String dogName = "fido";
@@ -1152,8 +1157,8 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
- ognlUtil.setExcludedClasses(Object.class.getName());
- ognlUtil.setValue("class.classLoader.defaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
+ // Object.class is excluded by default
+ ognlUtil.setValue("class.classLoader", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
expected = e;
@@ -1166,9 +1171,11 @@ public class OgnlUtilTest extends XWorkTestCase {
public void testAllowCallingMethodsOnObjectClassInDevModeTrue() {
Exception expected = null;
try {
- ognlUtil.setExcludedClasses(Foo.class.getName());
- ognlUtil.setDevModeExcludedClasses("");
- ognlUtil.setDevMode(Boolean.TRUE.toString());
+ Map properties = new HashMap<>();
+ properties.put(StrutsConstants.STRUTS_EXCLUDED_CLASSES, Foo.class.getName());
+ properties.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, "");
+ properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.TRUE.toString());
+ resetOgnlUtil(properties);
Foo foo = new Foo();
String result = (String) ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class);
@@ -1180,14 +1187,18 @@ public class OgnlUtilTest extends XWorkTestCase {
}
public void testExclusionListDevModeOnOff() throws Exception {
- ognlUtil.setDevModeExcludedClasses(Foo.class.getName());
Foo foo = new Foo();
- ognlUtil.setDevMode(Boolean.TRUE.toString());
+ Map properties = new HashMap<>();
+ properties.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, Foo.class.getName());
+ properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.TRUE.toString());
+ resetOgnlUtil(properties);
+
OgnlException e = assertThrows(OgnlException.class, () -> ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class));
assertThat(e).hasMessageContaining("com.opensymphony.xwork2.util.Foo.toString");
- ognlUtil.setDevMode(Boolean.FALSE.toString());
+ properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.FALSE.toString());
+ resetOgnlUtil(properties);
assertEquals("Foo", (String) ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class));
}
@@ -1196,7 +1207,7 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
- ognlUtil.setExcludedClasses(Object.class.getName());
+ // Object.class is excluded by default
ognlUtil.setValue("Class.ClassLoader.DefaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
@@ -1212,7 +1223,7 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
- ognlUtil.setExcludedClasses(Object.class.getName());
+ // Object.class is excluded by default
ognlUtil.setValue("class['classLoader']['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
@@ -1243,7 +1254,7 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
- ognlUtil.setExcludedClasses(Object.class.getName());
+ // Object.class is excluded by default
ognlUtil.setValue("class[\"classLoader\"]['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
@@ -1284,12 +1295,11 @@ public class OgnlUtilTest extends XWorkTestCase {
assertEquals(expected.getMessage(), "Inappropriate OGNL expression: toString()");
}
- public void testAvoidCallingSomeClasses() {
+ public void testStaticMethodBlocked() {
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) {
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
index 381b7d0ad..d508ef99d 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
@@ -26,12 +26,16 @@ import ognl.MemberAccess;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.struts2.ognl.ProviderAllowlist;
import org.apache.struts2.ognl.ThreadAllowlist;
+import org.hibernate.proxy.HibernateProxy;
+import org.hibernate.proxy.LazyInitializer;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -853,9 +857,11 @@ public class SecurityMemberAccessTest {
assertTrue("package java.lang. is accessible!", actual);
}
+ /**
+ * Test that the allowlist is enforced correctly for classes.
+ */
@Test
public void classInclusion() throws Exception {
-
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
TestBean2 bean = new TestBean2();
@@ -868,6 +874,9 @@ public class SecurityMemberAccessTest {
assertTrue(sma.checkAllowlist(bean, method));
}
+ /**
+ * Test that the allowlist is enforced correctly for packages.
+ */
@Test
public void packageInclusion() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -882,6 +891,9 @@ public class SecurityMemberAccessTest {
assertTrue(sma.checkAllowlist(bean, method));
}
+ /**
+ * Test that the allowlist doesn't allow inherited methods unless the declaring class is also allowlisted.
+ */
@Test
public void classInclusion_subclass() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -893,6 +905,9 @@ public class SecurityMemberAccessTest {
assertFalse(sma.checkAllowlist(bean, method));
}
+ /**
+ * Test that the allowlist allows inherited methods when both the target and declaring class are allowlisted.
+ */
@Test
public void classInclusion_subclass_both() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -904,6 +919,10 @@ public class SecurityMemberAccessTest {
assertTrue(sma.checkAllowlist(bean, method));
}
+ /**
+ * Test that the allowlist doesn't allow inherited methods unless the package of the declaring class is also
+ * allowlisted.
+ */
@Test
public void packageInclusion_subclass() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -915,6 +934,37 @@ public class SecurityMemberAccessTest {
assertFalse(sma.checkAllowlist(bean, method));
}
+ /**
+ * When the allowlist is enabled and proxy object access is disallowed, Hibernate proxies should not be allowed.
+ */
+ @Test
+ public void classInclusion_hibernateProxy_disallowProxyObjectAccess() throws Exception {
+ FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class);
+ Method proxyMethod = proxyObject.getClass().getMethod("fooLogic");
+
+ sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
+ sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
+ sma.useAllowlistClasses(FooBar.class.getName());
+
+ assertFalse(sma.checkAllowlist(proxyObject, proxyMethod));
+ }
+
+ /**
+ * When the allowlist is enabled and proxy object access is allowed, Hibernate proxies should be allowlisted based
+ * on their underlying target object. Class allowlisting should work as expected.
+ */
+ @Test
+ public void classInclusion_hibernateProxy_allowProxyObjectAccess() throws Exception {
+ FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class);
+ Method proxyMethod = proxyObject.getClass().getMethod("fooLogic");
+
+ sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
+ sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
+ sma.useAllowlistClasses(FooBar.class.getName());
+
+ assertTrue(sma.checkAllowlist(proxyObject, proxyMethod));
+ }
+
@Test
public void packageInclusion_subclass_both() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -931,6 +981,15 @@ public class SecurityMemberAccessTest {
private static String formGetterName(String propertyName) {
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
}
+
+ @SuppressWarnings("unchecked")
+ private static T mockHibernateProxy(T originalObject, Class proxyInterface) {
+ return (T) Proxy.newProxyInstance(
+ proxyInterface.getClassLoader(),
+ new Class>[]{proxyInterface, HibernateProxy.class},
+ new DummyHibernateProxyHandler(originalObject)
+ );
+ }
}
class FooBar implements FooBarInterface {
@@ -1042,10 +1101,28 @@ class StaticTester {
}
protected static Field getFieldByName(String fieldName) throws NoSuchFieldException {
- if (fieldName != null && fieldName.length() > 0) {
+ if (fieldName != null && !fieldName.isEmpty()) {
return StaticTester.class.getDeclaredField(fieldName);
} else {
throw new NoSuchFieldException("field: " + fieldName + " does not exist");
}
}
}
+
+class DummyHibernateProxyHandler implements InvocationHandler {
+ private final Object instance;
+
+ public DummyHibernateProxyHandler(Object instance) {
+ this.instance = instance;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (HibernateProxy.class.getMethod("getHibernateLazyInitializer").equals(method)) {
+ LazyInitializer initializer = mock(LazyInitializer.class);
+ when(initializer.getImplementation()).thenReturn(instance);
+ return initializer;
+ }
+ return method.invoke(instance, args);
+ }
+}
diff --git a/core/src/test/java/org/apache/struts2/interceptor/parameter/ParametersInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/parameter/ParametersInterceptorTest.java
index a142014b3..b15594f95 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/parameter/ParametersInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/parameter/ParametersInterceptorTest.java
@@ -116,15 +116,17 @@ public class ParametersInterceptorTest extends XWorkTestCase {
pi.setParameters(action, vs, HttpParameters.create(params).build());
// then
- assertEquals(3, action.getActionMessages().size());
+ assertEquals(3, action.getActionErrors().size());
- String msg1 = action.getActionMessage(0);
- String msg2 = action.getActionMessage(1);
- String msg3 = action.getActionMessage(2);
+ List actionErrors = new ArrayList<>(action.getActionErrors());
- assertEquals("Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
- assertEquals("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)'", msg2);
- assertEquals("Error setting expression 'top['name'](0)' with value 'true'", msg3);
+ String msg1 = actionErrors.get(0);
+ String msg2 = actionErrors.get(1);
+ String msg3 = actionErrors.get(2);
+
+ assertEquals("Unexpected Exception caught setting 'expression' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
+ assertEquals("Unexpected Exception caught setting 'name' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: 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)'", msg2);
+ assertEquals("Unexpected Exception caught setting 'top['name'](0)' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'top['name'](0)' with value 'true'", msg3);
assertNull(action.getName());
}
@@ -201,15 +203,16 @@ public class ParametersInterceptorTest extends XWorkTestCase {
pi.setParameters(action, vs, HttpParameters.create(params).build());
// then
- assertEquals(3, action.getActionMessages().size());
+ assertEquals(3, action.getActionErrors().size());
- String msg1 = action.getActionMessage(0);
- String msg2 = action.getActionMessage(1);
- String msg3 = action.getActionMessage(2);
+ List actionErrors = new ArrayList<>(action.getActionErrors());
+ String msg1 = actionErrors.get(0);
+ String msg2 = actionErrors.get(1);
+ String msg3 = actionErrors.get(2);
- 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);
+ assertEquals("Unexpected Exception caught setting 'class.classLoader.defaultAssertionStatus' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
+ assertEquals("Unexpected Exception caught setting 'class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
+ assertEquals("Unexpected Exception caught setting 'model.class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
assertFalse(excluded.get(pollution1));
assertFalse(excluded.get(pollution2));
@@ -582,8 +585,8 @@ public class ParametersInterceptorTest extends XWorkTestCase {
container.inject(config.getInterceptors().get(0).getInterceptor());
ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap());
proxy.execute();
- final String actionMessage = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0];
- assertTrue(actionMessage.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
+ final String actionError = "" + ((SimpleAction) proxy.getAction()).getActionErrors().toArray()[0];
+ assertTrue(actionError.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
}
public void testNonexistentParametersAreIgnoredInProductionMode() throws Exception {
@@ -1014,59 +1017,65 @@ public class ParametersInterceptorTest extends XWorkTestCase {
class ValidateAction implements ValidationAware {
private final List messages = new LinkedList<>();
+ private final List errors = new LinkedList<>();
private String name;
+ @Override
public void setActionErrors(Collection errorMessages) {
}
+ @Override
public Collection getActionErrors() {
- return null;
+ return errors;
}
+ @Override
public void setActionMessages(Collection messages) {
}
+ @Override
public Collection getActionMessages() {
return messages;
}
+ @Override
public void setFieldErrors(Map> errorMap) {
}
+ @Override
public Map> getFieldErrors() {
return null;
}
+ @Override
public void addActionError(String anErrorMessage) {
+ errors.add(anErrorMessage);
}
+ @Override
public void addActionMessage(String aMessage) {
messages.add(aMessage);
}
+ @Override
public void addFieldError(String fieldName, String errorMessage) {
}
+ @Override
public boolean hasActionErrors() {
- return false;
+ return !errors.isEmpty();
}
+ @Override
public boolean hasActionMessages() {
return !messages.isEmpty();
}
- public boolean hasErrors() {
- return false;
- }
-
+ @Override
public boolean hasFieldErrors() {
return false;
}
- public String getActionMessage(int index) {
- return messages.get(index);
- }
-
public String getName() {
return name;
}
diff --git a/core/src/test/java/org/apache/struts2/url/StrutsQueryStringParserTest.java b/core/src/test/java/org/apache/struts2/url/StrutsQueryStringParserTest.java
index c8183725b..8108a8d01 100644
--- a/core/src/test/java/org/apache/struts2/url/StrutsQueryStringParserTest.java
+++ b/core/src/test/java/org/apache/struts2/url/StrutsQueryStringParserTest.java
@@ -112,6 +112,14 @@ public class StrutsQueryStringParserTest {
assertEquals("test", queryParameters.getQueryFragment());
}
+ @Test
+ public void shouldHandleOnlyFragment() {
+ QueryStringParser.Result queryParameters = parser.parse("#test");
+
+ assertTrue(queryParameters.getQueryParams().isEmpty());
+ assertEquals("test", queryParameters.getQueryFragment());
+ }
+
@Before
public void setUp() throws Exception {
this.parser = new StrutsQueryStringParser(new StrutsUrlDecoder());
diff --git a/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java
index 633598837..64020d598 100644
--- a/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java
+++ b/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java
@@ -2068,6 +2068,42 @@ public class URLTagTest extends AbstractUITagTest {
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
+ public void testQueryParamsAndFragment() throws Exception {
+ request.setRequestURI("/public/about");
+ tag.setAction("company");
+ tag.setValue("/books?hl=en&lr=Y&redir_esc=y#v=twopage&q&f=false");
+ tag.setEscapeAmp("false");
+
+ tag.doStartTag();
+ tag.doEndTag();
+
+ assertEquals("/books?hl=en&lr=Y&redir_esc=y#v=twopage&q&f=false", writer.toString());
+ }
+
+ public void testDoubleEqualSigns() throws Exception {
+ request.setRequestURI("/public/about");
+ tag.setAction("company");
+ tag.setValue("/PublicationsDetail.aspx?ID=GjTu91suYQI=&t=1");
+ tag.setEscapeAmp("false");
+
+ tag.doStartTag();
+ tag.doEndTag();
+
+ assertEquals("/PublicationsDetail.aspx?ID=GjTu91suYQI%3D&t=1", writer.toString());
+ }
+
+ public void testOnlyFragment() throws Exception {
+ request.setRequestURI("/public/about");
+ tag.setAction("company");
+ tag.setValue("/books#v=twopage&q&f=false");
+ tag.setEscapeAmp("false");
+
+ tag.doStartTag();
+ tag.doEndTag();
+
+ assertEquals("/books#v=twopage&q&f=false", writer.toString());
+ }
+
@Override
protected void setUp() throws Exception {
super.setUp();
diff --git a/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java b/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
index 3838ca9ae..885665a12 100644
--- a/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
+++ b/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
@@ -19,76 +19,72 @@
package com.opensymphony.xwork2.ognl;
import com.opensymphony.xwork2.ActionProxy;
-import com.opensymphony.xwork2.XWorkTestCase;
+import com.opensymphony.xwork2.XWorkJUnit4TestCase;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
+import org.junit.Before;
+import org.junit.Test;
import java.lang.reflect.Member;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
-public class SecurityMemberAccessProxyTest extends XWorkTestCase {
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class SecurityMemberAccessProxyTest extends XWorkJUnit4TestCase {
+
+ private static final String PROXY_MEMBER_METHOD = "isExposeProxy";
+ private static final String TEST_SUB_BEAN_CLASS_METHOD = "getIssueId";
+
private Map context;
private ActionProxy proxy;
- private Map members;
- private final SecurityMemberAccess sma = new SecurityMemberAccess(true);
- private final String PROXY_MEMBER_METHOD = "isExposeProxy";
- private final String TEST_SUB_BEAN_CLASS_METHOD = "setIssueId";
+ private final SecurityMemberAccess sma = new SecurityMemberAccess(null, null);
+ private Member proxyObjectProxyMember;
+ private Member proxyObjectNonProxyMember;
+
+ @Before
@Override
public void setUp() throws Exception {
- super.setUp();
-
- context = new HashMap<>();
- // Set up XWork
XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("com/opensymphony/xwork2/spring/actionContext-xwork.xml");
- container.inject(provider);
loadConfigurationProviders(provider);
- // Setup proxy object
- setupProxy();
- }
-
- public void testProxyAccessIsBlocked() throws Exception {
- members.values().forEach(member -> {
- // When disallowProxyObjectAccess is set to true, and disallowProxyMemberAccess is set to false, the proxy access is blocked
- sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
- sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
- assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
-
- // When disallowProxyObjectAccess is set to true, and disallowProxyMemberAccess is set to true, the proxy access is blocked
- sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
- sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
- assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
- });
-
- // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to true, the proxy member access is blocked
- sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
- sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
- assertFalse(sma.isAccessible(context, proxy.getAction(), members.get(PROXY_MEMBER_METHOD), ""));
- }
-
- public void testProxyAccessIsAccessible() throws Exception {
- members.values().forEach(member -> {
- // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to false, the proxy access is allowed
- sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
- sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
- assertTrue(sma.isAccessible(context, proxy.getAction(), member, ""));
- });
-
- // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to true, the original class member access is allowed
- sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
- sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
- assertTrue(sma.isAccessible(context, proxy.getAction(), members.get(TEST_SUB_BEAN_CLASS_METHOD), ""));
- }
-
- private void setupProxy() throws NoSuchMethodException {
+ context = new HashMap<>();
proxy = actionProxyFactory.createActionProxy(null, "chaintoAOPedTestSubBeanAction", null, context);
+ proxyObjectProxyMember = proxy.getAction().getClass().getMethod(PROXY_MEMBER_METHOD);
+ proxyObjectNonProxyMember = proxy.getAction().getClass().getMethod(TEST_SUB_BEAN_CLASS_METHOD);
+ }
- members = new HashMap<>();
- // method is proxy member
- members.put(PROXY_MEMBER_METHOD, proxy.getAction().getClass().getMethod(PROXY_MEMBER_METHOD));
- // method is not proxy member but from POJO class
- members.put(TEST_SUB_BEAN_CLASS_METHOD, proxy.getAction().getClass().getMethod(TEST_SUB_BEAN_CLASS_METHOD, String.class));
+ /**
+ * When {@code disallowProxyObjectAccess} is {@code true}, proxy access is blocked irrespective of
+ * {@code disallowProxyMemberAccess} value and irrespective of whether the member itself originates from the proxy.
+ */
+ @Test
+ public void disallowProxyObjectAccess() {
+ sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
+ Arrays.asList(proxyObjectProxyMember, proxyObjectNonProxyMember).forEach(member ->
+ Arrays.asList(Boolean.TRUE, Boolean.FALSE).forEach(disallowProxyMemberAccess -> {
+ sma.useDisallowProxyMemberAccess(disallowProxyMemberAccess.toString());
+ assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
+ })
+ );
+ }
+
+ @Test
+ public void disallowProxyMemberAccess() {
+ sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
+ sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
+ assertFalse(sma.isAccessible(context, proxy.getAction(), proxyObjectProxyMember, ""));
+ assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectNonProxyMember, ""));
+ }
+
+ @Test
+ public void allowAllProxyAccess() {
+ sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
+ sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
+ assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectProxyMember, ""));
+ assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectNonProxyMember, ""));
}
}
diff --git a/plugins/tiles/pom.xml b/plugins/tiles/pom.xml
index a7ca5be29..4d1f8fd6d 100644
--- a/plugins/tiles/pom.xml
+++ b/plugins/tiles/pom.xml
@@ -40,7 +40,7 @@
org.codehaus.mojo
exec-maven-plugin
- 3.2.0
+ 3.3.0
compile
diff --git a/pom.xml b/pom.xml
index c06783f7e..00530fb0b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -352,7 +352,7 @@
org.apache.maven.plugins
maven-enforcer-plugin
- 3.4.1
+ 3.5.0
enforce
@@ -630,7 +630,7 @@
org.apache.felix
org.apache.felix.main
- 6.0.3
+ 7.0.5
org.apache.felix
@@ -843,7 +843,7 @@
commons-validator
commons-validator
- 1.8.0
+ 1.9.0