Lesson overview

This commit is contained in:
Nanne Baars
2016-11-29 20:25:00 +01:00
parent df29b36389
commit 1a854a500e
45 changed files with 705 additions and 345 deletions

View File

@ -32,9 +32,9 @@ package org.owasp.webgoat;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.plugins.Plugin;
import org.owasp.webgoat.plugins.PluginClassLoader;
import org.owasp.webgoat.plugins.PluginEndpointPublisher;
import org.owasp.webgoat.plugins.PluginsExtractor;
import org.owasp.webgoat.plugins.PluginsLoader;
import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.UserTracker;
@ -51,7 +51,6 @@ import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import java.io.File;
import java.util.List;
@SpringBootApplication
@Slf4j
@ -77,8 +76,8 @@ public class WebGoat extends SpringBootServletInitializer {
}
@Bean
public PluginsLoader pluginsLoader(@Qualifier("pluginTargetDirectory") File pluginTargetDirectory, PluginClassLoader classLoader) {
return new PluginsLoader(pluginTargetDirectory, classLoader);
public PluginsExtractor pluginsLoader(@Qualifier("pluginTargetDirectory") File pluginTargetDirectory, PluginClassLoader classLoader) {
return new PluginsExtractor(pluginTargetDirectory, classLoader);
}
@Bean
@ -88,27 +87,16 @@ public class WebGoat extends SpringBootServletInitializer {
}
@Bean
public Course course(PluginsLoader pluginsLoader, PluginEndpointPublisher pluginEndpointPublisher) {
Course course = new Course();
List<Plugin> plugins = pluginsLoader.loadPlugins();
if (plugins.isEmpty()) {
log.error("No lessons found if you downloaded an official release of WebGoat please take the time to");
log.error("create a new issue at https://github.com/WebGoat/WebGoat/issues/new");
log.error("For developers run 'mvn package' first from the root directory.");
log.error("Stopping WebGoat...");
System.exit(1); //we always run standalone
}
course.createLessonsFromPlugins(plugins);
plugins.forEach(p -> pluginEndpointPublisher.publish(p));
return course;
public Course course(PluginsExtractor extractor, PluginEndpointPublisher pluginEndpointPublisher) {
return new PluginsLoader(extractor, pluginEndpointPublisher).loadPlugins();
}
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
@SneakyThrows
public UserTracker userTracker(@Value("${webgoat.user.directory}") final String webgoatHome, WebSession webSession) {
UserTracker userTracker = new UserTracker(webgoatHome, webSession.getUserName());
public UserTracker userTracker(@Value("${webgoat.user.directory}") final String webgoatHome,
@Value("${webgoat.tracker.overwrite:false}") final boolean overwrite, WebSession webSession) {
UserTracker userTracker = new UserTracker(webgoatHome, webSession.getUserName(), overwrite);
userTracker.load();
return userTracker;
}

View File

@ -0,0 +1,65 @@
/**
* ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
* <p>
* Copyright (c) 2002 - 20014 Bruce Mayhew
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* Getting Source ==============
* <p>
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software
* projects.
* <p>
*/
package org.owasp.webgoat.endpoints;
import org.owasp.webgoat.lessons.AttackResult;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 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.
* </p>
* Extend this class and implement the met
* </p>
* Note: each subclass should declare this annotation otherwise the WebGoat framework cannot find your endpoint.
*/
public abstract class AssignmentEndpoint extends Endpoint {
@Autowired
private UserTracker userTracker;
@Autowired
private WebSession webSession;
//// TODO: 11/13/2016 events better fit?
protected AttackResult trackProgress(AttackResult attackResult) {
if (attackResult.assignmentSolved()) {
userTracker.assignmentSolved(webSession.getCurrentLesson(), this.getClass().getSimpleName());
} else {
userTracker.assignmentFailed(webSession.getCurrentLesson());
}
return attackResult;
}
protected WebSession getWebSession() {
return webSession;
}
}

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.lessons;
package org.owasp.webgoat.endpoints;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

View File

@ -44,9 +44,9 @@ public abstract class AbstractLesson extends Screen implements Comparable<Object
private Integer ranking;
@Setter
@Getter
private List<Class<Assignment>> assignments;
@Setter
private List<Assignment> assignments;
/**
* Constructor for the Lesson object

View File

@ -1,3 +1,10 @@
package org.owasp.webgoat.lessons;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
/**
* ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details,
@ -22,44 +29,16 @@
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software
* projects.
* <p>
*
* @author nbaars
* @version $Id: $Id
* @since November 25, 2016
*/
package org.owasp.webgoat.lessons;
@AllArgsConstructor
@Getter
public class Assignment implements Serializable {
import org.owasp.webgoat.lessons.model.AttackResult;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
private final String name;
/**
* 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.
* </p>
* Extend this class and implement the met
* </p>
* Note: each subclass should declare this annotation otherwise the WebGoat framework cannot find your endpoint.
*/
public abstract class Assignment extends Endpoint {
@Autowired
private UserTracker userTracker;
@Autowired
private WebSession webSession;
//// TODO: 11/13/2016 events better fit?
protected AttackResult trackProgress(AttackResult attackResult) {
if (attackResult.assignmentSolved()) {
userTracker.assignmentSolved(webSession.getCurrentLesson(), this);
} else {
userTracker.assignmentFailed(webSession.getCurrentLesson());
}
return attackResult;
}
protected WebSession getWebSession() {
return webSession;
}
}

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.lessons.model;
package org.owasp.webgoat.lessons;
import lombok.Getter;

View File

@ -24,7 +24,7 @@
* projects.
*
*/
package org.owasp.webgoat.lessons.model;
package org.owasp.webgoat.lessons;
/**
* <p>Hint class.</p>

View File

@ -1,7 +1,6 @@
package org.owasp.webgoat.lessons.model;
package org.owasp.webgoat.lessons;
import lombok.Getter;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.session.WebSession;
/**

View File

@ -27,7 +27,7 @@
* https://github.com/WebGoat/WebGoat, a repository for free software projects.
*
*/
package org.owasp.webgoat.lessons.model;
package org.owasp.webgoat.lessons;
import java.util.ArrayList;
import java.util.List;
@ -105,7 +105,7 @@ public class LessonMenuItem {
/**
* <p>addChild.</p>
*
* @param child a {@link org.owasp.webgoat.lessons.model.LessonMenuItem} object.
* @param child a {@link LessonMenuItem} object.
*/
public void addChild(LessonMenuItem child) {
children.add(child);

View File

@ -25,7 +25,7 @@
*
*/
package org.owasp.webgoat.lessons.model;
package org.owasp.webgoat.lessons;
/**
* <p>LessonMenuItemType class.</p>

View File

@ -27,7 +27,7 @@
* for free software projects.
*
*/
package org.owasp.webgoat.lessons.model;
package org.owasp.webgoat.lessons;
/**
* <p>RequestParameter class.</p>

View File

@ -3,9 +3,10 @@ package org.owasp.webgoat.plugins;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import lombok.Getter;
import org.owasp.webgoat.endpoints.AssignmentEndpoint;
import org.owasp.webgoat.endpoints.Endpoint;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.owasp.webgoat.lessons.Endpoint;
import org.owasp.webgoat.lessons.NewLesson;
import org.springframework.util.StringUtils;
@ -13,6 +14,7 @@ import java.io.File;
import java.nio.file.Path;
import java.util.List;
import static java.util.stream.Collectors.toList;
import static org.owasp.webgoat.plugins.PluginFileUtils.fileEndsWith;
/**
@ -27,7 +29,9 @@ public class Plugin {
private final String originationJar;
private PluginClassLoader classLoader;
private Class<NewLesson> newLesson;
private List<Class<Assignment>> assignments = Lists.newArrayList();
@Getter
private List<Class<AssignmentEndpoint>> assignments = Lists.newArrayList();
@Getter
private List<Class<Endpoint>> endpoints = Lists.newArrayList();
private List<File> pluginFiles = Lists.newArrayList();
@ -36,14 +40,6 @@ public class Plugin {
this.originationJar = originatingJar;
}
public List<Class<Assignment>> getAssignments() {
return this.assignments;
}
public List<Class<Endpoint>> getEndpoints() {
return this.endpoints;
}
/**
* <p>findLesson.</p>
*
@ -75,11 +71,12 @@ public class Plugin {
try {
Class clazz = classLoader.loadClass(realClassName);
if (Assignment.class.isAssignableFrom(clazz)) {
if (AssignmentEndpoint.class.isAssignableFrom(clazz)) {
this.assignments.add(clazz);
} else if (Endpoint.class.isAssignableFrom(clazz)) {
this.endpoints.add(clazz);
}
} else
if (Endpoint.class.isAssignableFrom(clazz)) {
this.endpoints.add(clazz);
}
} catch (ClassNotFoundException ce) {
throw new PluginLoadingFailure("Class " + realClassName + " listed in jar but unable to load the class.", ce);
}
@ -106,8 +103,8 @@ public class Plugin {
try {
if (newLesson != null) {
AbstractLesson lesson = newLesson.newInstance();
lesson.setAssignments(this.assignments);
return Optional.of(newLesson.newInstance());
lesson.setAssignments(createAssignment(assignments));
return Optional.of(lesson);
}
} catch (IllegalAccessException | InstantiationException e) {
throw new PluginLoadingFailure("Unable to instantiate the lesson " + newLesson.getName(), e);
@ -116,4 +113,9 @@ public class Plugin {
}
private List<Assignment> createAssignment(List<Class<AssignmentEndpoint>> endpoints) {
return endpoints.stream().map(e -> new Assignment(e.getSimpleName())).collect(toList());
}
}

View File

@ -0,0 +1,173 @@
package org.owasp.webgoat.plugins;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.owasp.webgoat.i18n.LabelProvider;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* <p>PluginsLoader class.</p>
*
* @author dm
* @version $Id: $Id
*/
@Slf4j
public class PluginsExtractor {
private static final String WEBGOAT_PLUGIN_EXTENSION = "jar";
private static final int BUFFER_SIZE = 32 * 1024;
private final File pluginTargetDirectory;
private final PluginClassLoader classLoader;
public PluginsExtractor(File pluginTargetDirectory, PluginClassLoader pluginClassLoader) {
this.classLoader = pluginClassLoader;
this.pluginTargetDirectory = pluginTargetDirectory;
}
/**
* <p>loadPlugins.</p>
*
* @return a {@link java.util.List} object.
*/
public List<Plugin> loadPlugins() {
List<Plugin> plugins = Lists.newArrayList();
try {
URL location = this.getClass().getProtectionDomain().getCodeSource().getLocation();
log.trace("Determining whether we run as standalone jar or as directory...");
if (ResourceUtils.isFileURL(location)) {
log.trace("Running from directory, copying lessons from {}", location.toString());
extractToTargetDirectoryFromExplodedDirectory(ResourceUtils.getFile(location));
} else {
log.trace("Running from standalone jar, extracting lessons from {}", location.toString());
extractToTargetDirectoryFromJarFile(ResourceUtils.getFile(ResourceUtils.extractJarFileURL(location)));
}
List<URL> jars = listJars();
plugins = processPlugins(jars);
} catch (Exception e) {
log.error("Loading plugins failed", e);
}
return plugins;
}
private void extractToTargetDirectoryFromJarFile(File jarFile) throws IOException {
ZipFile jar = new ZipFile(jarFile);
Enumeration<? extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().contains("plugin_lessons") && zipEntry.getName().endsWith(".jar")) {
unpack(jar, zipEntry);
}
}
}
private void unpack(ZipFile jar, ZipEntry zipEntry) throws IOException {
try (InputStream inputStream = jar.getInputStream(zipEntry)) {
String name = zipEntry.getName();
if (name.lastIndexOf("/") != -1) {
name = name.substring(name.lastIndexOf("/") + 1);
}
try (OutputStream outputStream = new FileOutputStream(new File(pluginTargetDirectory, name))) {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
}
}
log.trace("Extracting {} to {}", jar.getName(), pluginTargetDirectory);
}
private void extractToTargetDirectoryFromExplodedDirectory(File directory) throws IOException {
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (dir.endsWith("plugin_lessons")) {
log.trace("Copying {} to {}", dir.toString(), pluginTargetDirectory);
FileUtils.copyDirectory(dir.toFile(), pluginTargetDirectory);
}
return FileVisitResult.CONTINUE;
}
});
}
private List<URL> listJars() throws Exception {
final List<URL> jars = Lists.newArrayList();
Files.walkFileTree(Paths.get(pluginTargetDirectory.toURI()), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (PluginFileUtils.fileEndsWith(file, WEBGOAT_PLUGIN_EXTENSION)) {
jars.add(file.toUri().toURL());
log.trace("Found jar file at location: {}", file.toString());
}
return FileVisitResult.CONTINUE;
}
});
return jars;
}
private List<Plugin> processPlugins(List<URL> jars) throws Exception {
final ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
final List<Plugin> plugins = Lists.newArrayList();
final CompletionService<Plugin> completionService = new ExecutorCompletionService<>(executorService);
final List<Callable<Plugin>> callables = extractJars(jars);
callables.forEach(s -> completionService.submit(s));
int n = callables.size();
for (int i = 0; i < n; i++) {
Plugin plugin = completionService.take().get();
if (plugin.getLesson().isPresent()) {
log.trace("Plugin jar '{}' contains a lesson, loading into WebGoat...", plugin.getOriginationJar());
plugins.add(plugin);
} else {
log.trace("Plugin jar: '{}' does not contain a lesson not processing as a plugin (can be a utility jar)",
plugin.getOriginationJar());
}
}
LabelProvider.updatePluginResources(
pluginTargetDirectory.toPath().resolve("plugin/i18n/WebGoatLabels.properties"));
return plugins;
} finally {
executorService.shutdown();
}
}
private List<Callable<Plugin>> extractJars(List<URL> jars) {
List<Callable<Plugin>> extractorCallables = Lists.newArrayList();
for (final URL jar : jars) {
classLoader.addURL(jar);
extractorCallables.add(() -> {
PluginExtractor extractor = new PluginExtractor();
return extractor.extractJarFile(ResourceUtils.getFile(jar), pluginTargetDirectory, classLoader);
});
}
return extractorCallables;
}
}

