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

tv pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/turbine-core.git


The following commit(s) were added to refs/heads/trunk by this push:
     new c421b794 New configuration option "session.objectinputfilter"
c421b794 is described below

commit c421b794e95aa99444d9c04a522dc6143bb4279e
Author: Thomas Vandahl <[email protected]>
AuthorDate: Wed Jun 5 16:29:35 2024 +0200

    New configuration option "session.objectinputfilter"
---
 conf/test/CompleteTurbineResources.properties      |  6 ++
 src/changes/changes.xml                            | 13 ++-
 src/java/org/apache/turbine/TurbineConstants.java  |  6 ++
 src/java/org/apache/turbine/util/ObjectUtils.java  | 39 +++++++--
 .../org/apache/turbine/util/ObjectUtilsTest.java   | 96 ++++++++++++++++++++++
 5 files changed, 149 insertions(+), 11 deletions(-)

diff --git a/conf/test/CompleteTurbineResources.properties 
b/conf/test/CompleteTurbineResources.properties
index 4df93ad8..3e0bb02c 100644
--- a/conf/test/CompleteTurbineResources.properties
+++ b/conf/test/CompleteTurbineResources.properties
@@ -219,6 +219,12 @@ 
action.sessionvalidator=sessionvalidator.TemplateSessionValidator
 
 # session.timeout=1800
 
+# This is the ObjectInputFilter that limits the classes that can be 
+# deserialized into the session from persistent storage. If left commented 
+# out, all classes can be de-serialized.
+
+# 
session.objectinputfilter=!org.codehaus.groovy.runtime.**;!org.apache.commons.collections.functors.**;!org.apache.xalan*
+
 # This is the default action that builds up the AccessControlList for
 # the individual users session.
 
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9577f14f..5e52a928 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -24,13 +24,20 @@
   </properties>
 
   <body>
-     <release version="6.0-SNAPSHOT" date="in Git">
-       <action type="add" dev="gk">
+    <release version="6.1-SNAPSHOT" date="in Git">
+      <action type="add" dev="tv">
+        New configuration option "session.objectinputfilter" to limit the 
classes that can be 
+        deserialized into a session from persistent storage. This feature uses 
the 
+        ObjectInputFilter introduced in Java 9.
+      </action>
+    </release>
+    <release version="6.0" date="2024-02-14">
+      <action type="add" dev="gk">
         Provide mechanism to allow auto loading of Turbine (and Fulcrum) 
services. If a "known" service is extending FieldAnnotatedTurbineBaseService or 
         MethodAnnotatedTurbineBaseService it could declare fields and methods 
with more Turbine annotations. 
         Examples are  annotating a service with @TurbineService or autoload a 
service, which has class level annotation @TurbineService, if the callingclass 
itself is TurbineService annotated.
       </action>
-       <action type="add" dev="gk">
+      <action type="add" dev="gk">
         New  service DateTimeFormatterService and tool DateTimeFormatterTool, 
which allow date time formatting with locale and zone configuration.
       </action>
       <action type="update" dev="gk">
diff --git a/src/java/org/apache/turbine/TurbineConstants.java 
b/src/java/org/apache/turbine/TurbineConstants.java
index 828b445d..9555c569 100644
--- a/src/java/org/apache/turbine/TurbineConstants.java
+++ b/src/java/org/apache/turbine/TurbineConstants.java
@@ -184,6 +184,12 @@ public interface TurbineConstants
        /** Session Timeout Default Value */
        int SESSION_TIMEOUT_DEFAULT = -1;
 
+       /** 
+        * Filter for classes that can be de-serialized from the persistent 
session storage 
+        * See {@link java.io.ObjectInputFilter.Config#createFilter(String)}
+        */
+       String SESSION_OBJECTINPUTFILTER = "session.objectinputfilter";
+
        /** Indicate whether this Turbine application is using SSL. */
        String USE_SSL_KEY = "use.ssl";
 
