Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
Expand Down Expand Up @@ -79,6 +80,16 @@ public class ZookeeperDistributedQueue<T extends Serializable> implements Distri
private static final Log LOG = LogFactory.getLog(ZookeeperDistributedQueue.class);
private static final String QUEUE_ENTRY_NAME = "dz-queue-entry";

/**
* Default {@link ObjectInputFilter} pattern (see {@link ObjectInputFilter.Config#createFilter(String)}) applied when
* deserializing queue elements read from Zookeeper. Only JDK value/collection types, Broadleaf classes and Solr
* common types are permitted; everything else is rejected. Override {@link #getDeserializationFilterPattern()} to customize.
*/
public static final String DEFAULT_DESERIALIZATION_FILTER_PATTERN =
"maxdepth=50;maxrefs=100000;maxarray=100000;maxbytes=1048576;"
+ "java.lang.*;java.util.*;java.math.*;java.time.*;java.sql.Date;java.sql.Timestamp;"
+ "org.broadleafcommerce.**;org.apache.solr.common.**;!*";

protected final Object QUEUE_MONITOR = new Object();
private final String queueFolderPath;
private final ZooKeeper zk;
Expand Down Expand Up @@ -822,7 +833,8 @@ public void process(WatchedEvent event) {
}

/**
* Mechanism to convert a byte array to an object. Default implementation uses {@link ObjectInputStream}.
* Mechanism to convert a byte array to an object. Default implementation uses {@link ObjectInputStream} guarded by the
* {@link ObjectInputFilter} returned from {@link #createDeserializationFilter()}.
*
* @param bytes
* @return
Expand All @@ -832,6 +844,7 @@ protected Object deserialize(byte[] bytes) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(bais);
ois.setObjectInputFilter(createDeserializationFilter());
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new DistributedQueueException("Unable to deserialze an element from the Zookeeper queue.", e);
Expand All @@ -856,6 +869,26 @@ protected Object deserialize(byte[] bytes) {
}
}

/**
* Pattern used to build the {@link ObjectInputFilter} that restricts which classes may be deserialized from Zookeeper.
* Defaults to {@link #DEFAULT_DESERIALIZATION_FILTER_PATTERN}. Subclasses queuing element types outside the default
* allow list should override this and prepend their own patterns.
*
* @return
*/
protected String getDeserializationFilterPattern() {
return DEFAULT_DESERIALIZATION_FILTER_PATTERN;
}

/**
* Creates the {@link ObjectInputFilter} applied to every {@link ObjectInputStream} used by {@link #deserialize(byte[])}.
*
* @return
*/
protected ObjectInputFilter createDeserializationFilter() {
return ObjectInputFilter.Config.createFilter(getDeserializationFilterPattern());
}

/**
* Mechanism to convert an object to a byte array. Default implementation uses {@link ObjectOutputStream}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*-
* #%L
* BroadleafCommerce Framework
* %%
* Copyright (C) 2009 - 2026 Broadleaf Commerce
* %%
* Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
* (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
* unless the restrictions on use therein are violated and require payment to Broadleaf in which case
* the Broadleaf End User License Agreement (EULA), Version 1.1
* (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
* shall apply.
*
* Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
* between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
* #L%
*/
package org.broadleafcommerce.core.util.queue;

import org.apache.solr.common.SolrInputDocument;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InvalidClassException;
import java.io.IOException;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;

import static org.junit.Assert.assertEquals;

public class ZookeeperDistributedQueueDeserializationTest {

@Test
public void shouldDeserializeAllowedJdkTypes() throws Exception {
assertDeserializesEquals(Integer.valueOf(42));
assertDeserializesEquals("queue entry");

ArrayList<String> list = new ArrayList<>();
list.add("first");
list.add("second");
assertDeserializesEquals(list);

LinkedHashMap<String, Long> map = new LinkedHashMap<>();
map.put("one", 1L);
map.put("two", 2L);
assertDeserializesEquals(map);
}

@Test
public void shouldDeserializeAllowedBroadleafType() throws Exception {
assertDeserializesEquals(new TestQueueEntry("queue entry"));
}

@Test
public void shouldDeserializeAllowedSolrType() throws Exception {
SolrInputDocument document = new SolrInputDocument();
document.addField("id", "queue-entry");
SolrInputDocument deserialized = (SolrInputDocument) deserialize(serialize(document));
assertEquals(document.toString(), deserialized.toString());
assertEquals(document.getFieldValue("id"), deserialized.getFieldValue("id"));
}

@Test(expected = InvalidClassException.class)
public void shouldRejectDisallowedType() throws Exception {
deserialize(serialize(new File("queue-entry")));
}

private void assertDeserializesEquals(Serializable value) throws Exception {
assertEquals(value, deserialize(serialize(value)));
}

private Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
try (ObjectInputStream input = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
input.setObjectInputFilter(ObjectInputFilter.Config.createFilter(
ZookeeperDistributedQueue.DEFAULT_DESERIALIZATION_FILTER_PATTERN));
return input.readObject();
}
}

private byte[] serialize(Serializable value) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
output.writeObject(value);
}
return bytes.toByteArray();
}

private static class TestQueueEntry implements Serializable {

private final String value;

private TestQueueEntry(String value) {
this.value = value;
}

@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof TestQueueEntry)) {
return false;
}
TestQueueEntry that = (TestQueueEntry) other;
return value.equals(that.value);
}

@Override
public int hashCode() {
return value.hashCode();
}
}
}