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 <garydgreg...@gmail.com> 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")); + } +}