diff --git a/src/java/org/apache/turbine/util/ObjectUtils.java 
b/src/java/org/apache/turbine/util/ObjectUtils.java
index 0953cddf..57e6e014 100644
--- a/src/java/org/apache/turbine/util/ObjectUtils.java
+++ b/src/java/org/apache/turbine/util/ObjectUtils.java
@@ -21,11 +21,20 @@ package org.apache.turbine.util;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputFilter;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.turbine.Turbine;
+import org.apache.turbine.TurbineConstants;
+
 /**
  * This is where common Object manipulation routines should go.
  *
@@ -35,6 +44,8 @@ import java.util.Map;
  */
 public abstract class ObjectUtils
 {
+    private static final Logger log = LogManager.getLogger(ObjectUtils.class);
+
     /**
      * Converts a map to a byte array for storage/serialization.
      *
@@ -44,23 +55,27 @@ public abstract class ObjectUtils
      *
      * @throws Exception A generic exception.
      */
-       public static byte[] serializeMap(Map<String, Object> map)
-            throws Exception
+    public static byte[] serializeMap(Map<String, Object> map)
+        throws Exception
     {
         byte[] byteArray = null;
-
-        for (Object value : map.values())
+        Map<String, Object> mapCopy = new HashMap<>(map);
+        
+        // Remove all entries that are not serializable
+        for (Iterator<Map.Entry<String, Object>> i = 
mapCopy.entrySet().iterator(); i.hasNext();)
         {
-            if (! (value instanceof Serializable))
+            Map.Entry<String, Object> entry = i.next();
+            if (! (entry.getValue() instanceof Serializable))
             {
-                throw new Exception("Could not serialize, value is not 
serializable:" + value);
+               i.remove();
+                log.warn("Skipping serialization, value is not serializable: " 
+ entry.getValue());
             }
         }
 
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
              ObjectOutputStream out = new ObjectOutputStream(baos))
         {
-            out.writeObject(map);
+            out.writeObject(mapCopy);
             out.flush();
 
             byteArray = baos.toByteArray();
@@ -84,16 +99,24 @@ public abstract class ObjectUtils
 
         if (objectData != null)
         {
+            final String filterPattern = 
Turbine.getConfiguration().getString(TurbineConstants.SESSION_OBJECTINPUTFILTER);
+            
             try (ByteArrayInputStream bin = new 
ByteArrayInputStream(objectData);
                  ObjectInputStream in = new ObjectInputStream(bin))
             {
+                // Set filter to limit what can be deserialized if so 
configured
+                if (StringUtils.isNotEmpty(filterPattern))
+                {
+                    
in.setObjectInputFilter(ObjectInputFilter.Config.createFilter(filterPattern));
+                }
+                
                 // If objectData has not been initialized, an
                 // exception will occur.
                 object = (T)in.readObject();
             }
             catch (Exception e)
             {
-                // ignore
+                log.warn("Problem deserializing object.", e);
             }
         }
 
diff --git a/src/test/org/apache/turbine/util/ObjectUtilsTest.java 
b/src/test/org/apache/turbine/util/ObjectUtilsTest.java
new file mode 100644
index 00000000..4216d6b0
--- /dev/null
+++ b/src/test/org/apache/turbine/util/ObjectUtilsTest.java
@@ -0,0 +1,96 @@
+package org.apache.turbine.util;
+
+/*
+ * 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.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.collections4.bag.HashBag;
+import org.apache.turbine.Turbine;
+import org.apache.turbine.TurbineConstants;
+import org.apache.turbine.test.BaseTestCase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Testing of the ObjectUtils class.
+ *
+ * @author <a href="mailto:[email protected]";>Thomas Vandahl</a>
+ */
+public class ObjectUtilsTest extends BaseTestCase
+{
+    private TurbineConfig tc = null;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        tc = new TurbineConfig(".", 
"/conf/test/CompleteTurbineResources.properties");
+        tc.initialize();
+
+        
Turbine.getConfiguration().setProperty(TurbineConstants.SESSION_OBJECTINPUTFILTER,
+            "!org.apache.commons.collections4.**");
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        if (tc != null)
+        {
+            tc.dispose();
+        }
+    }
+
+    /**
+     * Verify that we can filter classes to be deserialized
+     *<p>
+     * @throws Exception
+     */
+    @Test
+    public void testDeserializationFilter()
+        throws Exception
+    {
+       Map<String, Object> map = new HashMap<>();
+       map.put("testKey1", new HashBag<String>()); // forbidden class
+        map.put("testKey2", new Object()); // non-serializable
+        map.put("testKey3", "actual Value");
+        assertEquals(map.size(), 3);
+       
+        final byte[] serialized1 = ObjectUtils.serializeMap(map);
+        assertNotNull(serialized1);
+
+        Map<String, Object> result1 =  ObjectUtils.deserialize(serialized1);
+        assertNull(result1); // Contains forbidden class
+        
+        map.remove("testKey1");
+        assertEquals(map.size(), 2);
+
+        final byte[] serialized2 = ObjectUtils.serializeMap(map);
+        assertNotNull(serialized2);
+
+        Map<String, Object> result2 =  ObjectUtils.deserialize(serialized2);
+        assertNotNull(result2); // Does not contain forbidden class
+        assertEquals(result2.size(), 1); // Non-serializable value skipped     
 
+    }
+}

Reply via email to