Browse Source

Add secure wrapper to plugin manager (#450)

pull/453/head
wolframhaussig 4 years ago committed by GitHub
parent
commit
dac4edb5f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
  2. 2
      pf4j/src/main/java/org/pf4j/PluginClassLoader.java
  3. 305
      pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java
  4. 272
      pf4j/src/test/java/org/pf4j/SecurePluginManagerWrapperTest.java

18
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java

@ -860,10 +860,7 @@ public abstract class AbstractPluginManager implements PluginManager {
ClassLoader pluginClassLoader = getPluginLoader().loadPlugin(pluginPath, pluginDescriptor); ClassLoader pluginClassLoader = getPluginLoader().loadPlugin(pluginPath, pluginDescriptor);
log.debug("Loaded plugin '{}' with class loader '{}'", pluginPath, pluginClassLoader); log.debug("Loaded plugin '{}' with class loader '{}'", pluginPath, pluginClassLoader);
// create the plugin wrapper PluginWrapper pluginWrapper = createPluginWrapper(pluginDescriptor, pluginPath, pluginClassLoader);
log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
pluginWrapper.setPluginFactory(getPluginFactory());
// test for disabled plugin // test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) { if (isPluginDisabled(pluginDescriptor.getPluginId())) {
@ -891,6 +888,19 @@ public abstract class AbstractPluginManager implements PluginManager {
return pluginWrapper; return pluginWrapper;
} }
/**
* creates the plugin wrapper. override this if you want to prevent plugins having full access to the plugin manager
*
* @return
*/
protected PluginWrapper createPluginWrapper(PluginDescriptor pluginDescriptor, Path pluginPath, ClassLoader pluginClassLoader) {
// create the plugin wrapper
log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
pluginWrapper.setPluginFactory(getPluginFactory());
return pluginWrapper;
}
/** /**
* Tests for already loaded plugins on given path. * Tests for already loaded plugins on given path.
* *

2
pf4j/src/main/java/org/pf4j/PluginClassLoader.java

@ -103,7 +103,7 @@ public class PluginClassLoader extends URLClassLoader {
} }
// if the class is part of the plugin engine use parent class loader // if the class is part of the plugin engine use parent class loader
if (className.startsWith(PLUGIN_PACKAGE_PREFIX) && !className.startsWith("org.pf4j.demo")) { if (className.startsWith(PLUGIN_PACKAGE_PREFIX) && !className.startsWith("org.pf4j.demo") && !className.startsWith("org.pf4j.test")) {
// log.trace("Delegate the loading of PF4J class '{}' to parent", className); // log.trace("Delegate the loading of PF4J class '{}' to parent", className);
return getParent().loadClass(className); return getParent().loadClass(className);
} }

305
pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java

@ -0,0 +1,305 @@
package org.pf4j;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Use this class to wrap the original plugin manager to prevent full access from within plugins.
* Override AbstractPluginManager.createPluginWrapper to use this class
* @author Wolfram Haussig
*
*/
public class SecurePluginManagerWrapper implements PluginManager {
private static final String PLUGIN_PREFIX = "Plugin ";
/**
* the current plugin
*/
private String currentPluginId;
/**
* the original plugin manager
*/
private PluginManager original;
/**
* The registered {@link PluginStateListener}s.
*/
protected List<PluginStateListener> pluginStateListeners = new ArrayList<>();
/**
* wrapper for pluginStateListeners
*/
private PluginStateListenerWrapper listenerWrapper = new PluginStateListenerWrapper();
/**
* constructor
* @param original the original plugin manager
* @param currentPlugin the current pluginId
*/
public SecurePluginManagerWrapper(PluginManager original, String currentPluginId) {
this.original = original;
this.currentPluginId = currentPluginId;
}
@Override
public boolean isDevelopment() {
return original.isDevelopment();
}
@Override
public boolean isNotDevelopment() {
return original.isNotDevelopment();
}
@Override
public List<PluginWrapper> getPlugins() {
return Arrays.asList(getPlugin(currentPluginId));
}
@Override
public List<PluginWrapper> getPlugins(PluginState pluginState) {
return getPlugins().stream().filter(p -> p.getPluginState() == pluginState).collect(Collectors.toList());
}
@Override
public List<PluginWrapper> getResolvedPlugins() {
return getPlugins().stream().filter(p -> p.getPluginState().ordinal() >= PluginState.RESOLVED.ordinal()).collect(Collectors.toList());
}
@Override
public List<PluginWrapper> getUnresolvedPlugins() {
return Collections.emptyList();
}
@Override
public List<PluginWrapper> getStartedPlugins() {
return getPlugins(PluginState.STARTED);
}
@Override
public PluginWrapper getPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPlugin for foreign pluginId!");
}
}
@Override
public void loadPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugins!");
}
@Override
public String loadPlugin(Path pluginPath) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugin!");
}
@Override
public void startPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugins!");
}
@Override
public PluginState startPlugin(String pluginId) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugin!");
}
@Override
public void stopPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugins!");
}
@Override
public PluginState stopPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.stopPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugin for foreign pluginId!");
}
}
@Override
public void unloadPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugins!");
}
@Override
public boolean unloadPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.unloadPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugin for foreign pluginId!");
}
}
@Override
public boolean disablePlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.disablePlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute disablePlugin for foreign pluginId!");
}
}
@Override
public boolean enablePlugin(String pluginId) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute enablePlugin!");
}
@Override
public boolean deletePlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.deletePlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute deletePlugin for foreign pluginId!");
}
}
@Override
public ClassLoader getPluginClassLoader(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getPluginClassLoader(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginClassLoader for foreign pluginId!");
}
}
@Override
public List<Class<?>> getExtensionClasses(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClasses(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");
}
}
@Override
public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type) {
return getExtensionClasses(type, currentPluginId);
}
@Override
public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type, String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClasses(type, pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");
}
}
@Override
public <T> List<T> getExtensions(Class<T> type) {
return getExtensions(type, currentPluginId);
}
@Override
public <T> List<T> getExtensions(Class<T> type, String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensions(type, pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");
}
}
@Override
public List<?> getExtensions(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensions(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");
}
}
@Override
public Set<String> getExtensionClassNames(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClassNames(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClassNames for foreign pluginId!");
}
}
@Override
public ExtensionFactory getExtensionFactory() {
return original.getExtensionFactory();
}
@Override
public RuntimeMode getRuntimeMode() {
return original.getRuntimeMode();
}
@Override
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
PluginWrapper plugin = getPlugin(currentPluginId);
if (plugin.getPluginClassLoader() == classLoader) {
return plugin;
}
return null;
}
@Override
public void addPluginStateListener(PluginStateListener listener) {
if (pluginStateListeners.isEmpty()) {
this.original.addPluginStateListener(listenerWrapper);
}
pluginStateListeners.add(listener);
}
@Override
public void removePluginStateListener(PluginStateListener listener) {
pluginStateListeners.remove(listener);
if (pluginStateListeners.isEmpty()) {
this.original.removePluginStateListener(listenerWrapper);
}
}
@Override
public void setSystemVersion(String version) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute setSystemVersion!");
}
@Override
public String getSystemVersion() {
return original.getSystemVersion();
}
@Override
public Path getPluginsRoot() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoot!");
}
@Override
public List<Path> getPluginsRoots() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoots!");
}
@Override
public VersionManager getVersionManager() {
return original.getVersionManager();
}
/**
* Wrapper for PluginStateListener events. will only propagate events if they match the current pluginId
* @author Wolfram Haussig
*
*/
private class PluginStateListenerWrapper implements PluginStateListener {
@Override
public void pluginStateChanged(PluginStateEvent event) {
if (event.getPlugin().getPluginId().equals(currentPluginId)) {
for (PluginStateListener listener : pluginStateListeners) {
listener.pluginStateChanged(event);
}
}
}
}
}

