diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/LessonEndpointProvider.java b/webgoat-container/src/main/java/org/owasp/webgoat/LessonEndpointProvider.java deleted file mode 100644 index f0c7354bb..000000000 --- a/webgoat-container/src/main/java/org/owasp/webgoat/LessonEndpointProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.owasp.webgoat; - -import org.owasp.webgoat.lessons.LessonEndpointMapping; -import org.owasp.webgoat.plugins.PluginClassLoader; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; -import org.springframework.core.type.filter.AnnotationTypeFilter; - -import java.util.Map; - -/** - * Each lesson can define an endpoint which can support the lesson. So for example if you create a lesson which uses JavaScript and - * needs to call out to the server to fetch data you can define an endpoint in that lesson. WebGoat will pick up this endpoint and - * Spring will publish it. - *

- * Find all the defined endpoints in the lessons and register those endpoints in the Spring context so later on the - * Actuator will pick them up and expose them as real endpoints. - *

- * We use the Actuator here so we don't have to do all the hard work ourselves (endpoint strategy pattern etc) so in a - * lesson you can just define a subclass of LessonEndpoint which this class will publish as an endpoint. So we can - * dynamically load endpoints from our plugins. - */ -public class LessonEndpointProvider { - - private final String pluginBasePackage; - private final ApplicationContext parentContext; - private final PluginClassLoader classLoader; - private ListableBeanFactory context; - private DefaultListableBeanFactory providedBeans; - private BeanFactory beanFactory; - - - public LessonEndpointProvider(String pluginBasePackage, ApplicationContext parentContext, BeanFactory beanFactory, PluginClassLoader cl) { - this.pluginBasePackage = pluginBasePackage; - this.parentContext = parentContext; - this.providedBeans = new DefaultListableBeanFactory(this.parentContext.getParentBeanFactory()); - this.beanFactory = beanFactory; - this.classLoader = cl; - } - - public void registerEndpoints() { - if (context == null) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.setParent(parentContext); - context.setClassLoader(classLoader); - - ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false); - scanner.addIncludeFilter(new AnnotationTypeFilter(LessonEndpointMapping.class)); - scanner.scan(pluginBasePackage); - context.refresh(); - - Map beansOfType = context.getBeansOfType(MvcEndpoint.class); - ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; - beansOfType.forEach((k, v) -> { - configurableBeanFactory.registerSingleton(k, v); - }); - this.context = context; - } - } -} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java b/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java index 4f5c0dd1b..f14333f62 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java @@ -30,6 +30,7 @@ */ package org.owasp.webgoat; +import org.owasp.webgoat.plugins.Plugin; import org.owasp.webgoat.plugins.PluginClassLoader; import org.owasp.webgoat.plugins.PluginsLoader; import org.owasp.webgoat.session.Course; @@ -37,8 +38,11 @@ import org.owasp.webgoat.session.UserTracker; import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebgoatContext; import org.owasp.webgoat.session.WebgoatProperties; -import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -48,9 +52,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.context.support.AbstractApplicationContext; import javax.servlet.ServletContext; import java.io.File; +import java.util.List; @SpringBootApplication @PropertySource("classpath:/webgoat.properties") @@ -88,22 +94,29 @@ public class WebGoat extends SpringBootServletInitializer { return new WebSession(course, webgoatContext, context); } - @Bean - public LessonEndpointProvider lessonEndpointProvider(ApplicationContext applicationContext, BeanFactory factory, PluginClassLoader cl) { - LessonEndpointProvider lessonEndpointProvider = new LessonEndpointProvider("org.owasp.webgoat", applicationContext, factory, cl); - return lessonEndpointProvider; - } - @Bean public Course course(PluginsLoader pluginsLoader, WebgoatContext webgoatContext, ServletContext context, WebgoatProperties webgoatProperties, - LessonEndpointProvider endpointProvider) { + ApplicationContext applicationContext) { Course course = new Course(webgoatProperties); course.loadCourses(webgoatContext, context, "/"); - course.loadLessonFromPlugin(pluginsLoader.loadPlugins()); - endpointProvider.registerEndpoints(); + List plugins = pluginsLoader.loadPlugins(); + course.loadLessonFromPlugin(plugins); + plugins.forEach(p -> publishEndpointsWithSpring(p, (AbstractApplicationContext)applicationContext)); return course; } + private void publishEndpointsWithSpring(Plugin plugin, AbstractApplicationContext applicationContext) { + plugin.getLessonEndpoints().forEach(e -> { + try { + BeanDefinition beanDefinition = new RootBeanDefinition(e, Autowire.BY_TYPE.value(), true); + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); + beanFactory.registerBeanDefinition(beanDefinition.getBeanClassName(), beanDefinition); + } catch (Exception ex) { + logger.warn("Failed to register " + e.getSimpleName() + " as endpoint with Spring, skipping..."); + } + }); + } + @Bean public UserTracker userTracker() { UserTracker userTracker = UserTracker.instance(); diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/model/AttackResult.java b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/model/AttackResult.java index 787c96b80..95af8dacf 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/model/AttackResult.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/model/AttackResult.java @@ -39,11 +39,11 @@ public class AttackResult { return AttackResult.success("Congratulations"); } - public static AttackResult success(String output) { + public static AttackResult success(String feedback) { AttackResult attackResult = new AttackResult(); attackResult.lessonCompleted = true; - attackResult.feedback = "Congratulations"; - attackResult.output = output; + attackResult.feedback = feedback; + attackResult.output = ""; return attackResult; } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/Plugin.java b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/Plugin.java index 5a9772523..59fb67201 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/Plugin.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/Plugin.java @@ -1,17 +1,13 @@ package org.owasp.webgoat.plugins; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.base.Optional; import com.google.common.collect.Lists; -import org.apache.commons.io.FileUtils; import org.owasp.webgoat.lessons.AbstractLesson; +import org.owasp.webgoat.lessons.LessonEndpoint; import org.owasp.webgoat.lessons.NewLesson; import org.springframework.util.StringUtils; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; import java.util.List; @@ -35,6 +31,7 @@ public class Plugin { private Class lesson; private YmlBasedLesson ymlBasedLesson; //TODO REMOVE! private Class newLesson; + private List> lessonEndpoints = Lists.newArrayList(); private Map solutionLanguageFiles = new HashMap<>(); private Map lessonPlansLanguageFiles = new HashMap<>(); private List pluginFiles = Lists.newArrayList(); @@ -44,6 +41,10 @@ public class Plugin { this.classLoader = classLoader; } + public List> getLessonEndpoints() { + return this.lessonEndpoints; + } + /** *

findLesson.

* @@ -71,28 +72,20 @@ public class Plugin { } catch (ClassNotFoundException ce) { throw new PluginLoadingFailure("Class " + realClassName + " listed in jar but unable to load the class.", ce); } - - //TODO remove - readYmlLessonConfiguration(); } - private void readYmlLessonConfiguration() { - java.util.Optional ymlFile = this.pluginFiles.stream().filter(f -> f.getName().endsWith(".yml")).findFirst(); - if (ymlFile.isPresent()) { + public void findEndpoints(List classes) { + for (String clazzName : classes) { + String realClassName = StringUtils.trimLeadingCharacter(clazzName, '/').replaceAll("/", ".").replaceAll(".class", ""); + try { - String ymlStr = FileUtils.readFileToString(ymlFile.get()); - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - Map ymlAsMap = mapper.readValue(ymlStr, new TypeReference>() { - }); - Map lessonYml = (Map) ymlAsMap.get("lesson"); - final String category = (String) lessonYml.get("category"); - final List hints = (List) lessonYml.get("hints"); - final String title = (String) lessonYml.get("title"); - final String html = (String) lessonYml.get("id"); - this.ymlBasedLesson = new YmlBasedLesson(category, hints, title, html); - this.lesson = null; - } catch (IOException e) { - throw new PluginLoadingFailure("Unable to read yml file", e); + Class clazz = classLoader.loadClass(realClassName); + + if (LessonEndpoint.class.isAssignableFrom(clazz)) { + this.lessonEndpoints.add(clazz); + } + } catch (ClassNotFoundException ce) { + throw new PluginLoadingFailure("Class " + realClassName + " listed in jar but unable to load the class.", ce); } } } @@ -118,41 +111,6 @@ public class Plugin { } } - /** - *

rewritePaths.

- * - * @param pluginTarget a {@link java.nio.file.Path} object. - */ - public void rewritePaths(Path pluginTarget) { -// try { -// replaceInFiles(this.lesson.getSimpleName() + "_files", -// "plugin_lessons/plugin/" + this.lesson -// .getSimpleName() + "/lessonSolutions/en/" + this.lesson.getSimpleName() + "_files", -// solutionLanguageFiles.values()); -// replaceInFiles(this.lesson.getSimpleName() + "_files", -// "plugin_lessons/plugin/" + this.lesson -// .getSimpleName() + "/lessonPlans/en/" + this.lesson.getSimpleName() + "_files", -// lessonPlansLanguageFiles.values()); -// -// String[] replacements = {"jsp", "js"}; -// for (String replacement : replacements) { -// String s = String.format("plugin/%s/%s/", this.lesson.getSimpleName(), replacement); -// String r = String.format("plugin_lessons/plugin/s/%s/", this.lesson.getSimpleName(), replacement); -// replaceInFiles(s, r, pluginFiles); -// replaceInFiles(s, r, Arrays.asList(lessonSourceFile)); -// } -// -// //CSS with url('/plugin/images') should not begin with / otherwise image cannot be found -// String s = String.format("/plugin/%s/images/", this.lesson.getSimpleName()); -// String r = String -// .format("plugin_lessons/plugin/%s/images/", this.lesson.getSimpleName()); -// replaceInFiles(s, r, pluginFiles); -// replaceInFiles(s, r, Arrays.asList(lessonSourceFile)); -// } catch (IOException e) { -// throw new PluginLoadingFailure("Unable to rewrite the paths in the solutions", e); -// } - } - /** * Lesson is optional, it is also possible that the supplied jar contains only helper classes. * Lesson could be a new lesson (adoc based) or still ECS based. diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginExtractor.java b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginExtractor.java index d8b9681d5..0f7b534c9 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginExtractor.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginExtractor.java @@ -54,9 +54,7 @@ public class PluginExtractor { } } finally { plugin.findLesson(this.classes); - if (plugin.getLesson().isPresent()) { - plugin.rewritePaths(targetDirectory.toPath()); - } + plugin.findEndpoints(this.classes); zipFile.close(); } return plugin;