View File

@ -1,173 +1,72 @@
package org.owasp.webgoat.plugins;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.owasp.webgoat.i18n.LabelProvider;
import org.springframework.util.ResourceUtils;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.NewLesson;
import org.owasp.webgoat.session.Course;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* <p>PluginsLoader class.</p>
* ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
* <p>
* Copyright (c) 2002 - 20014 Bruce Mayhew
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* Getting Source ==============
* <p>
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software
* projects.
* <p>
*
* @author dm
* @author nbaars
* @version $Id: $Id
* @since November 25, 2016
*/
@AllArgsConstructor
@Slf4j
public class PluginsLoader {
private static final String WEBGOAT_PLUGIN_EXTENSION = "jar";
private static final int BUFFER_SIZE = 32 * 1024;
private final File pluginTargetDirectory;
private final PluginClassLoader classLoader;
public PluginsLoader(File pluginTargetDirectory, PluginClassLoader pluginClassLoader) {
this.classLoader = pluginClassLoader;
this.pluginTargetDirectory = pluginTargetDirectory;
}
private final PluginsExtractor extractor;
private final PluginEndpointPublisher pluginEndpointPublisher;
/**
* <p>loadPlugins.</p>
*
* @return a {@link java.util.List} object.
* <p>createLessonsFromPlugins.</p>
*/
public List<Plugin> loadPlugins() {
List<Plugin> plugins = Lists.newArrayList();
try {
URL location = this.getClass().getProtectionDomain().getCodeSource().getLocation();
log.trace("Determining whether we run as standalone jar or as directory...");
if (ResourceUtils.isFileURL(location)) {
log.trace("Running from directory, copying lessons from {}", location.toString());
extractToTargetDirectoryFromExplodedDirectory(ResourceUtils.getFile(location));
} else {
log.trace("Running from standalone jar, extracting lessons from {}", location.toString());
extractToTargetDirectoryFromJarFile(ResourceUtils.getFile(ResourceUtils.extractJarFileURL(location)));
}
List<URL> jars = listJars();
plugins = processPlugins(jars);
} catch (Exception e) {
log.error("Loading plugins failed", e);
}
return plugins;
}
private void extractToTargetDirectoryFromJarFile(File jarFile) throws IOException {
ZipFile jar = new ZipFile(jarFile);
Enumeration<? extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().contains("plugin_lessons") && zipEntry.getName().endsWith(".jar")) {
unpack(jar, zipEntry);
public Course loadPlugins() {
List<AbstractLesson> lessons = Lists.newArrayList();
for (Plugin plugin : extractor.loadPlugins()) {
try {
NewLesson lesson = (NewLesson) plugin.getLesson().get();
lessons.add(lesson);
pluginEndpointPublisher.publish(plugin);
} catch (Exception e) {
log.error("Error in loadLessons: ", e);
}
}
}
private void unpack(ZipFile jar, ZipEntry zipEntry) throws IOException {
try (InputStream inputStream = jar.getInputStream(zipEntry)) {
String name = zipEntry.getName();
if (name.lastIndexOf("/") != -1) {
name = name.substring(name.lastIndexOf("/") + 1);
}
try (OutputStream outputStream = new FileOutputStream(new File(pluginTargetDirectory, name))) {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
}
if (lessons.isEmpty()) {
log.error("No lessons found if you downloaded an official release of WebGoat please take the time to");
log.error("create a new issue at https://github.com/WebGoat/WebGoat/issues/new");
log.error("For developers run 'mvn package' first from the root directory.");
log.error("Stopping WebGoat...");
System.exit(1); //we always run standalone
}
log.trace("Extracting {} to {}", jar.getName(), pluginTargetDirectory);
return new Course(lessons);
}
private void extractToTargetDirectoryFromExplodedDirectory(File directory) throws IOException {
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (dir.endsWith("plugin_lessons")) {
log.trace("Copying {} to {}", dir.toString(), pluginTargetDirectory);
FileUtils.copyDirectory(dir.toFile(), pluginTargetDirectory);
}
return FileVisitResult.CONTINUE;
}
});
}
private List<URL> listJars() throws Exception {
final List<URL> jars = Lists.newArrayList();
Files.walkFileTree(Paths.get(pluginTargetDirectory.toURI()), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (PluginFileUtils.fileEndsWith(file, WEBGOAT_PLUGIN_EXTENSION)) {
jars.add(file.toUri().toURL());
log.trace("Found jar file at location: {}", file.toString());
}
return FileVisitResult.CONTINUE;
}
});
return jars;
}
private List<Plugin> processPlugins(List<URL> jars) throws Exception {
final ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
final List<Plugin> plugins = Lists.newArrayList();
final CompletionService<Plugin> completionService = new ExecutorCompletionService<>(executorService);
final List<Callable<Plugin>> callables = extractJars(jars);
callables.forEach(s -> completionService.submit(s));
int n = callables.size();
for (int i = 0; i < n; i++) {
Plugin plugin = completionService.take().get();
if (plugin.getLesson().isPresent()) {
log.trace("Plugin jar '{}' contains a lesson, loading into WebGoat...", plugin.getOriginationJar());
plugins.add(plugin);
} else {
log.trace("Plugin jar: '{}' does not contain a lesson not processing as a plugin (can be a utility jar)",
plugin.getOriginationJar());
}
}
LabelProvider.updatePluginResources(
pluginTargetDirectory.toPath().resolve("plugin/i18n/WebGoatLabels.properties"));
return plugins;
} finally {
executorService.shutdown();
}
}
private List<Callable<Plugin>> extractJars(List<URL> jars) {
List<Callable<Plugin>> extractorCallables = Lists.newArrayList();
for (final URL jar : jars) {
classLoader.addURL(jar);
extractorCallables.add(() -> {
PluginExtractor extractor = new PluginExtractor();
return extractor.extractJarFile(ResourceUtils.getFile(jar), pluginTargetDirectory, classLoader);
});
}
return extractorCallables;
}
}

