This is an automated email from the ASF dual-hosted git repository. kusal pushed a commit to branch WW-5350-allowlist in repository https://gitbox.apache.org/repos/asf/struts.git
commit 82647959b2a7db068bbb219fd07128e6b32fdc5b Author: Kusal Kithul-Godage <g...@kusal.io> AuthorDate: Fri Nov 3 18:10:57 2023 +1100 WW-5350 Refactor SecurityMemberAccess --- .../xwork2/ognl/SecurityMemberAccess.java | 168 +++++++++++++-------- .../xwork2/ognl/SecurityMemberAccessTest.java | 16 +- 2 files changed, 120 insertions(+), 64 deletions(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java index 232c33e5a..6301ac1a3 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java @@ -26,6 +26,7 @@ import org.apache.logging.log4j.Logger; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Member; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashSet; @@ -104,89 +105,130 @@ public class SecurityMemberAccess implements MemberAccess { public boolean isAccessible(Map context, Object target, Member member, String propertyName) { LOG.debug("Checking access for [target: {}, member: {}, property: {}]", target, member, propertyName); - final int memberModifiers = member.getModifiers(); - final Class<?> memberClass = member.getDeclaringClass(); - // target can be null in case of accessing static fields, since OGNL 3.2.8 - final Class<?> targetClass = Modifier.isStatic(memberModifiers) ? memberClass : target.getClass(); - if (!memberClass.isAssignableFrom(targetClass)) { - throw new IllegalArgumentException("Target does not match member!"); + if (target instanceof Class) { // Target may be of type Class for static members + if (!member.getDeclaringClass().equals(target)) { + throw new IllegalArgumentException("Target class does not match static member!"); + } + target = null; + } else { + if (target != null && !member.getDeclaringClass().isAssignableFrom(target.getClass())) { + throw new IllegalArgumentException("Target does not match member!"); + } } - if (disallowProxyMemberAccess && ProxyUtil.isProxyMember(member, target)) { - LOG.warn("Access to proxy is blocked! Target class [{}] of target [{}], member [{}]", targetClass, target, member); + if (!checkProxyMemberAccess(target, member)) { + LOG.warn("Access to proxy is blocked! Member class [{}] of target [{}], member [{}]", member.getDeclaringClass(), target, member); return false; } - if (!checkPublicMemberAccess(memberModifiers)) { + if (!checkPublicMemberAccess(member)) { LOG.warn("Access to non-public [{}] is blocked!", member); return false; } - if (!checkStaticFieldAccess(member, memberModifiers)) { + if (!checkStaticFieldAccess(member)) { LOG.warn("Access to static field [{}] is blocked!", member); return false; } - // it needs to be before calling #checkStaticMethodAccess() - if (checkEnumAccess(target, member)) { - LOG.trace("Allowing access to enum: target [{}], member [{}]", target, member); - return true; - } - - if (!checkStaticMethodAccess(member, memberModifiers)) { + if (!checkStaticMethodAccess(member)) { LOG.warn("Access to static method [{}] is blocked!", member); return false; } - if (isClassExcluded(memberClass)) { - LOG.warn("Declaring class of member type [{}] is excluded!", member); + if (!checkDefaultPackageAccess(target, member)) { return false; } - if (targetClass != memberClass && isClassExcluded(targetClass)) { - LOG.warn("Target class [{}] of target [{}] is excluded!", targetClass, target); + if (!checkExclusionList(target, member)) { return false; } - if (disallowDefaultPackageAccess) { - if (targetClass.getPackage() == null || targetClass.getPackage().getName().isEmpty()) { - LOG.warn("Class [{}] from the default package is excluded!", targetClass); - return false; - } - if (memberClass.getPackage() == null || memberClass.getPackage().getName().isEmpty()) { - LOG.warn("Class [{}] from the default package is excluded!", memberClass); - return false; - } + if (!isAcceptableProperty(propertyName)) { + return false; } - if (isPackageExcluded(targetClass)) { + return true; + } + + /** + * @return {@code true} if member access is allowed + */ + protected boolean checkExclusionList(Object target, Member member) { + Class<?> memberClass = member.getDeclaringClass(); + if (isClassExcluded(memberClass)) { + LOG.warn("Declaring class of member type [{}] is excluded!", memberClass); + return false; + } + if (isPackageExcluded(memberClass)) { LOG.warn("Package [{}] of target class [{}] of target [{}] is excluded!", - targetClass.getPackage(), - targetClass, + memberClass.getPackage(), + memberClass, target); return false; } + if (target == null || target.getClass() == memberClass) { + return true; + } + Class<?> targetClass = target.getClass(); + if (isClassExcluded(targetClass)) { + LOG.warn("Target class [{}] of target [{}] is excluded!", targetClass, target); + return false; + } + if (isPackageExcluded(targetClass)) { + LOG.warn("Package [{}] of member [{}] are excluded!", targetClass.getPackage(), member); + return false; + } + return true; + } - if (targetClass != memberClass && isPackageExcluded(memberClass)) { - LOG.warn("Package [{}] of member [{}] are excluded!", memberClass.getPackage(), member); + /** + * @return {@code true} if member access is allowed + */ + protected boolean checkDefaultPackageAccess(Object target, Member member) { + if (!disallowDefaultPackageAccess) { + return true; + } + Class<?> memberClass = member.getDeclaringClass(); + if (memberClass.getPackage() == null || memberClass.getPackage().getName().isEmpty()) { + LOG.warn("Class [{}] from the default package is excluded!", memberClass); + return false; + } + if (target == null || target.getClass() == memberClass) { + return true; + } + Class<?> targetClass = target.getClass(); + if (targetClass.getPackage() == null || targetClass.getPackage().getName().isEmpty()) { + LOG.warn("Class [{}] from the default package is excluded!", targetClass); return false; } + return true; + } - return isAcceptableProperty(propertyName); + /** + * @return {@code true} if member access is allowed + */ + protected boolean checkProxyMemberAccess(Object target, Member member) { + return !(disallowProxyMemberAccess && ProxyUtil.isProxyMember(member, target)); } /** * Check access for static method (via modifiers). - * + * <p> * Note: For non-static members, the result is always true. * - * @param member - * @param memberModifiers - * - * @return + * @return {@code true} if member access is allowed */ - protected boolean checkStaticMethodAccess(Member member, int memberModifiers) { - return !Modifier.isStatic(memberModifiers) || member instanceof Field; + protected boolean checkStaticMethodAccess(Member member) { + if (checkEnumAccess(member)) { + LOG.trace("Exempting Enum#values from static method check: class [{}]", member.getDeclaringClass()); + return true; + } + return member instanceof Field || !isStatic(member); + } + + private static boolean isStatic(Member member) { + return Modifier.isStatic(member.getModifiers()); } /** @@ -194,36 +236,33 @@ public class SecurityMemberAccess implements MemberAccess { * <p> * Note: For non-static members, the result is always true. * - * @param member - * @param memberModifiers - * @return + * @return {@code true} if member access is allowed */ - protected boolean checkStaticFieldAccess(Member member, int memberModifiers) { - if (Modifier.isStatic(memberModifiers) && member instanceof Field) { - return allowStaticFieldAccess; - } else { + protected boolean checkStaticFieldAccess(Member member) { + if (allowStaticFieldAccess) { return true; } + return !(member instanceof Field) || !isStatic(member); } /** * Check access for public members (via modifiers) - * <p> - * Returns true if-and-only-if the member is public. * - * @param memberModifiers - * @return + * @return {@code true} if member access is allowed */ - protected boolean checkPublicMemberAccess(int memberModifiers) { - return Modifier.isPublic(memberModifiers); + protected boolean checkPublicMemberAccess(Member member) { + return Modifier.isPublic(member.getModifiers()); } - protected boolean checkEnumAccess(Object target, Member member) { - if (target instanceof Class) { - final Class<?> clazz = (Class<?>) target; - return Enum.class.isAssignableFrom(clazz) && member.getName().equals("values"); - } - return false; + /** + * @return {@code true} if member access is allowed + */ + protected boolean checkEnumAccess(Member member) { + return member.getDeclaringClass().isEnum() + && isStatic(member) + && member instanceof Method + && member.getName().equals("values") + && ((Method) member).getParameterCount() == 0; } protected boolean isPackageExcluded(Class<?> clazz) { @@ -260,8 +299,11 @@ public class SecurityMemberAccess implements MemberAccess { return excludedClasses.contains(clazz.getName()); } + /** + * @return {@code true} if member access is allowed + */ protected boolean isAcceptableProperty(String name) { - return name == null || ((!isExcluded(name)) && isAccepted(name)); + return name == null || !isExcluded(name) && isAccepted(name); } protected boolean isAccepted(String paramName) { 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 f38a7dce6..830cdbb2c 100644 --- a/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java @@ -351,6 +351,16 @@ public class SecurityMemberAccessTest { assertTrue("Access to enums is blocked!", actual); } + @Test + public void testAccessEnum_alternateValues() throws Exception { + // when + Member alternateValues = MyValues.class.getMethod("values", String.class); + boolean actual = sma.isAccessible(context, MyValues.class, alternateValues, null); + + // then + assertFalse("Access to unrelated #values method not blocked!", actual); + } + @Test public void testAccessStaticMethod() throws Exception { // given @@ -875,7 +885,11 @@ interface FooBarInterface extends FooInterface, BarInterface { } enum MyValues { - ONE, TWO, THREE + ONE, TWO, THREE; + + public static MyValues[] values(String notUsed) { + return new MyValues[] {ONE, TWO, THREE}; + } } class StaticTester {