This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git


The following commit(s) were added to refs/heads/master by this push:
     new bd20740d Add 
org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS
bd20740d is described below

commit bd20740da25b69552ddef8523beec0837297eaf9
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Sun May 25 09:07:45 2025 -0400

    Add 
org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS
---
 src/changes/changes.xml                            |  16 +++
 .../commons/beanutils2/PropertyUtilsBean.java      |   1 +
 .../SuppressPropertiesBeanIntrospector.java        |  14 ++-
 .../org/apache/commons/beanutils2/TestEnum.java    |  33 +++++++
 .../apache/commons/beanutils2/package-info.java    |   8 +-
 .../beanutils2/bugs/EnumDeclaringClassTest.java    | 108 +++++++++++++++++++++
 6 files changed, 178 insertions(+), 2 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4f82d3cc..e0ee838b 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -54,6 +54,7 @@
       <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ResultSetIterator.set(String, Object) now throws 
IllegalArgumentException instead of RuntimeException to wrap cases of 
SQLException.</action>
       <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ResultSetIterator.set(String, String, Object) now throws 
IllegalArgumentException instead of RuntimeException to wrap cases of 
SQLException.</action>
       <!-- ADD -->
+      <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS.</action>
       <!-- UPDATE -->
       <action type="update" dev="ggregory" due-to="Gary Gregory, 
Dependabot">Bump org.apache.commons:commons-parent from 78 to 84 #348.</action>
       <action type="update" dev="ggregory" due-to="Gary Gregory">Bump 
commons-logging:commons-logging from 1.3.4 to 1.3.5.</action>
@@ -258,6 +259,21 @@
         Do not implement Serializable.
       </action>
     </release>
+    <release version="1.11.0" date="YYYY-MM-DD" description="This is a 
maintenance release and requires Java 8.">
+      <!-- FIX -->
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">BeanComparator.compare(T, T) now throws IllegalArgumentException 
instead of RuntimeException to wrap all cases of 
ReflectiveOperationException.</action>
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">MappedMethodReference.get() now throws IllegalStateException instead 
of RuntimeException to wrap cases of NoSuchMethodException.</action>
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ResultSetIterator.get(String) now throws IllegalArgumentException 
instead of RuntimeException to wrap cases of SQLException.</action>
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ResultSetIterator.hasNext() now throws IllegalStateException instead 
of RuntimeException to wrap cases of SQLException.</action>
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ResultSetIterator.next() now throws IllegalStateException instead of 
RuntimeException to wrap cases of SQLException.</action>
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ResultSetIterator.set(String, Object) now throws 
IllegalArgumentException instead of RuntimeException to wrap cases of 
SQLException.</action>
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ResultSetIterator.set(String, String, Object) now throws 
IllegalArgumentException instead of RuntimeException to wrap cases of 
SQLException.</action>
+      <!-- ADD -->
+      <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS.</action>
+      <!-- UPDATE -->
+      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump 
org.apache.commons:commons-parent from 81 to 84.</action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump 
commons-logging:commons-logging from 1.3.4 to 1.3.5.</action>
+    </release>
     <release version="1.10.1" date="2025-01-31" description="This is a 
maintenance release and requires Java 8.">
       <!-- FIX -->
       <action type="fix" issue="BEANUTILS-541" dev="ggregory" due-to="Sergey 