View File

@ -6,7 +6,7 @@
package org.owasp.webgoat.service;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.model.Hint;
import org.owasp.webgoat.lessons.Hint;
import org.owasp.webgoat.session.WebSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@ -1,6 +1,6 @@
package org.owasp.webgoat.service;
import org.owasp.webgoat.lessons.model.LessonInfoModel;
import org.owasp.webgoat.lessons.LessonInfoModel;
import org.owasp.webgoat.session.WebSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@ -24,7 +24,7 @@ public class LessonInfoService {
/**
* <p>getLessonInfo.</p>
*
* @return a {@link org.owasp.webgoat.lessons.model.LessonInfoModel} object.
* @return a {@link LessonInfoModel} object.
*/
@RequestMapping(path = "/service/lessoninfo.mvc", produces = "application/json")
public @ResponseBody

View File

@ -31,8 +31,8 @@ package org.owasp.webgoat.service;
import lombok.AllArgsConstructor;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.model.LessonMenuItem;
import org.owasp.webgoat.lessons.model.LessonMenuItemType;
import org.owasp.webgoat.lessons.LessonMenuItem;
import org.owasp.webgoat.lessons.LessonMenuItemType;
import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;

View File

@ -1,9 +1,13 @@
package org.owasp.webgoat.service;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.owasp.webgoat.i18n.LabelManager;
import org.owasp.webgoat.lessons.model.LessonInfoModel;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.owasp.webgoat.lessons.LessonInfoModel;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession;
@ -11,6 +15,8 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -47,4 +53,38 @@ public class LessonProgressService {
json.put("successMessage", successMessage);
return json;
}
/**
* Endpoint for fetching the complete lesson overview which informs the user about whether all the assignments are solved.
* Used as the last page of the lesson to generate a lesson overview.
*
* @return list of assignments
*/
@RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json")
@ResponseBody
public List<LessonOverview> lessonOverview() {
AbstractLesson currentLesson = webSession.getCurrentLesson();
LessonTracker lessonTracker = userTracker.getLessonTracker(currentLesson);
return toJson(lessonTracker.getLessonOverview());
}
private List<LessonOverview> toJson(Map<Assignment, Boolean> map) {
ArrayList<LessonOverview> result = Lists.newArrayList();
for (Map.Entry<Assignment, Boolean> entry : map.entrySet()) {
result.add(new LessonOverview(entry.getKey(), entry.getValue()));
}
return result;
}
@AllArgsConstructor
@Getter
//Jackson does not really like returning a map of <Assignment, Boolean> directly, see http://stackoverflow.com/questions/11628698/can-we-make-object-as-key-in-map-when-using-json
//so creating intermediate object is the easiest solution
private static class LessonOverview {
private Assignment assignment;
private Boolean solved;
}
}

