]> source.dussan.org Git - pf4j.git/commitdiff
Add secure wrapper to plugin manager (#450)
authorwolframhaussig <13997737+wolframhaussig@users.noreply.github.com>
Sun, 13 Jun 2021 09:10:35 +0000 (11:10 +0200)
committerGitHub <noreply@github.com>
Sun, 13 Jun 2021 09:10:35 +0000 (12:10 +0300)
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
pf4j/src/main/java/org/pf4j/PluginClassLoader.java
pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java [new file with mode: 0644]
pf4j/src/test/java/org/pf4j/SecurePluginManagerWrapperTest.java [new file with mode: 0644]

index 95622573d2521ccd7b6ae86fdacec614f43d46d0..2374b56e5affc928e0046917e7175e23d334c92b 100644 (file)
@@ -860,10 +860,7 @@ public abstract class AbstractPluginManager implements PluginManager {
         ClassLoader pluginClassLoader = getPluginLoader().loadPlugin(pluginPath, pluginDescriptor);
         log.debug("Loaded plugin '{}' with class loader '{}'", pluginPath, pluginClassLoader);
 
-        // create the plugin wrapper
-        log.debug("Creating wrapper for plugin '{}'", pluginPath);
-        PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
-        pluginWrapper.setPluginFactory(getPluginFactory());
+        PluginWrapper pluginWrapper = createPluginWrapper(pluginDescriptor, pluginPath, pluginClassLoader);
 
         // test for disabled plugin
         if (isPluginDisabled(pluginDescriptor.getPluginId())) {
@@ -890,6 +887,19 @@ public abstract class AbstractPluginManager implements PluginManager {
 
         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.
index 4dbcb6acaf620b092427ee59cc179e473c007874..d22dd550209b60d1da4c17c8656e5db7e5ccb165 100644 (file)
@@ -103,7 +103,7 @@ public class PluginClassLoader extends URLClassLoader {
             }
 
             // 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);
                 return getParent().loadClass(className);
             }
diff --git a/pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java b/pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java
new file mode 100644 (file)
index 0000000..f55b1e6
--- /dev/null
@@ -0,0 +1,305 @@
+package org.pf4j;\r
+\r
+import java.nio.file.Path;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collections;\r
+import java.util.List;\r
+import java.util.Set;\r
+import java.util.stream.Collectors;\r
+\r
+/**\r
+ * Use this class to wrap the original plugin manager to prevent full access from within plugins.\r
+ * Override AbstractPluginManager.createPluginWrapper to use this class\r
+ * @author Wolfram Haussig\r
+ *\r
+ */\r
+public class SecurePluginManagerWrapper implements PluginManager {\r
+\r
+    private static final String PLUGIN_PREFIX = "Plugin ";\r
+    /**\r
+     * the current plugin\r
+     */\r
+    private String currentPluginId;\r
+    /**\r
+     * the original plugin manager\r
+     */\r
+    private PluginManager original;\r
+\r
+    /**\r
+     * The registered {@link PluginStateListener}s.\r
+     */\r
+    protected List<PluginStateListener> pluginStateListeners = new ArrayList<>();\r
+    /**\r
+     * wrapper for pluginStateListeners\r
+     */\r
+    private PluginStateListenerWrapper listenerWrapper = new PluginStateListenerWrapper();\r
+\r
+    /**\r
+     * constructor\r
+     * @param original the original plugin manager\r
+     * @param currentPlugin the current pluginId\r
+     */\r
+    public SecurePluginManagerWrapper(PluginManager original, String currentPluginId) {\r
+        this.original = original;\r
+        this.currentPluginId = currentPluginId;\r
+    }\r
+\r
+    @Override\r
+    public boolean isDevelopment() {\r
+        return original.isDevelopment();\r
+    }\r
+\r
+    @Override\r
+    public boolean isNotDevelopment() {\r
+        return original.isNotDevelopment();\r
+    }\r
+\r
+    @Override\r
+    public List<PluginWrapper> getPlugins() {\r
+        return Arrays.asList(getPlugin(currentPluginId));\r
+    }\r
+\r
+    @Override\r
+    public List<PluginWrapper> getPlugins(PluginState pluginState) {\r
+        return getPlugins().stream().filter(p -> p.getPluginState() == pluginState).collect(Collectors.toList());\r
+    }\r
+\r
+    @Override\r
+    public List<PluginWrapper> getResolvedPlugins() {\r
+        return getPlugins().stream().filter(p -> p.getPluginState().ordinal() >= PluginState.RESOLVED.ordinal()).collect(Collectors.toList());\r
+    }\r
+\r
+    @Override\r
+    public List<PluginWrapper> getUnresolvedPlugins() {\r
+        return Collections.emptyList();\r
+    }\r
+\r
+    @Override\r
+    public List<PluginWrapper> getStartedPlugins() {\r
+        return getPlugins(PluginState.STARTED);\r
+    }\r
+\r
+    @Override\r
+    public PluginWrapper getPlugin(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.getPlugin(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPlugin for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void loadPlugins() {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugins!");\r
+    }\r
+\r
+    @Override\r
+    public String loadPlugin(Path pluginPath) {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugin!");\r
+    }\r
+\r
+    @Override\r
+    public void startPlugins() {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugins!");\r
+    }\r
+\r
+    @Override\r
+    public PluginState startPlugin(String pluginId) {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugin!");\r
+    }\r
+\r
+    @Override\r
+    public void stopPlugins() {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugins!");\r
+    }\r
+\r
+    @Override\r
+    public PluginState stopPlugin(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.stopPlugin(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugin for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void unloadPlugins() {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugins!");\r
+    }\r
+\r
+    @Override\r
+    public boolean unloadPlugin(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.unloadPlugin(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugin for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public boolean disablePlugin(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.disablePlugin(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute disablePlugin for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public boolean enablePlugin(String pluginId) {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute enablePlugin!");\r
+    }\r
+\r
+    @Override\r
+    public boolean deletePlugin(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.deletePlugin(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute deletePlugin for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public ClassLoader getPluginClassLoader(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.getPluginClassLoader(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginClassLoader for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public List<Class<?>> getExtensionClasses(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.getExtensionClasses(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type) {\r
+        return getExtensionClasses(type, currentPluginId);\r
+    }\r
+\r
+    @Override\r
+    public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type, String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.getExtensionClasses(type, pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public <T> List<T> getExtensions(Class<T> type) {\r
+        return getExtensions(type, currentPluginId);\r
+    }\r
+\r
+    @Override\r
+    public <T> List<T> getExtensions(Class<T> type, String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.getExtensions(type, pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public List<?> getExtensions(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.getExtensions(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Set<String> getExtensionClassNames(String pluginId) {\r
+        if (currentPluginId.equals(pluginId)) {\r
+            return original.getExtensionClassNames(pluginId);\r
+        } else {\r
+            throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClassNames for foreign pluginId!");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public ExtensionFactory getExtensionFactory() {\r
+        return original.getExtensionFactory();\r
+    }\r
+\r
+    @Override\r
+    public RuntimeMode getRuntimeMode() {\r
+        return original.getRuntimeMode();\r
+    }\r
+\r
+    @Override\r
+    public PluginWrapper whichPlugin(Class<?> clazz) {\r
+        ClassLoader classLoader = clazz.getClassLoader();\r
+        PluginWrapper plugin = getPlugin(currentPluginId);\r
+        if (plugin.getPluginClassLoader() == classLoader) {\r
+            return plugin;\r
+        }\r
+        return null;\r
+    }\r
+\r
+    @Override\r
+    public void addPluginStateListener(PluginStateListener listener) {\r
+        if (pluginStateListeners.isEmpty()) {\r
+            this.original.addPluginStateListener(listenerWrapper);\r
+        }\r
+        pluginStateListeners.add(listener);\r
+    }\r
+\r
+    @Override\r
+    public void removePluginStateListener(PluginStateListener listener) {\r
+        pluginStateListeners.remove(listener);\r
+        if (pluginStateListeners.isEmpty()) {\r
+            this.original.removePluginStateListener(listenerWrapper);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void setSystemVersion(String version) {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute setSystemVersion!");\r
+    }\r
+\r
+    @Override\r
+    public String getSystemVersion() {\r
+        return original.getSystemVersion();\r
+    }\r
+\r
+    @Override\r
+    public Path getPluginsRoot() {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoot!");\r
+    }\r
+\r
+    @Override\r
+    public List<Path> getPluginsRoots() {\r
+        throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoots!");\r
+    }\r
+\r
+    @Override\r
+    public VersionManager getVersionManager() {\r
+        return original.getVersionManager();\r
+    }\r
+\r
+    /**\r
+     * Wrapper for PluginStateListener events. will only propagate events if they match the current pluginId\r
+     * @author Wolfram Haussig\r
+     *\r
+     */\r
+    private class PluginStateListenerWrapper implements PluginStateListener {\r
+\r
+        @Override\r
+        public void pluginStateChanged(PluginStateEvent event) {\r
+            if (event.getPlugin().getPluginId().equals(currentPluginId)) {\r
+                for (PluginStateListener listener : pluginStateListeners) {\r
+                    listener.pluginStateChanged(event);\r
+                }\r
+            }\r
+        }\r
+\r
+    }\r
+}\r
diff --git a/pf4j/src/test/java/org/pf4j/SecurePluginManagerWrapperTest.java b/pf4j/src/test/java/org/pf4j/SecurePluginManagerWrapperTest.java
new file mode 100644 (file)
index 0000000..f42d83e
--- /dev/null
@@ -0,0 +1,272 @@
+package org.pf4j;\r
+\r
+import static org.junit.jupiter.api.Assertions.assertTrue;\r
+import static org.junit.Assert.assertNotNull;\r
+import static org.junit.jupiter.api.Assertions.assertEquals;\r
+import static org.junit.jupiter.api.Assertions.assertThrows;\r
+\r
+import java.io.IOException;\r
+import java.nio.file.Path;\r
+import java.util.List;\r
+\r
+import org.junit.jupiter.api.AfterEach;\r
+import org.junit.jupiter.api.BeforeEach;\r
+import org.junit.jupiter.api.Test;\r
+import org.junit.jupiter.api.io.TempDir;\r
+import org.pf4j.test.PluginJar;\r
+import org.pf4j.test.TestExtension;\r
+import org.pf4j.test.TestExtensionPoint;\r
+import org.pf4j.test.TestPlugin;\r
+\r
+public class SecurePluginManagerWrapperTest {\r
+\r
+    private static final String OTHER_PLUGIN_ID = "test-plugin-2";\r
+    private static final String THIS_PLUGIN_ID = "test-plugin-1";\r
+    private PluginJar thisPlugin;\r
+    private PluginJar otherPlugin;\r
+    private PluginManager pluginManager;\r
+    private PluginManager wrappedPluginManager;\r
+    private int pluginManagerEvents = 0;\r
+    private int wrappedPluginManagerEvents = 0;\r
+\r
+    @TempDir\r
+    Path pluginsPath;\r
+\r
+    @BeforeEach\r
+    public void setUp() throws IOException {\r
+        pluginManagerEvents = 0;\r
+        wrappedPluginManagerEvents = 0;\r
+        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();\r
+        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();\r
+\r
+        pluginManager = new JarPluginManager(pluginsPath);\r
+        wrappedPluginManager = new SecurePluginManagerWrapper(pluginManager, THIS_PLUGIN_ID);\r
+    }\r
+\r
+    @AfterEach\r
+    public void tearDown() {\r
+        pluginManager.unloadPlugins();\r
+\r
+        thisPlugin = null;\r
+        otherPlugin = null;\r
+        pluginManager = null;\r
+    }\r
+\r
+    @Test\r
+    public void pluginStateListeners() {\r
+        pluginManager.addPluginStateListener(new PluginStateListener() {\r
+            @Override\r
+            public void pluginStateChanged(PluginStateEvent event) {\r
+                pluginManagerEvents++;\r
+            }\r
+        });\r
+        wrappedPluginManager.addPluginStateListener(new PluginStateListener() {\r
+            @Override\r
+            public void pluginStateChanged(PluginStateEvent event) {\r
+                wrappedPluginManagerEvents++;\r
+            }\r
+        });\r
+        pluginManager.loadPlugins();\r
+        pluginManager.startPlugins();\r
+        assertEquals(4, pluginManagerEvents);\r
+        assertEquals(2, wrappedPluginManagerEvents);\r
+    }\r
+\r
+    @Test\r
+    public void deletePlugin() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.deletePlugin(OTHER_PLUGIN_ID));\r
+        assertTrue(wrappedPluginManager.deletePlugin(THIS_PLUGIN_ID));\r
+    }\r
+\r
+    @Test\r
+    public void disablePlugin() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.disablePlugin(OTHER_PLUGIN_ID));\r
+        assertTrue(wrappedPluginManager.disablePlugin(THIS_PLUGIN_ID));\r
+    }\r
+\r
+    @Test\r
+    public void enablePlugin() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.enablePlugin(OTHER_PLUGIN_ID));\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.enablePlugin(THIS_PLUGIN_ID));\r
+    }\r
+\r
+    @Test\r
+    public void getExtensionClasses() {\r
+        pluginManager.loadPlugins();\r
+        pluginManager.startPlugins();\r
+        assertEquals(1, wrappedPluginManager.getExtensionClasses(TestExtensionPoint.class).size());\r
+\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensionClasses(TestExtensionPoint.class, OTHER_PLUGIN_ID));\r
+        assertEquals(1, wrappedPluginManager.getExtensionClasses(TestExtensionPoint.class, THIS_PLUGIN_ID).size());\r
+\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensionClasses(OTHER_PLUGIN_ID));\r
+        assertEquals(1, wrappedPluginManager.getExtensionClasses(THIS_PLUGIN_ID).size());\r
+    }\r
+\r
+    @Test\r
+    public void getExtensionClassNames() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensionClassNames(OTHER_PLUGIN_ID));\r
+        assertEquals(1, wrappedPluginManager.getExtensionClassNames(THIS_PLUGIN_ID).size());\r
+    }\r
+\r
+    @Test\r
+    public void getExtensionFactory() {\r
+        pluginManager.loadPlugins();\r
+        assertEquals(pluginManager.getExtensionFactory(), wrappedPluginManager.getExtensionFactory());\r
+    }\r
+\r
+    @Test\r
+    public void getExtensions() {\r
+        pluginManager.loadPlugins();\r
+        pluginManager.startPlugins();\r
+        assertEquals(1, wrappedPluginManager.getExtensions(TestExtensionPoint.class).size());\r
+\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensions(TestExtensionPoint.class, OTHER_PLUGIN_ID));\r
+        assertEquals(1, wrappedPluginManager.getExtensions(TestExtensionPoint.class, THIS_PLUGIN_ID).size());\r
+\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getExtensions(OTHER_PLUGIN_ID));\r
+        assertEquals(1, wrappedPluginManager.getExtensions(THIS_PLUGIN_ID).size());\r
+    }\r
+\r
+    @Test\r
+    public void getPlugin() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPlugin(OTHER_PLUGIN_ID));\r
+        assertEquals(THIS_PLUGIN_ID, wrappedPluginManager.getPlugin(THIS_PLUGIN_ID).getPluginId());\r
+    }\r
+\r
+    @Test\r
+    public void getPluginClassLoader() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPluginClassLoader(OTHER_PLUGIN_ID));\r
+        assertNotNull(wrappedPluginManager.getPluginClassLoader(THIS_PLUGIN_ID));\r
+    }\r
+\r
+    @Test\r
+    public void getPlugins() {\r
+        pluginManager.loadPlugins();\r
+        assertEquals(2, pluginManager.getPlugins().size());\r
+        assertEquals(1, wrappedPluginManager.getPlugins().size());\r
+    }\r
+\r
+    @Test\r
+    public void getPluginsRoot() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPluginsRoot());\r
+    }\r
+\r
+    @Test\r
+    public void getPluginsRoots() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.getPluginsRoots());\r
+    }\r
+\r
+    @Test\r
+    public void getResolvedPlugins() {\r
+        pluginManager.loadPlugins();\r
+        assertEquals(2, pluginManager.getResolvedPlugins().size());\r
+        assertEquals(1, wrappedPluginManager.getResolvedPlugins().size());\r
+    }\r
+\r
+    @Test\r
+    public void getRuntimeMode() {\r
+        assertEquals(pluginManager.getRuntimeMode(), wrappedPluginManager.getRuntimeMode());\r
+    }\r
+\r
+    @Test\r
+    public void getStartedPlugins() {\r
+        pluginManager.loadPlugins();\r
+        pluginManager.startPlugins();\r
+        assertEquals(2, pluginManager.getStartedPlugins().size());\r
+        assertEquals(1, wrappedPluginManager.getStartedPlugins().size());\r
+    }\r
+\r
+    @Test\r
+    public void getSystemVersion() {\r
+        assertEquals(pluginManager.getSystemVersion(), wrappedPluginManager.getSystemVersion());\r
+    }\r
+\r
+    @Test\r
+    public void getUnresolvedPlugins() {\r
+        assertNotNull(wrappedPluginManager);\r
+        assertNotNull(wrappedPluginManager.getUnresolvedPlugins());\r
+        assertTrue(wrappedPluginManager.getUnresolvedPlugins().isEmpty());\r
+    }\r
+\r
+    @Test\r
+    public void getVersionManager() {\r
+        assertEquals(pluginManager.getVersionManager(), wrappedPluginManager.getVersionManager());\r
+    }\r
+\r
+    @Test\r
+    public void isDevelopment() {\r
+        assertEquals(pluginManager.isDevelopment(), wrappedPluginManager.isDevelopment());\r
+    }\r
+\r
+    @Test\r
+    public void isNotDevelopment() {\r
+        assertEquals(pluginManager.isNotDevelopment(), wrappedPluginManager.isNotDevelopment());\r
+    }\r
+\r
+    @Test\r
+    public void loadPlugin() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.loadPlugin(thisPlugin.path()));\r
+    }\r
+\r
+    @Test\r
+    public void loadPlugins() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.loadPlugins());\r
+    }\r
+\r
+    @Test\r
+    public void setSystemVersion() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.setSystemVersion("1.0.0"));\r
+    }\r
+\r
+    @Test\r
+    public void startPlugin() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.startPlugin(OTHER_PLUGIN_ID));\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.startPlugin(THIS_PLUGIN_ID));\r
+    }\r
+\r
+    @Test\r
+    public void startPlugins() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.startPlugins());\r
+    }\r
+\r
+    @Test\r
+    public void stopPlugin() {\r
+        pluginManager.loadPlugins();\r
+        pluginManager.startPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.stopPlugin(OTHER_PLUGIN_ID));\r
+        assertEquals(PluginState.STOPPED, wrappedPluginManager.stopPlugin(THIS_PLUGIN_ID));\r
+    }\r
+\r
+    @Test\r
+    public void stopPlugins() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.stopPlugins());\r
+    }\r
+\r
+    @Test\r
+    public void unloadPlugin() {\r
+        pluginManager.loadPlugins();\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.unloadPlugin(OTHER_PLUGIN_ID));\r
+        assertTrue(wrappedPluginManager.unloadPlugin(THIS_PLUGIN_ID));\r
+    }\r
+\r
+    @Test\r
+    public void unloadPlugins() {\r
+        assertThrows(IllegalAccessError.class, () -> wrappedPluginManager.unloadPlugins());\r
+    }\r
+\r
+    @Test\r
+    public void whichPlugin() {\r
+        pluginManager.loadPlugins();\r
+        pluginManager.startPlugins();\r
+        assertEquals(null, wrappedPluginManager.whichPlugin(pluginManager.getExtensionClasses(OTHER_PLUGIN_ID).get(0)));\r
+        assertEquals(THIS_PLUGIN_ID, wrappedPluginManager.whichPlugin(pluginManager.getExtensionClasses(THIS_PLUGIN_ID).get(0)).getPluginId());\r
+    }\r
+}\r