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
+ }
+}