Skip to content

Commit

Permalink
Merge branch 'jdk8'
Browse files Browse the repository at this point in the history
# Conflicts:
#	common/rmi/src/main/java/is/codion/common/rmi/server/AbstractServer.java
#	common/rmi/src/main/java/is/codion/common/rmi/server/SerializationWhitelist.java
#	common/rmi/src/test/java/is/codion/common/rmi/server/SerializationWhitelistTest.java
  • Loading branch information
bjorndarri committed Jan 5, 2024
2 parents b5020ad + 4a39ff5 commit 5806d32
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 81 deletions.
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Codion Change Log
### is.codion.common.db
- AbstractDatabase, transaction isolation now a final field, instead of using the configuration value directly.
- AbstractDatabase, login timeout now set in a static initializer block, instead of during connection creation.
### is.codion.common.rmi
- SerializationWhitelist.writeToFile() improved a bit, javadocs fixed.
- SerializationWhitelist no longer public, refactored.
### is.codion.swing.common.ui
- KeyboardShortcuts.copy() added.
- CalendarPanel.Builder added, replaces factory methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ObjectInputFilter;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
Expand All @@ -51,8 +52,6 @@
import java.util.function.Predicate;

import static is.codion.common.rmi.server.RemoteClient.remoteClient;
import static is.codion.common.rmi.server.SerializationWhitelist.serializationDryRun;
import static is.codion.common.rmi.server.SerializationWhitelist.writeDryRunWhitelist;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
Expand Down Expand Up @@ -244,8 +243,9 @@ public final void shutdown() {
sharedAuthenticators.forEach(AbstractServer::closeAuthenticator);
authenticators.values().forEach(AbstractServer::closeAuthenticator);
auxiliaryServers.forEach(AbstractServer::stopAuxiliaryServer);
if (serializationDryRun()) {
writeDryRunWhitelist();
ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter();
if (serialFilter instanceof SerializationWhitelist.DryRun) {
((SerializationWhitelist.DryRun) serialFilter).writeToFile(configuration.serializationFilterWhitelist());
}
shutdownEvent.run();
}
Expand Down Expand Up @@ -463,11 +463,19 @@ private static RemoteClient clearPasswords(RemoteClient remoteClient) {
}

private static void configureSerializationWhitelist(ServerConfiguration configuration) {
if (configuration.serializationFilterDryRun()) {
SerializationWhitelist.configureDryRun(configuration.serializationFilterWhitelist());
String whitelistFile = configuration.serializationFilterWhitelist();
if (nullOrEmpty(whitelistFile)) {
LOG.info("No serialization whitelist file specified");
}
else {
SerializationWhitelist.configure(configuration.serializationFilterWhitelist());
if (configuration.serializationFilterDryRun()) {
ObjectInputFilter.Config.setSerialFilter(SerializationWhitelist.whitelistDryRun());
LOG.info("Serialization filter dry-run enabled");
}
else {
ObjectInputFilter.Config.setSerialFilter(SerializationWhitelist.whitelistFilter(whitelistFile));
LOG.info("Serialization filter whitelist set: " + whitelistFile);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,29 @@
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputFilter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import static is.codion.common.NullOrEmpty.nullOrEmpty;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

/**
* Implements a serialization whitelist
*/
public final class SerializationWhitelist {
final class SerializationWhitelist {

private static final Logger LOG = LoggerFactory.getLogger(SerializationWhitelist.class);

Expand All @@ -54,64 +53,35 @@ public final class SerializationWhitelist {
private SerializationWhitelist() {}

/**
* Configures a serialization whitelist, does nothing if {@code whitelist} is null or empty.
* Creates a serialization filter based on a whitelist.
* Supports 'classpath:' prefix for a whitelist in the classpath root.
* @param whitelistFile the path to the file containing the whitelisted class names
*/
public static void configure(String whitelistFile) {
if (!nullOrEmpty(whitelistFile)) {
ObjectInputFilter.Config.setSerialFilter(new SerializationFilter(whitelistFile));
LOG.info("Serialization filter whitelist set: " + whitelistFile);
}
}

/**
* Configures a serialization whitelist for a dry run, does nothing if {@code dryRunFile} is null or empty.
* Note that this will append to an existing file.
* @param dryRunFile the dry-run results file to write to, appended to if it exists
* @throws IllegalArgumentException in case of a classpath dry run file
*/
public static void configureDryRun(String dryRunFile) {
if (!nullOrEmpty(dryRunFile)) {
ObjectInputFilter.Config.setSerialFilter(new SerializationFilterDryRun(dryRunFile));
LOG.info("Serialization filter whitelist set for dry-run: " + dryRunFile);
}
static WhitelistFilter whitelistFilter(String whitelistFile) {
return new WhitelistFilter(whitelistFile);
}

/**
* Returns true if a serialization dry-run is active.
* @return true if a dry-run is active.
* Creates a serialization filter based on a whitelist.
* @param classnames the whitelisted class names
*/
public static boolean serializationDryRun() {
return ObjectInputFilter.Config.getSerialFilter() instanceof SerializationFilterDryRun;
static WhitelistFilter whitelistFilter(Collection<String> classnames) {
return new WhitelistFilter(classnames);
}

/**
* Writes the class names collected during a dry-run to file.
* If dry-run was not active this method has no effect.
* Creates a serialization filter for a whitelist dry run.
* @param whitelistFile the file to write the dry-run results to
* @throws IllegalArgumentException in case of a classpath dry run file
*/
public static void writeDryRunWhitelist() {
ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter();
if (serialFilter instanceof SerializationFilterDryRun) {
((SerializationFilterDryRun) serialFilter).writeToFile();
}
static DryRun whitelistDryRun() {
return new DryRun();
}

private static final class SerializationFilterDryRun implements ObjectInputFilter {
static final class DryRun implements ObjectInputFilter {

private final String whitelistFile;
private final Set<Class<?>> deserializedClasses = new HashSet<>();

private SerializationFilterDryRun(String whitelistFile) {
if (requireNonNull(whitelistFile).toLowerCase().startsWith(CLASSPATH_PREFIX)) {
throw new IllegalArgumentException("Filter dry run can not be performed with a classpath whitelist: " + whitelistFile);
}
if (new File(whitelistFile).exists()) {
throw new IllegalArgumentException("Whitelist file to write to after dry-run already exists");
}
this.whitelistFile = requireNonNull(whitelistFile, "whitelistFile");
}

@Override
public Status checkInput(FilterInfo filterInfo) {
Class<?> clazz = filterInfo.serialClass();
Expand All @@ -122,16 +92,19 @@ public Status checkInput(FilterInfo filterInfo) {
return Status.ALLOWED;
}

private void writeToFile() {
/**
* Writes all classnames found during the dry-run to the specified file.
* @param whitelistFile the file to write to
*/
synchronized void writeToFile(String whitelistFile) {
if (requireNonNull(whitelistFile).toLowerCase().startsWith(CLASSPATH_PREFIX)) {
throw new IllegalArgumentException("Filter dry run can not be performed with a classpath whitelist: " + whitelistFile);
}
try {
File file = new File(whitelistFile);
if (!file.createNewFile()) {
throw new IOException("Whitelist file already exists: " + whitelistFile);
}
Files.write(file.toPath(), deserializedClasses.stream()
Files.write(Paths.get(whitelistFile), deserializedClasses.stream()
.map(Class::getName)
.sorted()
.collect(toList()));
.collect(toList()), StandardOpenOption.CREATE);
LOG.debug("Serialization whitelist written: " + whitelistFile);
}
catch (Exception e) {
Expand All @@ -140,19 +113,19 @@ private void writeToFile() {
}
}

static final class SerializationFilter implements ObjectInputFilter {
static final class WhitelistFilter implements ObjectInputFilter {

private static final String COMMENT = "#";
private static final String WILDCARD = "*";

private final Set<String> allowedClassnames = new HashSet<>();
private final List<String> allowedWildcardClassnames = new ArrayList<>();

SerializationFilter(String whitelistFile) {
private WhitelistFilter(String whitelistFile) {
this(readWhitelistItems(whitelistFile));
}

SerializationFilter(Collection<String> whitelistItems) {
private WhitelistFilter(Collection<String> whitelistItems) {
addWhitelistItems(whitelistItems);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
package is.codion.common.rmi.server;

import is.codion.common.Serializer;
import is.codion.common.rmi.server.SerializationWhitelist.SerializationFilter;
import is.codion.common.rmi.server.SerializationWhitelist.WhitelistFilter;

import org.junit.jupiter.api.Test;

Expand All @@ -31,28 +31,41 @@
import java.util.List;

import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public final class SerializationWhitelistTest {

@Test
void dryRun() throws IOException, ClassNotFoundException {
assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.configureDryRun("classpath:dryrun"));
assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.whitelistDryRun().writeToFile("classpath:dryrun"));
File tempFile = File.createTempFile("serialization_dry_run_test", "txt");
assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.configureDryRun(tempFile.getAbsolutePath()));
tempFile.delete();
SerializationWhitelist.configureDryRun(tempFile.getAbsolutePath());

SerializationWhitelist.DryRun serialFilter = SerializationWhitelist.whitelistDryRun();
ObjectInputFilter.Config.setSerialFilter(serialFilter);

Serializer.deserialize(Serializer.serialize(Integer.valueOf(42)));
Serializer.deserialize(Serializer.serialize(Double.valueOf(42)));
Serializer.deserialize(Serializer.serialize(Long.valueOf(42)));
SerializationWhitelist.writeDryRunWhitelist();
serialFilter.writeToFile(tempFile.getAbsolutePath());

List<String> classNames = Files.readAllLines(tempFile.toPath(), StandardCharsets.UTF_8);

assertEquals(3, classNames.size());
assertEquals(Integer.class.getName(), classNames.get(0));
assertEquals(Long.class.getName(), classNames.get(1));
assertEquals(Number.class.getName(), classNames.get(2));

Serializer.deserialize(Serializer.serialize(Double.valueOf(42)));
serialFilter.writeToFile(tempFile.getAbsolutePath());

classNames = Files.readAllLines(tempFile.toPath(), StandardCharsets.UTF_8);

assertEquals(4, classNames.size());
assertEquals(Double.class.getName(), classNames.get(0));
assertEquals(Integer.class.getName(), classNames.get(1));
assertEquals(Long.class.getName(), classNames.get(2));
assertEquals(Number.class.getName(), classNames.get(3));
assertTrue(tempFile.exists());

tempFile.delete();
}

Expand All @@ -64,7 +77,7 @@ void testNoWildcards() {
"is.codion.common.state.State",
"is.codion.common.state.StateObserver"
);
SerializationFilter filter = new SerializationFilter(whitelistItems);
WhitelistFilter filter = SerializationWhitelist.whitelistFilter(whitelistItems);
assertEquals(filter.checkInput("is.codion.common.value.Value"), ObjectInputFilter.Status.ALLOWED);
assertEquals(filter.checkInput("is.codion.common.state.State"), ObjectInputFilter.Status.ALLOWED);
assertEquals(filter.checkInput("is.codion.common.state.States"), ObjectInputFilter.Status.REJECTED);
Expand All @@ -75,18 +88,18 @@ void testNoWildcards() {

@Test
void file() {
assertThrows(RuntimeException.class, () -> new SerializationFilter("src/test/resources/whitelist_test_non_existing.txt"));
testFilter(new SerializationFilter("src/test/resources/whitelist_test.txt"));
assertThrows(RuntimeException.class, () -> SerializationWhitelist.whitelistFilter("src/test/resources/whitelist_test_non_existing.txt"));
testFilter(SerializationWhitelist.whitelistFilter("src/test/resources/whitelist_test.txt"));
}

@Test
void classpath() {
assertThrows(IllegalArgumentException.class, () -> new SerializationFilter("classpath:src/test/resources/whitelist_test.txt"));
testFilter(new SerializationFilter("classpath:whitelist_test.txt"));
testFilter(new SerializationFilter("classpath:/whitelist_test.txt"));
assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.whitelistFilter("classpath:src/test/resources/whitelist_test.txt"));
testFilter(SerializationWhitelist.whitelistFilter("classpath:whitelist_test.txt"));
testFilter(SerializationWhitelist.whitelistFilter("classpath:/whitelist_test.txt"));
}

private static void testFilter(SerializationFilter filter) {
private static void testFilter(WhitelistFilter filter) {
assertEquals(filter.checkInput("is.codion.common.value.Value"), ObjectInputFilter.Status.ALLOWED);
assertEquals(filter.checkInput("is.codion.common.state.State"), ObjectInputFilter.Status.ALLOWED);
assertEquals(filter.checkInput("is.codion.common.state.States"), ObjectInputFilter.Status.ALLOWED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes;
import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerIJTheme;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JTable;
Expand Down Expand Up @@ -243,6 +244,7 @@ public NotesApplicationPanel(NotesApplicationModel applicationModel) {
notePanel.initialize();// Lazy initialization of UI components
applicationPanel.setLayout(Layouts.borderLayout());
applicationPanel.add(notePanel, BorderLayout.CENTER);
applicationPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
});
}

Expand Down
5 changes: 4 additions & 1 deletion documentation/src/docs/asciidoc/technical/server.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ No dynamic class loading is required.

=== Serialization whitelist

A {url-javadoc}{common-rmi}/is/codion/common/rmi/server/SerializationWhitelist.html[serialization whitelist] can be used by setting the following system property.
A serialization whitelist can be used by setting the following system property.

[source]
----
codion.server.serializationFilterWhitelist=config/whitelist.txt
----
----
codion.server.serializationFilterWhitelist=classpath:whitelist.txt
----

A whitelist can be created during a server dry-run by adding the following system property. The whitelist containing all classes deserialized during the run is written to disk on server shutdown.

Expand Down
4 changes: 2 additions & 2 deletions readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ public class StoreDemo {
@Override
protected void initializeUI() {
setInitialFocusAttribute(Customer.FIRST_NAME);
initialFocusAttribute().set(Customer.FIRST_NAME);
createTextField(Customer.FIRST_NAME);
createTextField(Customer.LAST_NAME);
createTextField(Customer.EMAIL);
Expand All @@ -404,7 +404,7 @@ public class StoreDemo {
@Override
protected void initializeUI() {
setInitialFocusAttribute(Address.STREET);
initialFocusAttribute().set(Address.STREET);
createForeignKeyComboBox(Address.CUSTOMER_FK);
createTextField(Address.STREET);
createTextField(Address.CITY);
Expand Down

0 comments on commit 5806d32

Please sign in to comment.