View File

@ -30,7 +30,7 @@
package org.owasp.webgoat.service;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.model.RequestParameter;
import org.owasp.webgoat.lessons.RequestParameter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

View File

@ -1,10 +1,9 @@
package org.owasp.webgoat.session;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import org.owasp.webgoat.plugins.Plugin;
import java.util.LinkedList;
import java.util.List;
@ -42,6 +41,7 @@ import static java.util.stream.Collectors.toList;
* @since October 28, 2003
*/
@Slf4j
@AllArgsConstructor
public class Course {
private List<AbstractLesson> lessons = new LinkedList<>();
@ -89,18 +89,5 @@ public class Course {
this.lessons = lessons;
}
/**
* <p>createLessonsFromPlugins.</p>
*/
public void createLessonsFromPlugins(List<Plugin> plugins) {
for (Plugin plugin : plugins) {
try {
NewLesson lesson = (NewLesson) plugin.getLesson().get();
lesson.setAssignments(plugin.getAssignments());
lessons.add(lesson);
} catch (Exception e) {
log.error("Error in loadLessons: ", e);
}
}
}
}

View File

@ -1,11 +1,16 @@
package org.owasp.webgoat.session;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.Getter;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -42,12 +47,17 @@ import java.util.stream.Collectors;
*/
public class LessonTracker implements Serializable {
private static final long serialVersionUID = 5410058267505412928L;
private final List<String> assignments;
private final Set<String> solvedAssignments = Sets.newHashSet();
private final Set<Assignment> solvedAssignments = Sets.newHashSet();
private final List<Assignment> allAssignments = Lists.newArrayList();
@Getter
private int numberOfAttempts = 0;
public LessonTracker(AbstractLesson lesson) {
this.assignments = lesson.getAssignments().stream().map(a -> a.getSimpleName()).collect(Collectors.toList());
allAssignments.addAll(lesson.getAssignments());
}
public Optional<Assignment> getAssignment(String name) {
return allAssignments.stream().filter(a -> a.getName().equals(name)).findFirst();
}
/**
@ -56,14 +66,14 @@ public class LessonTracker implements Serializable {
* @param solvedAssignment the assignment which the user solved
*/
public void assignmentSolved(String solvedAssignment) {
solvedAssignments.add(solvedAssignment);
getAssignment(solvedAssignment).ifPresent(a -> solvedAssignments.add(a));
}
/**
* @return did they user solved all assignments for the lesson?
* @return did they user solved all solvedAssignments for the lesson?
*/
public boolean isLessonSolved() {
return solvedAssignments.size() == assignments.size();
return allAssignments.size() == solvedAssignments.size();
}
/**
@ -79,4 +89,16 @@ public class LessonTracker implements Serializable {
void reset() {
solvedAssignments.clear();
}
/**
* @return list containing all the assignments solved or not
*/
public Map<Assignment, Boolean> getLessonOverview() {
List<Assignment> notSolved = allAssignments.stream()
.filter(i -> !solvedAssignments.contains(i))
.collect(Collectors.toList());
Map<Assignment, Boolean> overview = notSolved.stream().collect(Collectors.toMap(a -> a, b -> false));
overview.putAll(solvedAssignments.stream().collect(Collectors.toMap(a -> a, b -> true)));
return overview;
}
}