Chernov">FluentPropertyBeanIntrospector concurrency issue (backport to 1.X) 
#325.</action>
diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java 
b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
index d1fe015b..f16f06e3 100644
--- a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
@@ -1201,6 +1201,7 @@ public class PropertyUtilsBean {
         introspectors.clear();
         introspectors.add(DefaultBeanIntrospector.INSTANCE);
         introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
+        
introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS);
     }
 
     /**
diff --git 
a/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java
 
b/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java
index bb297ffd..8d9b3562 100644
--- 
a/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java
+++ 
b/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java
@@ -35,6 +35,7 @@ import java.util.Set;
  * @since 1.9.2
  */
 public class SuppressPropertiesBeanIntrospector implements BeanIntrospector {
+
     /**
      * A specialized instance which is configured to suppress the special 
{@code class} properties of Java beans. Unintended access to the property
      * {@code class} (which is common to all Java objects) can be a security 
risk because it also allows access to the class loader. Adding this instance as
@@ -42,7 +43,18 @@ public class SuppressPropertiesBeanIntrospector implements 
BeanIntrospector {
      */
     public static final SuppressPropertiesBeanIntrospector SUPPRESS_CLASS = 
new SuppressPropertiesBeanIntrospector(Collections.singleton("class"));
 
-    /** A set with the names of the properties to be suppressed. */
+    /**
+     * A specialized instance which is configured to suppress the special 
{@code class} properties of Java beans. Unintended access to the call for
+     * {@code declaringClass} (which is common to all Java {@code enum}) can 
be a security risk because it also allows access to the class loader. Adding 
this
+     * instance as {@code BeanIntrospector} to an instance of {@code 
PropertyUtilsBean} suppresses the {@code class} property; it can then no longer 
be
+     * accessed.
+     *
+     * @since 2.0.0-M2
+     */
+    public static final SuppressPropertiesBeanIntrospector 
SUPPRESS_DECLARING_CLASS = new SuppressPropertiesBeanIntrospector(
+            Collections.singleton("declaringClass"));
+
+/** A set with the names of the properties to be suppressed. */
     private final Set<String> propertyNames;
 
     /**
diff --git a/src/main/java/org/apache/commons/beanutils2/TestEnum.java 
b/src/main/java/org/apache/commons/beanutils2/TestEnum.java
new file mode 100644
index 00000000..a64142a0
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils2/TestEnum.java
@@ -0,0 +1,33 @@
+/*
+ * 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
+ *
+ *      https://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 org.apache.commons.beanutils2;
+
+/**
+ * An {@code enum} test fixture.
+ */
+public enum TestEnum {
+
+    /** Test fixture. */
+    A,
+
+    /** Test fixture. */
+    B,
+
+    /** Test fixture. */
+    C
+}
diff --git a/src/main/java/org/apache/commons/beanutils2/package-info.java 
b/src/main/java/org/apache/commons/beanutils2/package-info.java
index a2bd66f2..50b8a996 100644
--- a/src/main/java/org/apache/commons/beanutils2/package-info.java
+++ b/src/main/java/org/apache/commons/beanutils2/package-info.java
@@ -424,7 +424,7 @@
  *
  * <p>A good use case for suppressing properties is the special {@code class}
  * property which is per default available for all beans; it is generated from 
the
- * {@code getClass()</code> method inherited from <code>Object} which follows 
the
+ * {@code getClass()} method inherited from {@code Object} which follows the
  * naming conventions for property get methods. Exposing this property in an
  * uncontrolled way can lead to a security vulnerability as it allows access to
  * the class loader. More information can be found at
@@ -437,6 +437,12 @@
  * {@code SUPPRESS_CLASS} constant of
  * {@code SuppressPropertiesBeanIntrospector}.</p>
  *
+ * <p>Another problematic property is the {@code enum} "declaredClass" 
property,
+ * through which you can also access that class' class loader. The {@code 
SuppressPropertiesBeanIntrospector}
+ * provides {@code SUPPRESS_DECLARING_CLASS} to workaround this issue.</p>
+ *
+ * <p>Both {@code SUPPRESS_CLASS} and {@code SUPPRESS_DECLARING_CLASS} are 
enabled by default.</p>
+ *
  * <a id="dynamic"></a>
  * <h2>3. Dynamic Beans (DynaBeans)</h2>
  *
