From 8d2771c1089b92374e8a5b776250609e0709cba7 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Tue, 22 Sep 2015 20:38:19 +0200 Subject: [PATCH 1/3] Adding the ability to reload plugins directly from the interface instead of restarting Tomcat --- README.MD | 6 ++ pom.xml | 1 - webgoat-classloader/.gitignore | 4 -- webgoat-classloader/pom.xml | 39 ----------- .../classloader/PluginClassLoader.java | 46 ------------- webgoat-container/pom.xml | 33 --------- .../owasp/webgoat/lessons/AbstractLesson.java | 1 + .../org/owasp/webgoat/plugins/Plugin.java | 9 +-- .../plugins/PluginBackgroundLoader.java | 4 +- .../webgoat/plugins/PluginExtractor.java | 2 +- .../owasp/webgoat/plugins/PluginsLoader.java | 34 +++++----- .../classloader/PluginClassLoaderFactory.java | 31 +++++++++ .../PluginClassLoaderRepository.java | 35 ++++++++++ .../webgoat/service/PluginReloadService.java | 67 +++++++++++++++++++ .../org/owasp/webgoat/session/Course.java | 11 ++- .../webgoat/session/WebgoatProperties.java | 6 +- .../src/main/webapp/META-INF/context.xml | 2 +- .../src/main/webapp/WEB-INF/context.xml | 4 +- 18 files changed, 176 insertions(+), 159 deletions(-) delete mode 100644 webgoat-classloader/.gitignore delete mode 100644 webgoat-classloader/pom.xml delete mode 100644 webgoat-classloader/src/main/java/org/owasp/webgoat/classloader/PluginClassLoader.java create mode 100644 webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderFactory.java create mode 100644 webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderRepository.java create mode 100644 webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java diff --git a/README.MD b/README.MD index e30413d36..74ce35e33 100644 --- a/README.MD +++ b/README.MD @@ -149,3 +149,9 @@ cp webgoat-container/target/webgoat-container-7.0-SNAPSHOT-war-exec.jar webgoat-container - webgoat-classloader diff --git a/webgoat-classloader/.gitignore b/webgoat-classloader/.gitignore deleted file mode 100644 index 1428faf7e..000000000 --- a/webgoat-classloader/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target/ -.idea/ -*.iml -dependency-reduced-pom.xml \ No newline at end of file diff --git a/webgoat-classloader/pom.xml b/webgoat-classloader/pom.xml deleted file mode 100644 index 835a8dae4..000000000 --- a/webgoat-classloader/pom.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - webgoat-classloader - 4.0.0 - webgoat-classloader - jar - - - org.owasp.webgoat - webgoat-parent - 7.0-SNAPSHOT - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - 1.7 - 1.7 - ISO-8859-1 - - - - - - - - org.apache.tomcat - tomcat-catalina - ${tomcat-catalina.version} - - - - diff --git a/webgoat-classloader/src/main/java/org/owasp/webgoat/classloader/PluginClassLoader.java b/webgoat-classloader/src/main/java/org/owasp/webgoat/classloader/PluginClassLoader.java deleted file mode 100644 index 4be6fefda..000000000 --- a/webgoat-classloader/src/main/java/org/owasp/webgoat/classloader/PluginClassLoader.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.owasp.webgoat.classloader; - -import org.apache.catalina.loader.WebappClassLoader; - -import java.net.URL; -import java.util.List; - -/** - * Classloader for Tomcat. - * - * We need to provide this classloader otherwise jsp files cannot be compiled. JspContextWrapper uses - * Thread.currentThread().getContextClassLoader() but during initialisation it loads the classloader which means - * this classloader will never pickup the plugin classes. - * - * With this loader we can add jars we load during the plugin loading and the jsp will pick it up because this is - * the same classloader. - * - * @version $Id: $Id - */ -public class PluginClassLoader extends WebappClassLoader { - /** - *

Constructor for PluginClassLoader.

- */ - public PluginClassLoader() { - } - - /** - *

Constructor for PluginClassLoader.

- * - * @param parent a {@link java.lang.ClassLoader} object. - */ - public PluginClassLoader(ClassLoader parent) { - super(parent); - } - - /** - *

addURL.

- * - * @param urls a {@link java.util.List} object. - */ - public void addURL(List urls) { - for (URL url : urls) { - super.addURL(url); - } - } -} diff --git a/webgoat-container/pom.xml b/webgoat-container/pom.xml index 569abbd4a..50b89a045 100644 --- a/webgoat-container/pom.xml +++ b/webgoat-container/pom.xml @@ -39,11 +39,6 @@ ${project.basedir}/src/main/webapp/WEB-INF/context.xml - - org.owasp.webgoat - webgoat-classloader - ${project.version} - org.owasp.webgoat webgoat-container @@ -57,15 +52,6 @@ exec-war-only package - - - - org.owasp.webgoat - webgoat-classloader - ${project.version} - - - tomcat-startup @@ -195,11 +181,6 @@ ${project.basedir}/src/main/webapp/WEB-INF/context.xml - - org.owasp.webgoat - webgoat-classloader - ${project.version} - org.owasp.webgoat webgoat-container @@ -213,15 +194,6 @@ exec-war-only package - - - - org.owasp.webgoat - webgoat-classloader - ${project.version} - - - @@ -239,11 +211,6 @@ - - org.owasp.webgoat - webgoat-classloader - ${project.version} - javax.activation activation diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java index 2307f484e..e2baaa458 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java @@ -1032,4 +1032,5 @@ public abstract class AbstractLesson extends Screen implements ComparableWEBGOAT_PLUGIN_EXTENSION="jar" */ - protected static final String WEBGOAT_PLUGIN_EXTENSION = "jar"; + private static final String WEBGOAT_PLUGIN_EXTENSION = "jar"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Path pluginSource; + private final PluginClassLoaderRepository repository; private Path pluginTarget; - /** *

Constructor for PluginsLoader.

* * @param pluginSource a {@link java.nio.file.Path} object. * @param pluginTarget a {@link java.nio.file.Path} object. */ - public PluginsLoader(Path pluginSource, Path pluginTarget) { - Preconditions.checkNotNull(pluginSource, "plugin source cannot be null"); - Preconditions.checkNotNull(pluginTarget, "plugin target cannot be null"); - - this.pluginSource = pluginSource; - this.pluginTarget = pluginTarget; + public PluginsLoader(PluginClassLoaderRepository repository, Path pluginSource, Path pluginTarget) { + this.pluginSource = Objects.requireNonNull(pluginSource, "plugin source cannot be null"); + this.pluginTarget = Objects.requireNonNull(pluginTarget, "plugin target cannot be null"); + this.repository = Objects.requireNonNull(repository, "repository cannot be null"); } /** @@ -58,21 +57,26 @@ public class PluginsLoader implements Runnable { * @return a {@link java.util.List} object. */ public List loadPlugins(final boolean reload) { - final PluginClassLoader cl = (PluginClassLoader) Thread.currentThread().getContextClassLoader(); List plugins = Lists.newArrayList(); try { PluginFileUtils.createDirsIfNotExists(pluginTarget); cleanupExtractedPluginsDirectory(); List jars = listJars(); - cl.addURL(jars); - plugins = processPlugins(jars, reload); + initClassLoader(jars); + plugins = processPlugins(jars); } catch (Exception e) { logger.error("Loading plugins failed", e); } return plugins; } + private void initClassLoader(List jars) { + URLClassLoader classLoader = PluginClassLoaderFactory.createClassLoader(jars); + this.repository.replaceClassLoader(classLoader); + Thread.currentThread().setContextClassLoader(classLoader); + } + private void cleanupExtractedPluginsDirectory() { Path i18nDirectory = pluginTarget.resolve("plugin/i18n/"); FileUtils.deleteQuietly(i18nDirectory.toFile()); @@ -93,7 +97,7 @@ public class PluginsLoader implements Runnable { return jars; } - private List processPlugins(List jars, boolean reload) throws Exception { + private List processPlugins(List jars) throws Exception { final List plugins = Lists.newArrayList(); final ExecutorService executorService = Executors.newFixedThreadPool(10); final CompletionService completionService = new ExecutorCompletionService<>(executorService); diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderFactory.java b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderFactory.java new file mode 100644 index 000000000..cd20a0215 --- /dev/null +++ b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderFactory.java @@ -0,0 +1,31 @@ +package org.owasp.webgoat.plugins.classloader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; + +/** + * Create a classloader for the plugins + */ +public class PluginClassLoaderFactory { + + private static final Logger logger = LoggerFactory.getLogger(PluginClassLoaderFactory.class); + + public static URLClassLoader createClassLoader(List urls) { + return new URLClassLoader(urls.toArray(new URL[urls.size()]), determineParentClassLoader()); + } + + private static ClassLoader determineParentClassLoader() { + ClassLoader parent = Thread.currentThread().getContextClassLoader(); + try { + parent = Thread.currentThread().getContextClassLoader().getParent() + .loadClass("org.apache.jasper.runtime.JspContextWrapper").getClassLoader(); + } catch (ClassNotFoundException e) { + logger.info("Tomcat JspContextWrapper not found, probably not running on Tomcat..."); + } + return parent; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderRepository.java b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderRepository.java new file mode 100644 index 000000000..994aea0b5 --- /dev/null +++ b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/classloader/PluginClassLoaderRepository.java @@ -0,0 +1,35 @@ +package org.owasp.webgoat.plugins.classloader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URLClassLoader; + +/** + * Holds the classloaders for the plugins. For now all the plugins are loaded by the same + * classloader. This class can be extended to contain a classloader per plugin. + */ +public class PluginClassLoaderRepository { + + private static final Logger logger = LoggerFactory.getLogger(PluginClassLoaderRepository.class); + private URLClassLoader currentPluginLoader; + + /** + * @return the plugin classloader + */ + public URLClassLoader get() { + return currentPluginLoader; + } + + public void replaceClassLoader(URLClassLoader classLoader) { + if (this.currentPluginLoader != null) { + try { + this.currentPluginLoader.close(); + } catch (IOException e) { + logger.warn("Unable to close the current classloader", e); + } + } + this.currentPluginLoader = classLoader; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java b/webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java new file mode 100644 index 000000000..374e6272f --- /dev/null +++ b/webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java @@ -0,0 +1,67 @@ +/** + * ************************************************************************************************* + * + * + * This file is part of WebGoat, an Open Web Application Security Project + * utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 20014 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at + * https://github.com/WebGoat/WebGoat, a repository for free software projects. + * + * For details, please see http://webgoat.github.io + */ +package org.owasp.webgoat.service; + +import org.owasp.webgoat.session.WebSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpSession; + +/** + *

PluginReloadService class.

+ * + * @author nbaars + * @version $Id: $Id + */ +@Controller +public class PluginReloadService extends BaseService { + + private static final Logger logger = LoggerFactory.getLogger(PluginReloadService.class); + + /** + * Reload all the plugins + * + * @param session a {@link HttpSession} object. + */ + @RequestMapping(value = "/reloadplugins.mvc") + public @ResponseBody + ResponseEntity reloadPlugins(HttpSession session) { + WebSession webSession = (WebSession) session.getAttribute(WebSession.SESSION); + webSession.getCourse().loadLessonFromPlugin(session.getServletContext()); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/Course.java b/webgoat-container/src/main/java/org/owasp/webgoat/session/Course.java index cb3c1a188..d4cc0ed2b 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/session/Course.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/session/Course.java @@ -5,6 +5,7 @@ import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.Category; import org.owasp.webgoat.plugins.Plugin; import org.owasp.webgoat.plugins.PluginsLoader; +import org.owasp.webgoat.plugins.classloader.PluginClassLoaderRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,14 +66,13 @@ public class Course { final Logger logger = LoggerFactory.getLogger(Course.class); + private final PluginClassLoaderRepository repository = new PluginClassLoaderRepository(); private final List lessons = new LinkedList(); private final static String PROPERTIES_FILENAME = HammerHead.propertiesPath; private WebgoatProperties properties = null; - private final List files = new LinkedList(); - private WebgoatContext webgoatContext; /** @@ -327,7 +327,7 @@ public class Course { return null; } - private void loadLessonFromPlugin(ServletContext context) { + public void loadLessonFromPlugin(ServletContext context) { logger.debug("Loading plugins into cache"); String pluginPath = context.getRealPath("plugin_lessons"); String targetPath = context.getRealPath("plugin_extracted"); @@ -336,8 +336,8 @@ public class Course { logger.error("Plugins directory {} not found", pluginPath); return; } - - List plugins = new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)).loadPlugins(true); + lessons.clear(); + List plugins = new PluginsLoader(repository, Paths.get(pluginPath), Paths.get(targetPath)).loadPlugins(true); for (Plugin plugin : plugins) { try { AbstractLesson lesson = plugin.getLesson().get(); @@ -376,5 +376,4 @@ public class Course { LegacyLoader loader = new LegacyLoader(); lessons.addAll(loader.loadLessons(webgoatContext, context, path, properties)); } - } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/WebgoatProperties.java b/webgoat-container/src/main/java/org/owasp/webgoat/session/WebgoatProperties.java index 3edb20d6b..3452142c4 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/session/WebgoatProperties.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/session/WebgoatProperties.java @@ -1,12 +1,12 @@ package org.owasp.webgoat.session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; -import org.owasp.webgoat.HammerHead; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * ************************************************************************************************* diff --git a/webgoat-container/src/main/webapp/META-INF/context.xml b/webgoat-container/src/main/webapp/META-INF/context.xml index dc8faa5a6..adc5cb807 100644 --- a/webgoat-container/src/main/webapp/META-INF/context.xml +++ b/webgoat-container/src/main/webapp/META-INF/context.xml @@ -1,4 +1,4 @@ - + diff --git a/webgoat-container/src/main/webapp/WEB-INF/context.xml b/webgoat-container/src/main/webapp/WEB-INF/context.xml index dc8faa5a6..658058885 100644 --- a/webgoat-container/src/main/webapp/WEB-INF/context.xml +++ b/webgoat-container/src/main/webapp/WEB-INF/context.xml @@ -1,4 +1,2 @@ - - - + From 87d196c4272cf222e3a3f02365d0a44e864d4ade Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Tue, 22 Sep 2015 21:13:41 +0200 Subject: [PATCH 2/3] Return indication that plugins have been reloaded --- .../java/org/owasp/webgoat/service/PluginReloadService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java b/webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java index 374e6272f..9415446cc 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/service/PluginReloadService.java @@ -59,9 +59,9 @@ public class PluginReloadService extends BaseService { */ @RequestMapping(value = "/reloadplugins.mvc") public @ResponseBody - ResponseEntity reloadPlugins(HttpSession session) { + ResponseEntity reloadPlugins(HttpSession session) { WebSession webSession = (WebSession) session.getAttribute(WebSession.SESSION); webSession.getCourse().loadLessonFromPlugin(session.getServletContext()); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity("Plugins reload refresh the WebGoat page!",HttpStatus.OK); } } From 9218a0dbb011917014d320e3e97be45defea3759 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Tue, 22 Sep 2015 21:17:09 +0200 Subject: [PATCH 3/3] Updated the README.MD with reloading functionality of plugins --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 74ce35e33..947670062 100644 --- a/README.MD +++ b/README.MD @@ -154,4 +154,4 @@ Browse to [http://localhost:8080/WebGoat](http://localhost:8080/WebGoat) and hap ## Reloading plugins If you want to reload all the plugin visit the following url: `http://localhost:8080/WebGoat/service/reloadplugins.mvc` -After you receive a 200/OK the page can be reloaded. \ No newline at end of file +in a new browser tab. After reloading a message will appear and you can refresh the WebGoat browser tab. \ No newline at end of file