This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 8.5.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/8.5.x by this push: new 48dd609fd1 Fix BZ 65736 replace forceString with a String setter lookup 48dd609fd1 is described below commit 48dd609fd193dbe8dd94fd231c45d987da6c359f Author: Mark Thomas <ma...@apache.org> AuthorDate: Wed Mar 30 12:37:26 2022 +0100 Fix BZ 65736 replace forceString with a String setter lookup --- java/org/apache/naming/factory/BeanFactory.java | 77 ++++---------- .../apache/naming/factory/LocalStrings.properties | 1 + .../org/apache/naming/factory/TestBeanFactory.java | 67 ++++++++++++ test/org/apache/naming/factory/TesterBean.java | 41 ++++++++ webapps/docs/changelog.xml | 6 ++ webapps/docs/jndi-resources-howto.xml | 112 +++------------------ 6 files changed, 151 insertions(+), 153 deletions(-) diff --git a/java/org/apache/naming/factory/BeanFactory.java b/java/org/apache/naming/factory/BeanFactory.java index 7a42991b05..1f207cd712 100644 --- a/java/org/apache/naming/factory/BeanFactory.java +++ b/java/org/apache/naming/factory/BeanFactory.java @@ -19,13 +19,9 @@ package org.apache.naming.factory; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Enumeration; -import java.util.HashMap; import java.util.Hashtable; -import java.util.Locale; -import java.util.Map; import javax.naming.Context; import javax.naming.Name; @@ -34,6 +30,8 @@ import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; import org.apache.naming.ResourceRef; import org.apache.naming.StringManager; @@ -92,6 +90,8 @@ public class BeanFactory implements ObjectFactory { private static final StringManager sm = StringManager.getManager(BeanFactory.class); + private final Log log = LogFactory.getLog(BeanFactory.class); // Not static + /** * Create a new Bean instance. * @@ -125,44 +125,14 @@ public class BeanFactory implements ObjectFactory { Object bean = beanClass.getConstructor().newInstance(); - /* Look for properties with explicitly configured setter */ + // Look for the removed forceString option RefAddr ra = ref.get("forceString"); - Map<String, Method> forced = new HashMap<>(); - String value; - if (ra != null) { - value = (String)ra.getContent(); - Class<?> paramTypes[] = new Class[1]; - paramTypes[0] = String.class; - String setterName; - int index; - - /* Items are given as comma separated list */ - for (String param: value.split(",")) { - param = param.trim(); - /* A single item can either be of the form name=method - * or just a property name (and we will use a standard - * setter) */ - index = param.indexOf('='); - if (index >= 0) { - setterName = param.substring(index + 1).trim(); - param = param.substring(0, index).trim(); - } else { - setterName = "set" + - param.substring(0, 1).toUpperCase(Locale.ENGLISH) + - param.substring(1); - } - try { - forced.put(param, beanClass.getMethod(setterName, paramTypes)); - } catch (NoSuchMethodException|SecurityException ex) { - throw new NamingException - ("Forced String setter " + setterName + - " not found for property " + param); - } - } + log.warn(sm.getString("beanFactory.noForceString")); } Enumeration<RefAddr> e = ref.getAll(); + String value; while (e.hasMoreElements()) { @@ -180,28 +150,13 @@ public class BeanFactory implements ObjectFactory { Object[] valueArray = new Object[1]; - /* Shortcut for properties with explicitly configured setter */ - Method method = forced.get(propName); - if (method != null) { - valueArray[0] = value; - try { - method.invoke(bean, valueArray); - } catch (IllegalAccessException| - IllegalArgumentException| - InvocationTargetException ex) { - throw new NamingException - ("Forced String setter " + method.getName() + - " threw exception for property " + propName); - } - continue; - } - int i = 0; for (i = 0; i < pda.length; i++) { if (pda[i].getName().equals(propName)) { Class<?> propType = pda[i].getPropertyType(); + Method setProp = pda[i].getWriteMethod(); if (propType.equals(String.class)) { valueArray[0] = value; @@ -221,12 +176,22 @@ public class BeanFactory implements ObjectFactory { valueArray[0] = Double.valueOf(value); } else if (propType.equals(Boolean.class) || propType.equals(boolean.class)) { valueArray[0] = Boolean.valueOf(value); + } else if (setProp != null) { + // This is a Tomcat specific extension and is not part of the + // Java Bean specification. + String setterName = setProp.getName(); + try { + setProp = bean.getClass().getMethod(setterName, String.class); + valueArray[0] = value; + } catch (NoSuchMethodException nsme) { + throw new NamingException(sm.getString( + "beanFactory.noStringConversion", propName, propType.getName())); + } } else { - throw new NamingException( - sm.getString("beanFactory.noStringConversion", propName, propType.getName())); + throw new NamingException(sm.getString( + "beanFactory.noStringConversion", propName, propType.getName())); } - Method setProp = pda[i].getWriteMethod(); if (setProp != null) { setProp.invoke(bean, valueArray); } else { diff --git a/java/org/apache/naming/factory/LocalStrings.properties b/java/org/apache/naming/factory/LocalStrings.properties index a4d1d570f7..cbd43355ee 100644 --- a/java/org/apache/naming/factory/LocalStrings.properties +++ b/java/org/apache/naming/factory/LocalStrings.properties @@ -14,6 +14,7 @@ # limitations under the License. beanFactory.classNotFound=Class not found: [{0}] +beanFactory.noForceString=The forceString option has been removed as a security hardening measure. Instead, if the setter method doesn't use String, a primitive or a primitive wrapper, the factory will look for a method with the same name as the setter that accepts a String and use that if found. beanFactory.noSetMethod=No set method found for property [{0}] beanFactory.noStringConversion=String conversion for property [{0}] of type [{1}] not available beanFactory.readOnlyProperty=Write not allowed for property [{0}] diff --git a/test/org/apache/naming/factory/TestBeanFactory.java b/test/org/apache/naming/factory/TestBeanFactory.java new file mode 100644 index 0000000000..ec85ff6136 --- /dev/null +++ b/test/org/apache/naming/factory/TestBeanFactory.java @@ -0,0 +1,67 @@ +/* + * 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 org.apache.naming.factory; + +import javax.naming.StringRefAddr; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.naming.ResourceRef; + +public class TestBeanFactory { + + private static final String IP_ADDRESS = "127.0.0.1"; + + @Test + public void testForceStringAlternativeWithout() throws Exception { + doTestForceStringAlternatove(false); + } + + + @Test + public void testForceStringAlternativeWith() throws Exception { + doTestForceStringAlternatove(true); + } + + + private void doTestForceStringAlternatove(boolean useForceString) throws Exception { + + // Create the resource definition + ResourceRef resourceRef = new ResourceRef(TesterBean.class.getName(), null, null, null, false); + StringRefAddr server = new StringRefAddr("server", IP_ADDRESS); + resourceRef.add(server); + if (useForceString) { + StringRefAddr force = new StringRefAddr("forceString", "server"); + resourceRef.add(force); + } + + // Create the factory + BeanFactory factory = new BeanFactory(); + + // Use the factory to create the resource from the definition + Object obj = factory.getObjectInstance(resourceRef, null, null, null); + + // Check the correct type was created + Assert.assertNotNull(obj); + Assert.assertEquals(obj.getClass(), TesterBean.class); + // Check the server field was set + TesterBean result = (TesterBean) obj; + Assert.assertNotNull(result.getServer()); + Assert.assertEquals(IP_ADDRESS, result.getServer().getHostAddress()); + } +} diff --git a/test/org/apache/naming/factory/TesterBean.java b/test/org/apache/naming/factory/TesterBean.java new file mode 100644 index 0000000000..d2beef275c --- /dev/null +++ b/test/org/apache/naming/factory/TesterBean.java @@ -0,0 +1,41 @@ +/* + * 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 org.apache.naming.factory; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class TesterBean { + + private InetAddress server; + + public InetAddress getServer() { + return server; + } + + public void setServer(InetAddress server) { + this.server = server; + } + + public void setServer(String server) { + try { + this.server = InetAddress.getByName(server); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 743cdc76bd..77dff10b0a 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -107,6 +107,12 @@ <section name="Tomcat 8.5.79 (schultz)" rtext="in development"> <subsection name="Catalina"> <changelog> + <fix> + <bug>65736</bug>: Disable the <code>forceString</code> option for the + JNDI <code>BeanFactory</code> and replace it with an automatic search + for an alternative setter with the same name that accepts a + <code>String</code>. This is a security hardening measure. (markt) + </fix> <scode> <bug>65853</bug>: Refactor the <code>CsrfPreventionFilter</code> to make it easier for sub-classes to modify the nonce generation and storage. diff --git a/webapps/docs/jndi-resources-howto.xml b/webapps/docs/jndi-resources-howto.xml index d8a20fcf24..391e565e64 100644 --- a/webapps/docs/jndi-resources-howto.xml +++ b/webapps/docs/jndi-resources-howto.xml @@ -328,103 +328,21 @@ writer.println("foo = " + bean.getFoo() + ", bar = " + <code>foo</code> property (although we could have), the bean will contain whatever default value is set up by its constructor.</p> - <p>Some beans have properties with types that cannot automatically be - converted from a string value. Setting such properties using the Tomcat - BeanFactory will fail with a NamingException. In cases were those beans - provide methods to set the properties from a string value, the Tomcat - BeanFactory can be configured to use these methods. The configuration is - done with the <code>forceString</code> attribute.</p> - - <p>Assume our bean looks like this:</p> - -<source><![CDATA[package com.mycompany; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -public class MyBean2 { - - private InetAddress local = null; - - public InetAddress getLocal() { - return local; - } - - public void setLocal(InetAddress ip) { - local = ip; - } - - public void setLocal(String localHost) { - try { - local = InetAddress.getByName(localHost); - } catch (UnknownHostException ex) { - } - } - - private InetAddress remote = null; - - public InetAddress getRemote() { - return remote; - } - - public void setRemote(InetAddress ip) { - remote = ip; - } - - public void host(String remoteHost) { - try { - remote = InetAddress.getByName(remoteHost); - } catch (UnknownHostException ex) { - } - } - -}]]></source> - - <p>The bean has two properties, both are of type <code>InetAddress</code>. - The first property <code>local</code> has an additional setter taking a - string argument. By default the Tomcat BeanFactory would try to use the - automatically detected setter with the same argument type as the property - type and then throw a NamingException, because it is not prepared to convert - the given string attribute value to <code>InetAddress</code>. - We can tell the Tomcat BeanFactory to use the other setter like that:</p> - -<source><![CDATA[<Context ...> - ... - <Resource name="bean/MyBeanFactory" auth="Container" - type="com.mycompany.MyBean2" - factory="org.apache.naming.factory.BeanFactory" - forceString="local" - local="localhost"/> - ... -</Context>]]></source> - - <p>The bean property <code>remote</code> can also be set from a string, - but one has to use the non-standard method name <code>host</code>. - To set <code>local</code> and <code>remote</code> use the following - configuration:</p> - -<source><![CDATA[<Context ...> - ... - <Resource name="bean/MyBeanFactory" auth="Container" - type="com.mycompany.MyBean2" - factory="org.apache.naming.factory.BeanFactory" - forceString="local,remote=host" - local="localhost" - remote="tomcat.apache.org"/> - ... -</Context>]]></source> - - <p>Multiple property descriptions can be combined in - <code>forceString</code> by concatenation with comma as a separator. - Each property description consists of either only the property name - in which case the BeanFactory calls the setter method. Or it consist - of <code>name=method</code> in which case the property named - <code>name</code> is set by calling method <code>method</code>. - For properties of types <code>String</code> or of primitive type - or of their associated primitive wrapper classes using - <code>forceString</code> is not needed. The correct setter will be - automatically detected and argument conversion will be applied.</p> - + <p>If the bean property is of type <code>String</code>, the BeanFactory + will call the property setter using the provided property value. If the + bean property type is a primitive or a primitive wrapper, the the + BeanFactory will convert the value to the appropriate primitive or + primitive wrapper and then use that value when calling the setter. Some + beans have properties with types that cannot automatically be converted + from <code>String</code>. If the bean provides an alternative setter with + the same name that does take a <code>String</code>, the BeanFactory will + attempt to use that setter. If the BeanFactory cannot use the value or + perform an appropriate conversion, setting the property will fail with a + NamingException.</p> + + <p>The <code>forceString</code> property available in earlier Tomcat + releases has been removed as a security hardening measure.</p> + </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org