diff --git 
a/src/test/java/org/apache/commons/beanutils2/bugs/EnumDeclaringClassTest.java 
b/src/test/java/org/apache/commons/beanutils2/bugs/EnumDeclaringClassTest.java
new file mode 100644
index 00000000..e0d1c229
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/beanutils2/bugs/EnumDeclaringClassTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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
+ *
+ *      https://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 org.apache.commons.beanutils2.bugs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.beanutils2.BeanUtilsBean;
+import org.apache.commons.beanutils2.PropertyUtilsBean;
+import org.apache.commons.beanutils2.SuppressPropertiesBeanIntrospector;
+import org.apache.commons.beanutils2.TestEnum;
+import org.junit.jupiter.api.Test;
+
+public class EnumDeclaringClassTest {
+
+    public static class Fixture {
+
+        String name = "default";
+        TestEnum testEnum = TestEnum.A;
+
+        public String getName() {
+            return name;
+        }
+
+        public TestEnum getTestEnum() {
+            return testEnum;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
+        public void setTestEnum(final TestEnum day) {
+            this.testEnum = day;
+        }
+    }
+
+    /**
+     * Allow opt-out to make your app less secure but allow access to 
"declaringClass".
+     */
+    @Test
+    public void testAllowAccessToClassPropertyFromBeanUtilsBean() throws 
ReflectiveOperationException {
+        final BeanUtilsBean bub = new BeanUtilsBean();
+        final PropertyUtilsBean propertyUtilsBean = bub.getPropertyUtils();
+        
propertyUtilsBean.removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS);
+        final Fixture fixture = new Fixture();
+        final String string = bub.getProperty(fixture, 
"testEnum.declaringClass");
+        assertEquals(TestEnum.class.getName(), string);
+        final Class<TestEnum> teClass = assertInstanceOf(Class.class, 
propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass"));
+        final ClassLoader classLoader = teClass.getClassLoader();
+        assertNotNull(classLoader);
+        assertNotNull(bub.getProperty(fixture, 
"testEnum.declaringClass.classLoader"));
+        assertInstanceOf(ClassLoader.class, 
propertyUtilsBean.getNestedProperty(fixture, 
"testEnum.declaringClass.classLoader"));
+    }
+
+    /**
+     * Allow opt-out to make your app less secure but allow access to 
"declaringClass".
+     */
+    @Test
+    public void testAllowAccessToClassPropertyFromPropertyUtilsBean() throws 
ReflectiveOperationException {
+        final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
+        
propertyUtilsBean.removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS);
+        final Fixture fixture = new Fixture();
+        final Object cls = propertyUtilsBean.getNestedProperty(fixture, 
"testEnum.declaringClass");
+        final Class<TestEnum> teClass = assertInstanceOf(Class.class, cls);
+        final ClassLoader classLoader = teClass.getClassLoader();
+        assertNotNull(classLoader);
+        assertInstanceOf(ClassLoader.class, 
propertyUtilsBean.getNestedProperty(fixture, 
"testEnum.declaringClass.classLoader"));
+    }
+
+    /**
+     * By default opt-in to security that does not allow access to 
"declaringClass".
+     */
+    @Test
+    public void testSuppressClassPropertyByDefaultFromBeanUtilsBean() throws 
ReflectiveOperationException {
+        final Fixture fixture = new Fixture();
+        final BeanUtilsBean bub = new BeanUtilsBean();
+        assertThrows(NoSuchMethodException.class, () -> 
bub.getProperty(fixture, "testEnum.declaringClass.classLoader"));
+        assertThrows(NoSuchMethodException.class, () -> 
bub.getPropertyUtils().getNestedProperty(fixture, 
"testEnum.declaringClass.classLoader"));
+    }
+
+    /**
+     * By default opt-in to security that does not allow access to 
"declaringClass".
+     */
+    @Test
+    public void testSuppressClassPropertyByDefaultFromPropertyUtilsBean() 
throws ReflectiveOperationException {
+        final Fixture fixture = new Fixture();
+        final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
+        assertThrows(NoSuchMethodException.class, () -> 
propertyUtilsBean.getNestedProperty(fixture, 
"testEnum.declaringClass.classLoader"));
+    }
+}

Reply via email to