272
pf4j/src/test/java/org/pf4j/SecurePluginManagerWrapperTest.java

@ -0,0 +1,272 @@
package org.pf4j;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.pf4j.test.PluginJar;
import org.pf4j.test.TestExtension;
import org.pf4j.test.TestExtensionPoint;
import org.pf4j.test.TestPlugin;
public class SecurePluginManagerWrapperTest {
private static final String OTHER_PLUGIN_ID = "test-plugin-2";
private static final String THIS_PLUGIN_ID = "test-plugin-1";
private PluginJar thisPlugin;
private PluginJar otherPlugin;
private PluginManager pluginManager;
private PluginManager wrappedPluginManager;
private int pluginManagerEvents = 0;
private int wrappedPluginManagerEvents = 0;
@TempDir
Path pluginsPath;
@BeforeEach
public void setUp() throws IOException {
pluginManagerEvents = 0;
wrappedPluginManagerEvents = 0;
thisPlugin = new PluginJar.Builder(pluginsPath.resolve("test-plugin1.jar"), THIS_PLUGIN_ID).pluginClass(TestPlugin.class.getName()).pluginVersion("1.2.3").extension(TestExtension.class.getName()).build();
otherPlugin = new PluginJar.Builder(pluginsPath.resolve("test-plugin2.jar"), OTHER_PLUGIN_ID).pluginClass(TestPlugin.class.getName()).pluginVersion("1.2.3").extension(TestExtension.class.getName()).build();
pluginManager = new JarPluginManager(pluginsPath);
wrappedPluginManager = new SecurePluginManagerWrapper(pluginManager, THIS_PLUGIN_ID);
}
@AfterEach
public void tearDown() {
pluginManager.unloadPlugins();
thisPlugin = null;
otherPlugin = null;
pluginManager = null;
}
@Test
public void pluginStateListeners() {
pluginManager.addPluginStateListener(new PluginStateListener() {
@Override
public void pluginStateChanged(PluginStateEvent event) {
pluginManagerEvents++;
}
});
wrappedPluginManager.addPluginStateListener(new PluginStateListener() {
@Override
public void pluginStateChanged(PluginStateEvent event) {
wrappedPluginManagerEvents++;
}
});
pluginManager.loadPlugins();
pluginManager.startPlugins();
assertEquals(4, pluginManagerEvents);
assertEquals(2, wrappedPluginManagerEvents);
}
@Test
public void deletePlugin() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.deletePlugin(OTHER_PLUGIN_ID));
assertTrue(wrappedPluginManager.deletePlugin(THIS_PLUGIN_ID));
}
@Test
public void disablePlugin() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.disablePlugin(OTHER_PLUGIN_ID));
assertTrue(wrappedPluginManager.disablePlugin(THIS_PLUGIN_ID));
}
@Test
public void enablePlugin() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.enablePlugin(OTHER_PLUGIN_ID));
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.enablePlugin(THIS_PLUGIN_ID));
}
@Test
public void getExtensionClasses() {
pluginManager.loadPlugins();
pluginManager.startPlugins();
assertEquals(1, wrappedPluginManager.getExtensionClasses(TestExtensionPoint.class).size());
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensionClasses(TestExtensionPoint.class, OTHER_PLUGIN_ID));
assertEquals(1, wrappedPluginManager.getExtensionClasses(TestExtensionPoint.class, THIS_PLUGIN_ID).size());
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensionClasses(OTHER_PLUGIN_ID));
assertEquals(1, wrappedPluginManager.getExtensionClasses(THIS_PLUGIN_ID).size());
}
@Test
public void getExtensionClassNames() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensionClassNames(OTHER_PLUGIN_ID));
assertEquals(1, wrappedPluginManager.getExtensionClassNames(THIS_PLUGIN_ID).size());
}
@Test
public void getExtensionFactory() {
pluginManager.loadPlugins();
assertEquals(pluginManager.getExtensionFactory(), wrappedPluginManager.getExtensionFactory());
}
@Test
public void getExtensions() {
pluginManager.loadPlugins();
pluginManager.startPlugins();
assertEquals(1, wrappedPluginManager.getExtensions(TestExtensionPoint.class).size());
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensions(TestExtensionPoint.class, OTHER_PLUGIN_ID));
assertEquals(1, wrappedPluginManager.getExtensions(TestExtensionPoint.class, THIS_PLUGIN_ID).size());
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensions(OTHER_PLUGIN_ID));
assertEquals(1, wrappedPluginManager.getExtensions(THIS_PLUGIN_ID).size());
}
@Test
public void getPlugin() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPlugin(OTHER_PLUGIN_ID));
assertEquals(THIS_PLUGIN_ID, wrappedPluginManager.getPlugin(THIS_PLUGIN_ID).getPluginId());
}
@Test
public void getPluginClassLoader() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPluginClassLoader(OTHER_PLUGIN_ID));
assertNotNull(wrappedPluginManager.getPluginClassLoader(THIS_PLUGIN_ID));
}
@Test
public void getPlugins() {
pluginManager.loadPlugins();
assertEquals(2, pluginManager.getPlugins().size());
assertEquals(1, wrappedPluginManager.getPlugins().size());
}
@Test
public void getPluginsRoot() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPluginsRoot());
}
@Test
public void getPluginsRoots() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPluginsRoots());
}
@Test
public void getResolvedPlugins() {
pluginManager.loadPlugins();
assertEquals(2, pluginManager.getResolvedPlugins().size());
assertEquals(1, wrappedPluginManager.getResolvedPlugins().size());
}
@Test
public void getRuntimeMode() {
assertEquals(pluginManager.getRuntimeMode(), wrappedPluginManager.getRuntimeMode());
}
@Test
public void getStartedPlugins() {
pluginManager.loadPlugins();
pluginManager.startPlugins();
assertEquals(2, pluginManager.getStartedPlugins().size());
assertEquals(1, wrappedPluginManager.getStartedPlugins().size());
}
@Test
public void getSystemVersion() {
assertEquals(pluginManager.getSystemVersion(), wrappedPluginManager.getSystemVersion());
}
@Test
public void getUnresolvedPlugins() {
assertNotNull(wrappedPluginManager);
assertNotNull(wrappedPluginManager.getUnresolvedPlugins());
assertTrue(wrappedPluginManager.getUnresolvedPlugins().isEmpty());
}
@Test
public void getVersionManager() {
assertEquals(pluginManager.getVersionManager(), wrappedPluginManager.getVersionManager());
}
@Test
public void isDevelopment() {
assertEquals(pluginManager.isDevelopment(), wrappedPluginManager.isDevelopment());
}
@Test
public void isNotDevelopment() {
assertEquals(pluginManager.isNotDevelopment(), wrappedPluginManager.isNotDevelopment());
}
@Test
public void loadPlugin() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.loadPlugin(thisPlugin.path()));
}
@Test
public void loadPlugins() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.loadPlugins());
}
@Test
public void setSystemVersion() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.setSystemVersion("1.0.0"));
}
@Test
public void startPlugin() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.startPlugin(OTHER_PLUGIN_ID));
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.startPlugin(THIS_PLUGIN_ID));
}
@Test
public void startPlugins() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.startPlugins());
}
@Test
public void stopPlugin() {
pluginManager.loadPlugins();
pluginManager.startPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.stopPlugin(OTHER_PLUGIN_ID));
assertEquals(PluginState.STOPPED, wrappedPluginManager.stopPlugin(THIS_PLUGIN_ID));
}
@Test
public void stopPlugins() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.stopPlugins());
}
@Test
public void unloadPlugin() {
pluginManager.loadPlugins();
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.unloadPlugin(OTHER_PLUGIN_ID));
assertTrue(wrappedPluginManager.unloadPlugin(THIS_PLUGIN_ID));
}
@Test
public void unloadPlugins() {
assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.unloadPlugins());
}
@Test
public void whichPlugin() {
pluginManager.loadPlugins();
pluginManager.startPlugins();
assertEquals(null, wrappedPluginManager.whichPlugin(pluginManager.getExtensionClasses(OTHER_PLUGIN_ID).get(0)));
assertEquals(THIS_PLUGIN_ID, wrappedPluginManager.whichPlugin(pluginManager.getExtensionClasses(THIS_PLUGIN_ID).get(0)).getPluginId());
}
}
Loading…
Cancel
Save