This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch 1.X
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git
The following commit(s) were added to refs/heads/1.X by this push:
new 28ad955a Add
org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS
28ad955a is described below
commit 28ad955a1613ed5885870cc7da52093c1ce739dc
Author: Gary Gregory <[email protected]>
AuthorDate: Sun May 25 09:07:32 2025 -0400
Add
org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS
---
pom.xml | 11 ++-
src/changes/changes.xml | 3 +-
.../commons/beanutils/PropertyUtilsBean.java | 1 +
.../SuppressPropertiesBeanIntrospector.java | 24 +++--
.../org/apache/commons/beanutils/package-info.java | 18 ++--
.../org/apache/commons/beanutils/TestEnum.java | 33 +++++++
.../beanutils/bugs/EnumDeclaringClassTest.java | 108 +++++++++++++++++++++
7 files changed, 180 insertions(+), 18 deletions(-)
diff --git a/pom.xml b/pom.xml
index f803e098..bf7133e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
- <version>1.10.2-SNAPSHOT</version>
+ <version>1.11.0-SNAPSHOT</version>
<name>Apache Commons BeanUtils</name>
<inceptionYear>2000</inceptionYear>
@@ -38,8 +38,8 @@
<commons.componentid>beanutils</commons.componentid>
<commons.main.branch>1.X</commons.main.branch>
<commons.release.branch>release-1.x</commons.release.branch>
- <commons.release.version>1.10.1</commons.release.version>
- <commons.release.next>1.10.2</commons.release.next>
+ <commons.release.version>1.11.0</commons.release.version>
+ <commons.release.next>1.11.1</commons.release.next>
<commons.jira.id>BEANUTILS</commons.jira.id>
<commons.jira.pid>12310460</commons.jira.pid>
<!-- Limit memory size see BEANUTILS-291; allow command-line override -->
@@ -99,6 +99,11 @@
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index fe9cdfb1..c652852e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -28,7 +28,7 @@
<title>Release Notes</title>
</properties>
<body>
- <release version="1.10.2" date="YYYY-MM-DD" description="This is a
maintenance release and requires Java 8.">
+ <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>
@@ -38,6 +38,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 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>
diff --git a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
index 7955f850..f7b1509b 100644
--- a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
@@ -1478,6 +1478,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/beanutils/SuppressPropertiesBeanIntrospector.java
b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
index c5fa1736..61b45f28 100644
---
a/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
+++
b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
@@ -36,16 +36,24 @@ 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 {@code BeanIntrospector} to
an instance
- * of {@code PropertyUtilsBean} suppresses the {@code class} property; it
can then no
- * longer be accessed.
+ * 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
+ * {@code BeanIntrospector} to an instance of {@code PropertyUtilsBean}
suppresses the {@code class} property; it can then no longer be accessed.
+ */
+ public static final SuppressPropertiesBeanIntrospector SUPPRESS_CLASS =
new SuppressPropertiesBeanIntrospector(Collections.singleton("class"));
+
+ /**
+ * 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 1.11.0
*/
- public static final SuppressPropertiesBeanIntrospector SUPPRESS_CLASS =
- new
SuppressPropertiesBeanIntrospector(Collections.singleton("class"));
+ 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/beanutils/package-info.java
b/src/main/java/org/apache/commons/beanutils/package-info.java
index 0f7e7740..252814cc 100644
--- a/src/main/java/org/apache/commons/beanutils/package-info.java
+++ b/src/main/java/org/apache/commons/beanutils/package-info.java
@@ -420,20 +420,26 @@
* then be removed if they have been detected by other
<code>BeanIntrospector</code>
* instances during processing of a bean class.</p>
*
- * <p>A good use case for suppressing properties is the special
<code>class</code>
+ * <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</code> 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
* <a href="https://issues.apache.org/jira/browse/BEANUTILS-463">
* https://issues.apache.org/jira/browse/BEANUTILS-463</a>.</p>
*
- * <p>Because the <code>class</code> property is undesired in many use cases
- * there is already an instance of
<code>SuppressPropertiesBeanIntrospector</code>
+ * <p>Because the {@code class} property is undesired in many use cases
+ * there is already an instance of {@code SuppressPropertiesBeanIntrospector}
* which is configured to suppress this property. It can be obtained via the
- * <code>SUPPRESS_CLASS</code> constant of
- * <code>SuppressPropertiesBeanIntrospector</code>.</p>
+ * {@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>
*
* <h2>3. Dynamic Beans (DynaBeans)</h2>
*
diff --git a/src/test/java/org/apache/commons/beanutils/TestEnum.java
b/src/test/java/org/apache/commons/beanutils/TestEnum.java
new file mode 100644
index 00000000..643a9d28
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils/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.beanutils;
+
+/**
+ * An {@code enum} test fixture.
+ */
+public enum TestEnum {
+
+ /** Test fixture. */
+ A,
+
+ /** Test fixture. */
+ B,
+
+ /** Test fixture. */
+ C
+}
diff --git
a/src/test/java/org/apache/commons/beanutils/bugs/EnumDeclaringClassTest.java
b/src/test/java/org/apache/commons/beanutils/bugs/EnumDeclaringClassTest.java
new file mode 100644
index 00000000..15edf16d
--- /dev/null
+++
b/src/test/java/org/apache/commons/beanutils/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.beanutils.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.beanutils.BeanUtilsBean;
+import org.apache.commons.beanutils.PropertyUtilsBean;
+import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
+import org.apache.commons.beanutils.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("class " + 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"));
+ }
+}