View File

@ -1,10 +1,9 @@
package org.owasp.webgoat.session;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SerializationUtils;
@ -47,11 +46,13 @@ public class UserTracker {
private final String webgoatHome;
private final String user;
private final boolean overwrite;
private Map<String, LessonTracker> storage = new HashMap<>();
public UserTracker(@Value("${webgoat.user.directory}") final String webgoatHome, final String user) {
public UserTracker(final String webgoatHome, final String user, final boolean overwrite) {
this.webgoatHome = webgoatHome;
this.user = user;
this.overwrite = overwrite;
}
/**
@ -69,10 +70,10 @@ public class UserTracker {
return lessonTracker;
}
public void assignmentSolved(AbstractLesson lesson, Assignment assignment) {
public void assignmentSolved(AbstractLesson lesson, String assignmentName) {
LessonTracker lessonTracker = getLessonTracker(lesson);
lessonTracker.incrementAttempts();
lessonTracker.assignmentSolved(assignment.getClass().getSimpleName());
lessonTracker.assignmentSolved(assignmentName);
save();
}
@ -85,7 +86,9 @@ public class UserTracker {
@SneakyThrows
public void load() {
File file = new File(webgoatHome, user + ".progress");
if (file.exists() && file.isFile()) {
if (overwrite) {
this.storage = Maps.newHashMap();
} else if (file.exists() && file.isFile()) {
this.storage = (Map<String, LessonTracker>) SerializationUtils.deserialize(FileCopyUtils.copyToByteArray(file));
}
}

View File

@ -6,7 +6,7 @@ server.port=8080
logging.level.org.springframework=WARN
logging.level.org.springframework.boot.devtools=DEBUG
logging.level.org.springframework.boot.devtools=WARN
logging.level.org.owasp=DEBUG
logging.level.org.owasp.webgoat=TRACE
@ -14,9 +14,10 @@ spring.thymeleaf.cache=false
spring.thymeleaf.content-type=text/html
security.enable-csrf=false
spring.devtools.restart.enabled=true
spring.devtools.restart.enabled=false
webgoat.tracker.overwrite=true
webgoat.user.directory=${user.home}/.webgoat/
webgoat.build.version=@project.version@
webgoat.build.number=@build.number@