lessons = course.getLessons();
ReportCard reportCard = new ReportCard();
reportCard.setTotalNumberOfLessons(course.getTotalOfLessons());
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/service/RestartLessonService.java b/webgoat-container/src/main/java/org/owasp/webgoat/service/RestartLessonService.java
index 34650cc1c..4ea036996 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/service/RestartLessonService.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/service/RestartLessonService.java
@@ -26,8 +26,9 @@ package org.owasp.webgoat.service;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson;
-import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession;
+import org.owasp.webgoat.users.UserTracker;
+import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -45,7 +46,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
public class RestartLessonService {
private final WebSession webSession;
- private final UserTracker userTracker;
+ private UserTrackerRepository userTrackerRepository;
/**
* Returns current lesson
@@ -58,6 +59,8 @@ public class RestartLessonService {
AbstractLesson al = webSession.getCurrentLesson();
log.debug("Restarting lesson: " + al);
+ UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
userTracker.reset(al);
+ userTrackerRepository.save(userTracker);
}
}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/DatabaseUtilities.java b/webgoat-container/src/main/java/org/owasp/webgoat/session/DatabaseUtilities.java
index 4692528b4..1d015ff3d 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/session/DatabaseUtilities.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/session/DatabaseUtilities.java
@@ -7,8 +7,6 @@ import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
-import org.springframework.beans.factory.annotation.Autowired;
-
/**
*************************************************************************************************
@@ -39,6 +37,8 @@ import org.springframework.beans.factory.annotation.Autowired;
* @author Jeff Williams Aspect Security
* @version $Id: $Id
*/
+//TODO: class we need to refactor to new structure, we can put the connection in the current session of the user
+ // start using jdbc template
public class DatabaseUtilities
{
@@ -122,7 +122,7 @@ public class DatabaseUtilities
private static Connection getHsqldbConnection(String user, WebgoatContext context) throws ClassNotFoundException,
SQLException
{
- String url = context.getDatabaseConnectionString().replaceAll("\\$\\{USER\\}", user);
+ String url = context.getDatabaseConnectionString().replace("{USER}", user);
return DriverManager.getConnection(url, "sa", "");
}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/UserDatabase.java b/webgoat-container/src/main/java/org/owasp/webgoat/session/UserDatabase.java
deleted file mode 100644
index 09e542487..000000000
--- a/webgoat-container/src/main/java/org/owasp/webgoat/session/UserDatabase.java
+++ /dev/null
@@ -1,181 +0,0 @@
-package org.owasp.webgoat.session;
-
-import java.io.File;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-class UserDatabase {
- private Connection userDB;
- private final String USER_DB_URI = "jdbc:h2:" + System.getProperty("user.dir") + File.separator + "UserDatabase";
-
- private final String CREATE_USERS_TABLE = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTO_INCREMENT, username VARCHAR(255) NOT NULL UNIQUE);";
- private final String CREATE_ROLES_TABLE = "CREATE TABLE IF NOT EXISTS roles (id INTEGER PRIMARY KEY AUTO_INCREMENT, rolename VARCHAR(255) NOT NULL UNIQUE);";
- private final String CREATE_USER_ROLES_TABLE = "CREATE TABLE IF NOT EXISTS user_roles (id INTEGER PRIMARY KEY AUTO_INCREMENT, user_id INTEGER NOT NULL, role_id INTEGER NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (role_id) REFERENCES roles(id));";
- private final String ADD_DEFAULT_USERS = "INSERT INTO users (username) VALUES ('webgoat'),('basic'),('guest');";
- private final String ADD_DEFAULT_ROLES = "INSERT INTO roles (rolename) VALUES ('webgoat_basic'),('webgoat_admin'),('webgoat_user');";
- private final String ADD_ROLE_TO_USER = "INSERT INTO user_roles (user_id, role_id) SELECT users.id, roles.id FROM users, roles WHERE users.username = ? AND roles.rolename = ?;";
-
- private final String QUERY_ALL_USERS = "SELECT username FROM users;";
- private final String QUERY_ALL_ROLES_FOR_USERNAME = "SELECT rolename FROM roles, user_roles, users WHERE roles.id = user_roles.role_id AND user_roles.user_id = users.id AND users.username = ?;";
- private final String QUERY_TABLE_COUNT = "SELECT count(id) AS count FROM table;";
-
- /**
- * Constructor for UserDatabase.
- */
- public UserDatabase() {
- createDefaultTables();
- if (getTableCount("users") <= 0) {
- createDefaultUsers();
- }
- if (getTableCount("roles") <= 0) {
- createDefaultRoles();
- }
- if (getTableCount("user_roles") <= 0) {
- addDefaultRolesToDefaultUsers();
- }
- }
-
- /**
- * open.
- *
- * @return a boolean.
- */
- public boolean open() {
- try {
- if (userDB == null || userDB.isClosed()) {
- Class.forName("org.h2.Driver");
- userDB = DriverManager.getConnection(USER_DB_URI, "webgoat_admin", "");
- }
- } catch (SQLException e) {
- e.printStackTrace();
- return false;
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- /**
- * close.
- *
- * @return a boolean.
- */
- public boolean close() {
- try {
- if (userDB != null && !userDB.isClosed())
- userDB.close();
- } catch (SQLException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- /**
- * getTableCount.
- *
- * @param tableName a {@link java.lang.String} object.
- * @return a int.
- */
- public int getTableCount(String tableName) {
- int count = 0;
- try {
- open();
- Statement statement = userDB.createStatement();
- ResultSet countResult = statement.executeQuery(QUERY_TABLE_COUNT.replace("table", tableName));
- if (countResult.next()) {
- count = countResult.getInt("count");
- }
- countResult.close();
- statement.close();
- close();
- } catch (SQLException e) {
- e.printStackTrace();
- count = -1;
- }
- return count;
- }
-
- /**
- * addRoleToUser.
- *
- * @param username a {@link java.lang.String} object.
- * @param rolename a {@link java.lang.String} object.
- * @return a boolean.
- */
- public boolean addRoleToUser(String username, String rolename) {
- try {
- open();
- PreparedStatement statement = userDB.prepareStatement(ADD_ROLE_TO_USER);
- statement.setString(1, username);
- statement.setString(2, rolename);
- statement.execute();
- statement.close();
- close();
- } catch (SQLException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- /*
- * Methods to initialise the default state of the database.
- */
-
- private boolean createDefaultTables() {
- try {
- open();
- Statement statement = userDB.createStatement();
- statement.execute(CREATE_USERS_TABLE);
- statement.execute(CREATE_ROLES_TABLE);
- statement.execute(CREATE_USER_ROLES_TABLE);
- statement.close();
- close();
- } catch (SQLException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- private boolean createDefaultUsers() {
- try {
- open();
- Statement statement = userDB.createStatement();
- statement.execute(ADD_DEFAULT_USERS);
- statement.close();
- close();
- } catch (SQLException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- private boolean createDefaultRoles() {
- try {
- open();
- Statement statement = userDB.createStatement();
- statement.execute(ADD_DEFAULT_ROLES);
- statement.close();
- close();
- } catch (SQLException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- private void addDefaultRolesToDefaultUsers() {
- addRoleToUser("webgoat", "webgoat_admin");
- addRoleToUser("basic", "webgoat_user");
- addRoleToUser("basic", "webgoat_basic");
- addRoleToUser("guest", "webgoat_user");
- }
-}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/UserTracker.java b/webgoat-container/src/main/java/org/owasp/webgoat/session/UserTracker.java
deleted file mode 100644
index a25abd00f..000000000
--- a/webgoat-container/src/main/java/org/owasp/webgoat/session/UserTracker.java
+++ /dev/null
@@ -1,154 +0,0 @@
-
-package org.owasp.webgoat.session;
-
-import com.google.common.collect.Maps;
-import com.google.common.io.ByteStreams;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.owasp.webgoat.lessons.AbstractLesson;
-import org.owasp.webgoat.lessons.Assignment;
-import org.springframework.core.serializer.DefaultDeserializer;
-
-import java.io.*;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-
-/**
- * ************************************************************************************************
- *
- *
- * 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.
- *
- * @author Bruce Mayhew WebGoat
- * @version $Id: $Id
- * @since October 29, 2003
- */
-@Slf4j
-public class UserTracker {
-
- private final String webgoatHome;
- private final String user;
-
- public UserTracker(final String webgoatHome, final String user) {
- this.webgoatHome = webgoatHome;
- this.user = user;
- }
-
- /**
- * Returns the lesson tracker for a specific lesson if available.
- *
- * @param lesson the lesson
- * @return the optional lesson tracker
- */
- public LessonTracker getLessonTracker(AbstractLesson lesson) {
- return getLessonTracker(load(), lesson);
- }
-
- /**
- * Returns the lesson tracker for a specific lesson if available.
- *
- * @param lesson the lesson
- * @return the optional lesson tracker
- */
- public LessonTracker getLessonTracker(Map storage, AbstractLesson lesson) {
- LessonTracker lessonTracker = storage.get(lesson.getTitle());
- if (lessonTracker == null) {
- lessonTracker = new LessonTracker(lesson);
- storage.put(lesson.getTitle(), lessonTracker);
- save(storage);
- }
- return lessonTracker;
- }
-
- public void assignmentSolved(AbstractLesson lesson, String assignmentName) {
- Map storage = load();
- LessonTracker lessonTracker = storage.get(lesson.getTitle());
- lessonTracker.incrementAttempts();
- lessonTracker.assignmentSolved(assignmentName);
- save(storage);
- }
-
- public void assignmentFailed(AbstractLesson lesson) {
- Map storage = load();
- LessonTracker lessonTracker = storage.get(lesson.getTitle());
- lessonTracker.incrementAttempts();
- save(storage);
- }
-
- public Map load() {
- File file = new File(webgoatHome, user + ".progress");
- Map storage = Maps.newHashMap();
- if (file.exists() && file.isFile()) {
- try {
- DefaultDeserializer deserializer = new DefaultDeserializer(Thread.currentThread().getContextClassLoader());
- try (FileInputStream fis = new FileInputStream(file)) {
- byte[] b = ByteStreams.toByteArray(fis);
- storage = (Map) deserializer.deserialize(new ByteArrayInputStream(b));
- }
- } catch (Exception e) {
- log.error("Unable to read the progress file, creating a new one...");
- }
- }
- return storage;
- }
-
- @SneakyThrows
- private void save(Map storage) {
- File file = new File(webgoatHome, user + ".progress");
-
- try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file))) {
- objectOutputStream.writeObject(storage);
- objectOutputStream.flush();
- }
- }
-
-
- public void reset(AbstractLesson al) {
- Map storage = load();
- LessonTracker lessonTracker = getLessonTracker(storage, al);
- lessonTracker.reset();
- save(storage);
- }
-
- public int numberOfLessonsSolved() {
- int numberOfLessonsSolved = 0;
- Map storage = load();
- for (LessonTracker lessonTracker : storage.values()) {
- if (lessonTracker.isLessonSolved()) {
- numberOfLessonsSolved = numberOfLessonsSolved + 1;
- }
- }
- return numberOfLessonsSolved;
- }
-
- public int numberOfAssignmentsSolved() {
- int numberOfAssignmentsSolved = 0;
- Map storage = load();
- for (LessonTracker lessonTracker : storage.values()) {
- Map lessonOverview = lessonTracker.getLessonOverview();
- numberOfAssignmentsSolved = lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue();
- }
- return numberOfAssignmentsSolved;
- }
-}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/WebSession.java b/webgoat-container/src/main/java/org/owasp/webgoat/session/WebSession.java
index bc12af039..33196575a 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/session/WebSession.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/session/WebSession.java
@@ -2,6 +2,7 @@ package org.owasp.webgoat.session;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson;
+import org.owasp.webgoat.users.WebGoatUser;
import org.springframework.security.core.context.SecurityContextHolder;
import java.sql.Connection;
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/LessonTracker.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/LessonTracker.java
similarity index 93%
rename from webgoat-container/src/main/java/org/owasp/webgoat/session/LessonTracker.java
rename to webgoat-container/src/main/java/org/owasp/webgoat/users/LessonTracker.java
index edd0d1483..bff30316e 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/session/LessonTracker.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/LessonTracker.java
@@ -1,5 +1,5 @@
-package org.owasp.webgoat.session;
+package org.owasp.webgoat.users;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -7,7 +7,6 @@ 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;
@@ -45,14 +44,20 @@ import java.util.stream.Collectors;
* @version $Id: $Id
* @since October 29, 2003
*/
-public class LessonTracker implements Serializable {
- private static final long serialVersionUID = 5410058267505412928L;
+public class LessonTracker {
+ @Getter
+ private String lessonName;
private final Set solvedAssignments = Sets.newHashSet();
private final List allAssignments = Lists.newArrayList();
@Getter
private int numberOfAttempts = 0;
+ protected LessonTracker() {
+ //Mongo
+ }
+
public LessonTracker(AbstractLesson lesson) {
+ lessonName = lesson.getId();
allAssignments.addAll(lesson.getAssignments());
}
@@ -61,7 +66,7 @@ public class LessonTracker implements Serializable {
}
/**
- * Mark an assingment as solved
+ * Mark an assignment as solved
*
* @param solvedAssignment the assignment which the user solved
*/
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java
index f4bf8fe61..5c7a4fff3 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java
@@ -2,7 +2,6 @@ package org.owasp.webgoat.users;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.owasp.webgoat.session.WebGoatUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/Scoreboard.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/Scoreboard.java
new file mode 100644
index 000000000..1b08e35bc
--- /dev/null
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/Scoreboard.java
@@ -0,0 +1,61 @@
+package org.owasp.webgoat.users;
+
+import com.google.common.collect.Lists;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.owasp.webgoat.i18n.PluginMessages;
+import org.owasp.webgoat.session.Course;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Temp endpoint just for the CTF.
+ *
+ * @author nbaars
+ * @since 3/23/17.
+ */
+@RestController
+@AllArgsConstructor
+public class Scoreboard {
+
+ private final UserTrackerRepository userTrackerRepository;
+ private final UserRepository userRepository;
+ private final Course course;
+ private final PluginMessages pluginMessages;
+
+ @AllArgsConstructor
+ @Getter
+ private class Ranking {
+ private String username;
+ private List flagsCaptured;
+ }
+
+ @GetMapping("/scoreboard-data")
+ public List getRankings() {
+ List allUsers = userRepository.findAll();
+ List rankings = Lists.newArrayList();
+ for (WebGoatUser user : allUsers) {
+ UserTracker userTracker = userTrackerRepository.findOne(user.getUsername());
+ rankings.add(new Ranking(user.getUsername(), challengesSolved(userTracker)));
+ }
+ return rankings;
+ }
+
+ private List challengesSolved(UserTracker userTracker) {
+ List challenges = Lists.newArrayList("Challenge1", "Challenge2", "Challenge3", "Challenge4", "Challenge5");
+ return challenges.stream()
+ .map(c -> userTracker.getLessonTracker(c))
+ .filter(l -> l.isPresent()).map(l -> l.get())
+ .map(l -> l.getLessonName())
+ .map(l -> toLessonTitle(l))
+ .collect(Collectors.toList());
+ }
+
+ private String toLessonTitle(String id) {
+ String titleKey = course.getLessons().stream().filter(l -> l.getId().equals(id)).findFirst().get().getTitle();
+ return pluginMessages.getMessage(titleKey, titleKey);
+ }
+}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserRepository.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserRepository.java
index d7d85db54..ae2f1063e 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserRepository.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserRepository.java
@@ -1,13 +1,12 @@
package org.owasp.webgoat.users;
-import org.owasp.webgoat.session.WebGoatUser;
-import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author nbaars
* @since 3/19/17.
*/
-public interface UserRepository extends CrudRepository {
+public interface UserRepository extends MongoRepository {
WebGoatUser findByUsername(String username);
}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserService.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserService.java
index 15a6cf6d4..7a1175c45 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserService.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserService.java
@@ -1,7 +1,6 @@
package org.owasp.webgoat.users;
import lombok.AllArgsConstructor;
-import org.owasp.webgoat.session.WebGoatUser;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@@ -15,6 +14,7 @@ import org.springframework.stereotype.Service;
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
+ private final UserTrackerRepository userTrackerRepository;
@Override
public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException {
@@ -29,5 +29,6 @@ public class UserService implements UserDetailsService {
public void addUser(String username, String password) {
userRepository.save(new WebGoatUser(username, password));
+ userTrackerRepository.save(new UserTracker(username));
}
}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTracker.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTracker.java
new file mode 100644
index 000000000..c139d2571
--- /dev/null
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTracker.java
@@ -0,0 +1,119 @@
+
+package org.owasp.webgoat.users;
+
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.owasp.webgoat.lessons.AbstractLesson;
+import org.owasp.webgoat.lessons.Assignment;
+import org.springframework.data.annotation.Id;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+
+/**
+ * ************************************************************************************************
+ *
+ *
+ * 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.
+ *
+ * @author Bruce Mayhew WebGoat
+ * @version $Id: $Id
+ * @since October 29, 2003
+ */
+@Slf4j
+public class UserTracker {
+
+ @Id
+ private final String user;
+ private List lessonTrackers = Lists.newArrayList();
+
+ public UserTracker(final String user) {
+ this.user = user;
+ }
+
+ /**
+ * Returns an existing lesson tracker or create a new one based on the lesson
+ *
+ * @param lesson the lesson
+ * @return a lesson tracker created if not already present
+ */
+ public LessonTracker getLessonTracker(AbstractLesson lesson) {
+ Optional lessonTracker = lessonTrackers
+ .stream().filter(l -> l.getLessonName().equals(lesson.getId())).findFirst();
+ if (!lessonTracker.isPresent()) {
+ LessonTracker newLessonTracker = new LessonTracker(lesson);
+ lessonTrackers.add(newLessonTracker);
+ return newLessonTracker;
+ } else {
+ return lessonTracker.get();
+ }
+ }
+
+ /**
+ * Query method for finding a specific lesson tracker based on id
+ *
+ * @param id the id of the lesson
+ * @return optional due to the fact we can only create a lesson tracker based on a lesson
+ */
+ public Optional getLessonTracker(String id) {
+ return lessonTrackers.stream().filter(l -> l.getLessonName().equals(id)).findFirst();
+ }
+
+ public void assignmentSolved(AbstractLesson lesson, String assignmentName) {
+ LessonTracker lessonTracker = getLessonTracker(lesson);
+ lessonTracker.incrementAttempts();
+ lessonTracker.assignmentSolved(assignmentName);
+ }
+
+ public void assignmentFailed(AbstractLesson lesson) {
+ LessonTracker lessonTracker = getLessonTracker(lesson);
+ lessonTracker.incrementAttempts();
+ }
+
+ public void reset(AbstractLesson al) {
+ LessonTracker lessonTracker = getLessonTracker(al);
+ lessonTracker.reset();
+ }
+
+ public int numberOfLessonsSolved() {
+ int numberOfLessonsSolved = 0;
+ for (LessonTracker lessonTracker : lessonTrackers) {
+ if (lessonTracker.isLessonSolved()) {
+ numberOfLessonsSolved = numberOfLessonsSolved + 1;
+ }
+ }
+ return numberOfLessonsSolved;
+ }
+
+ public int numberOfAssignmentsSolved() {
+ int numberOfAssignmentsSolved = 0;
+ for (LessonTracker lessonTracker : lessonTrackers) {
+ Map lessonOverview = lessonTracker.getLessonOverview();
+ numberOfAssignmentsSolved = lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue();
+ }
+ return numberOfAssignmentsSolved;
+ }
+}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTrackerRepository.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTrackerRepository.java
new file mode 100644
index 000000000..f915154cb
--- /dev/null
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTrackerRepository.java
@@ -0,0 +1,12 @@
+package org.owasp.webgoat.users;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+/**
+ * @author nbaars
+ * @since 4/30/17.
+ */
+public interface UserTrackerRepository extends MongoRepository {
+
+
+}
diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/session/WebGoatUser.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/WebGoatUser.java
similarity index 91%
rename from webgoat-container/src/main/java/org/owasp/webgoat/session/WebGoatUser.java
rename to webgoat-container/src/main/java/org/owasp/webgoat/users/WebGoatUser.java
index 381617386..8b3c7c88c 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/session/WebGoatUser.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/WebGoatUser.java
@@ -1,14 +1,13 @@
-package org.owasp.webgoat.session;
+package org.owasp.webgoat.users;
import lombok.Getter;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Transient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.Transient;
import java.util.Collection;
import java.util.Collections;
@@ -17,7 +16,6 @@ import java.util.Collections;
* @since 3/19/17.
*/
@Getter
-@Entity
public class WebGoatUser implements UserDetails {
public static final String ROLE_USER = "WEBGOAT_USER";
diff --git a/webgoat-container/src/main/resources/application.properties b/webgoat-container/src/main/resources/application.properties
index 9d6ad7e56..4119f9887 100644
--- a/webgoat-container/src/main/resources/application.properties
+++ b/webgoat-container/src/main/resources/application.properties
@@ -16,6 +16,7 @@ security.enable-csrf=false
spring.resources.cache-period=0
spring.thymeleaf.cache=false
+webgoat.clean=true
webgoat.server.directory=${user.home}/.webgoat/
webgoat.user.directory=${user.home}/.webgoat/
webgoat.build.version=@project.version@
@@ -25,18 +26,11 @@ webgoat.emaillist=owasp-webgoat@lists.owasp.org
webgoat.feedback.address=webgoat@owasp.org
webgoat.feedback.address.html=webgoat@owasp.org
webgoat.database.driver=org.hsqldb.jdbcDriver
-webgoat.database.connection.string=jdbc:hsqldb:mem:test
-# TODO_NB
-#webgoat.database.connection.string=jdbc:hsqldb:mem:${USER}
+webgoat.database.connection.string=jdbc:hsqldb:mem:{USER}
webgoat.default.language=en
+spring.data.mongodb.database=webgoat
+spring.mongodb.embedded.storage.databaseDir=${webgoat.user.directory}/mongodb/
-liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
-spring.datasource.url=jdbc:hsqldb:file:${user.home}/.webgoat/WebGoatDatabase;hsqldb.write_delay=false
-spring.datasource.driverClassName=org.hsqldb.jdbcDriver
-spring.datasource.username=sa
-spring.datasource.password=
-spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect
-spring.jpa.show-sql=true
-spring.jpa.hibernate.ddl-auto=none
-
+#For static file refresh ... and faster dev :D
+spring.devtools.restart.additional-paths=webgoat-container/src/main/resources/static/js,webgoat-container/src/main/resources/static/css
diff --git a/webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml b/webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml
index f3b67826b..e2f25133e 100644
--- a/webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml
@@ -6,9 +6,7 @@
-
-
-
+
diff --git a/webgoat-container/src/main/resources/static/css/img/appseceu-17.png b/webgoat-container/src/main/resources/static/css/img/appseceu-17.png
new file mode 100644
index 000000000..4cab365f0
Binary files /dev/null and b/webgoat-container/src/main/resources/static/css/img/appseceu-17.png differ
diff --git a/webgoat-container/src/main/resources/static/css/main.css b/webgoat-container/src/main/resources/static/css/main.css
index f733b3d14..93edc7081 100644
--- a/webgoat-container/src/main/resources/static/css/main.css
+++ b/webgoat-container/src/main/resources/static/css/main.css
@@ -152,7 +152,7 @@ img {
margin-left: 1.5em;*/
margin-right: 5px;
margin-top: -38px; /* << don't like doing this, but otherwise it does not line up correctly */
- color:#0F0
+ color:#88FB88 /* #0F0 */
}
/* ==========================================================================
@@ -958,8 +958,52 @@ cookie-container {
cursor:pointer;
}
+.page-nav-wrapper {
+ display:inline-block;
+ width: 30px;
+}
+
+.attack-link, .page-link {
+ display: inline-block;
+ background-color: #555;
+ border-radius: 8px;
+ min-width: 20px;
+ text-align: center;
+ font-weight: bold;
+ padding-top:2px;
+}
+
+.attack-link.solved-true {
+ color:#88FB88;
+}
+
+.attack-link.solved-false {
+ color:#f2baba;
+}
+
+.attack-link.cur-page, .page-link.cur-page {
+ color:#fff;
+}
+
+.page-link {
+ color:#eee;
+}
+
+.page-link-wrapper {
+ display:inline-block;
+}
+
+.page-link-wrapper span {
+ margin: 3px;
+}
+
+.cur-page {
+ border-bottom: 2px solid #000;
+ color:#aaa;
+}
+
span.show-next-page, span.show-prev-page {
-font-size: 1.3em;
+ font-size: 1.3em;
}
.show-prev-page {
@@ -970,6 +1014,8 @@ font-size: 1.3em;
cursor:pointer;
}
+/* attack ... */
+
.attack-feedback {
font-weight:800;
}
@@ -984,10 +1030,11 @@ font-size: 1.3em;
}
#lesson-hint {
- background-color: #ccc;
+ background-color: #f1f1f1;
border-radius: 4px;
- border-color: #999;
- margin-top:4px;
+ border-color: #4fa44c;
+ margin-top: 4px;
+ border: 2px solid #24b054;
}
#hintsViewTop{
@@ -1043,6 +1090,60 @@ font-size: 1.3em;
padding: 10px;
}
+/* temp override
+//TODO: come up with longer term solution for full-window viewing
+*/
.col-md-8 {
- width: 95% !important;
+ width: 95% !important
+}
+
+/* scoreboard */
+div.scoreboard-title {
+ font-size:xx-large;
+}
+
+.scoreboard-table tr {
+}
+
+div.scoreboard-username {
+ background-color: #222;
+ color: aliceblue;
+ padding: 4px;
+ padding-left:8px;
+ font-size: x-large;
+ border-radius:6px;
+}
+
+th.username {
+ padding-bottom: 6px;
+}
+
+td.user-flags {
+ padding-left: 8px;
+ padding-bottom: 6px;
+}
+
+div.captured-flag {
+ border-radius: 6px;
+ background-color: #444;
+ color: white;
+ padding: 4px;
+ font-size: x-large;
+ display: inline-block;
+}
+
+.scoreboard-page {
+ background-color: #e0dfdc;
+ padding: 20px;
+}
+
+.fa-flag {
+ color:red
+}
+
+.appseceu-banner {
+ background: url('img/appseceu-17.png') no-repeat 0px 0px;
+ height: 117px;
+ width: 1268px;
+ margin-bottom: 20px;
}
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js b/webgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js
index a4b93ff18..07a64c502 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js
@@ -3,12 +3,11 @@ define(['jquery',
'libs/backbone',
'goatApp/model/LessonContentModel',
'goatApp/view/LessonContentView',
- 'goatApp/view/PlanView',
- 'goatApp/view/SourceView',
- 'goatApp/view/SolutionView',
+// 'goatApp/view/PlanView',
+// 'goatApp/view/SourceView',
+// 'goatApp/view/SolutionView',
'goatApp/view/HintView',
'goatApp/view/HelpControlsView',
- 'goatApp/view/CookieView',
'goatApp/view/ParamView',
'goatApp/model/ParamModel',
'goatApp/view/DeveloperControlsView',
@@ -27,12 +26,11 @@ define(['jquery',
Backbone,
LessonContentModel,
LessonContentView,
- PlanView,
- SourceView,
- SolutionView,
+// PlanView,
+// SourceView,
+// SolutionView,
HintView,
HelpControlsView,
- CookieView,
ParamView,
ParamModel,
DeveloperControlsView,
@@ -66,13 +64,29 @@ define(['jquery',
this.menuButtonView = new MenuButtonView();
this.listenTo(this.lessonContentView, 'assignment:complete', this.updateMenu);
this.listenTo(this.lessonContentView, 'assignment:complete', this.updateLessonOverview);
+ this.listenTo(this.lessonContentView, 'endpoints:filtered', this.filterPageHints);
};
- this.loadLesson = function(name,pageNum) {
+ this.filterPageHints = function(endpoints) {
+ //filter hints for page by
+ this.lessonHintView.filterHints(endpoints);
+ }
+ this.onHideHintsButton = function() {
+ this.helpControlsView.hideHintsButton();
+ }
+
+ this.onShowHintsButton = function() {
+ this.helpControlsView.showHintsButton();
+ }
+
+ this.loadLesson = function(name,pageNum) {
if (this.name === name) {
+ this.listenTo(this.lessonHintView, 'hints:showButton', this.onShowHintsButton);
+ this.listenTo(this.lessonHintView, 'hints:hideButton', this.onHideHintsButton);
this.lessonContentView.navToPage(pageNum);
this.lessonHintView.hideHints();
+ //this.lessonHintView.selectHints();
this.titleView.render(this.lessonInfoModel.get('lessonTitle'));
return;
}
@@ -82,10 +96,10 @@ define(['jquery',
//TODO: implement lesson not found or return to welcome page?
}
this.lessonContent.loadData({'name':name});
- this.planView = {};
- this.solutionView = {};
- this.sourceView = {};
- this.lessonHintView = {};
+// this.planView = {};
+// this.solutionView = {};
+// this.sourceView = {};
+// this.lessonHintView = {};
this.name = name;
};
@@ -98,15 +112,13 @@ define(['jquery',
this.listenTo(this.helpControlsView,'hints:show',this.showHints);
this.listenTo(this.helpControlsView,'lessonOverview:show',this.showLessonOverview)
- this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps);
- this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps);
+
this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson);
this.listenTo(this.developerControlsView, 'dev:labels', this.restartLesson);
this.helpControlsView.render();
this.lessonOverview.hideLessonOverview();
this.titleView.render(this.lessonInfoModel.get('lessonTitle'));
- this.helpControlsView.showHideHintsButton({});
};
this.updateMenu = function() {
@@ -126,11 +138,14 @@ define(['jquery',
this.lessonContentView.model = this.lessonContent;
this.lessonContentView.render();
- this.planView = new PlanView();
- this.solutionView = new SolutionView();
- this.sourceView = new SourceView();
+ //this.planView = new PlanView();
+ //this.solutionView = new SolutionView();
+ //this.sourceView = new SourceView();
+ if (this.lessonHintView) {
+ this.lessonHintView.stopListening();
+ this.lessonHintView = null;
+ }
this.lessonHintView = new HintView();
- this.cookieView = new CookieView();
//TODO: instantiate model with values (not sure why was not working before)
var paramModel = new ParamModel({});
@@ -150,34 +165,34 @@ define(['jquery',
this.helpsLoaded[curHelp.helpElement] = curHelp.value;
};
- this.hideShowHelps = function(showHelp) {
- var showId = '#lesson-' + showHelp + '-row';
- var contentId = '#lesson-' + showHelp + '-content';
- $('.lesson-help').not(showId).hide();
- if (!showId) {
- return;
- }
-
- if ($(showId).is(':visible')) {
- $(showId).hide();
- return;
- } else {
- //TODO: move individual .html operations into individual help views
- switch(showHelp) {
- case 'plan':
- $(contentId).html(this.planView.model.get('content'));
- break;
- case 'solution':
- $(showId).html(this.solutionView.model.get('content'));
- break;
- case 'source':
- $(contentId).html('' + this.sourceView.model.get('content') + ' ');
- break;
- }
- $(showId).show();
- GoatUtils.scrollToHelp()
- }
- };
+// this.hideShowHelps = function(showHelp) {
+// var showId = '#lesson-' + showHelp + '-row';
+// var contentId = '#lesson-' + showHelp + '-content';
+// $('.lesson-help').not(showId).hide();
+// if (!showId) {
+// return;
+// }
+//
+// if ($(showId).is(':visible')) {
+// $(showId).hide();
+// return;
+// } else {
+// //TODO: move individual .html operations into individual help views
+// switch(showHelp) {
+// case 'plan':
+// $(contentId).html(this.planView.model.get('content'));
+// break;
+// case 'solution':
+// $(showId).html(this.solutionView.model.get('content'));
+// break;
+// case 'source':
+// $(contentId).html('' + this.sourceView.model.get('content') + ' ');
+// break;
+// }
+// $(showId).show();
+// GoatUtils.scrollToHelp()
+// }
+// };
this.showHints = function() {
this.lessonHintView.render();
@@ -194,6 +209,7 @@ define(['jquery',
method:'GET'
}).done(function(lessonLink) {
self.loadLesson(self.name);
+ self.updateMenu();
});
};
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/model/CookieModel.js b/webgoat-container/src/main/resources/static/js/goatApp/model/FlagModel.js
similarity index 100%
rename from webgoat-container/src/main/resources/static/js/goatApp/model/CookieModel.js
rename to webgoat-container/src/main/resources/static/js/goatApp/model/FlagModel.js
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/model/CookieCollection.js b/webgoat-container/src/main/resources/static/js/goatApp/model/FlagsCollection.js
similarity index 55%
rename from webgoat-container/src/main/resources/static/js/goatApp/model/CookieCollection.js
rename to webgoat-container/src/main/resources/static/js/goatApp/model/FlagsCollection.js
index ae3ed023a..8832310a3 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/model/CookieCollection.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/model/FlagsCollection.js
@@ -1,13 +1,13 @@
define(['jquery',
'underscore',
'backbone',
- 'goatApp/model/CookieModel'],
+ 'goatApp/model/FlagModel'],
function($,
_,
Backbone,
- CookieModel) {
+ FlagModel) {
return Backbone.Collection.extend({
- url:'service/cookie.mvc',
- model:CookieModel
+ url:'/WebGoat/scoreboard-data',
+ model:FlagModel
});
});
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/model/LessonContentModel.js b/webgoat-container/src/main/resources/static/js/goatApp/model/LessonContentModel.js
index f036f22e6..f26aad564 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/model/LessonContentModel.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/model/LessonContentModel.js
@@ -20,7 +20,6 @@ define(['jquery',
loadData: function(options) {
this.urlRoot = _.escape(encodeURIComponent(options.name)) + '.lesson'
-
var self = this;
this.fetch().done(function(data) {
self.setContent(data);
@@ -32,7 +31,8 @@ define(['jquery',
loadHelps = true;
}
this.set('content',content);
- this.set('lessonUrl',document.URL);
+ this.set('lessonUrl',document.URL.replace(/\.lesson.*/,'.lesson'));
+ this.set('pageNum',document.URL.replace(/.*\.lesson\/(\d{1,4})$/,'$1'));
this.trigger('content:loaded',this,loadHelps);
},
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/model/LessonOverviewModel.js b/webgoat-container/src/main/resources/static/js/goatApp/model/LessonOverviewModel.js
index 28647bb89..17c906bd5 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/model/LessonOverviewModel.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/model/LessonOverviewModel.js
@@ -3,7 +3,7 @@ define([
function(
Backbone) {
return Backbone.Collection.extend({
- tagName: 'ul',
+ //tagName: 'ul',
url: 'service/lessonoverview.mvc'
});
});
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/scoreboardApp.js b/webgoat-container/src/main/resources/static/js/goatApp/scoreboardApp.js
new file mode 100644
index 000000000..d47bd0b60
--- /dev/null
+++ b/webgoat-container/src/main/resources/static/js/goatApp/scoreboardApp.js
@@ -0,0 +1,17 @@
+define(['jquery',
+ 'underscore',
+ 'backbone',
+ 'goatApp/support/goatAsyncErrorHandler',
+ 'goatApp/view/ScoreboardView'],
+ function ($,
+ _,
+ Backbone,
+ asyncErrorHandler,
+ ScoreboardView) {
+ 'use strict'
+ return {
+ initApp: function () {
+ scoreboard = new ScoreboardView();
+ }
+ };
+ });
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/templates/paging_controls.html b/webgoat-container/src/main/resources/static/js/goatApp/templates/paging_controls.html
new file mode 100644
index 000000000..141c3f5af
--- /dev/null
+++ b/webgoat-container/src/main/resources/static/js/goatApp/templates/paging_controls.html
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/templates/scoreboard.html b/webgoat-container/src/main/resources/static/js/goatApp/templates/scoreboard.html
new file mode 100644
index 000000000..cae628c71
--- /dev/null
+++ b/webgoat-container/src/main/resources/static/js/goatApp/templates/scoreboard.html
@@ -0,0 +1,16 @@
+WebGoat Challenge - AppSec EU 2017
+banner here
+
+ <% _.each(rankings, function(userRanking, index) { %>
+
+ <%= index%> <%=userRanking.username %>
+ <% _.each(userRanking.flagsCaptured, function(flag) { %>
+
+
+
+ <%=flag%>
+ <% }); %>
+
+
+ <% }); %>
+
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/CookieView.js b/webgoat-container/src/main/resources/static/js/goatApp/view/CookieView.js
deleted file mode 100644
index 106b6f952..000000000
--- a/webgoat-container/src/main/resources/static/js/goatApp/view/CookieView.js
+++ /dev/null
@@ -1,34 +0,0 @@
-define(['jquery',
- 'underscore',
- 'backbone',
- 'goatApp/model/CookieCollection'],
-function($,
- _,
- Backbone,
- CookieCollection) {
- return Backbone.View.extend({
- el:'#cookies-view',
-
- initialize: function() {
- this.collection = new CookieCollection();
- this.listenTo(this.collection,'reset',this.render)
- this.collection.fetch({reset:true});
- },
-
- render: function() {
- this.$el.html('')
- var cookieTable;
- this.collection.each(function(model) {
- cookieTable = $('',{'class':'cookie-table table-striped table-nonfluid'});
- _.each(model.keys(), function(attribute) {
- var newRow = $('');
- newRow.append($('',{text:_.escape(attribute)}))
- newRow.append($(' ',{text:_.escape(model.get(attribute))}));
- cookieTable.append(newRow);
- });
- });
- this.$el.append($('',{text:'Cookie/s'}));
- this.$el.append(cookieTable);
- }
- });
-});
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/HelpControlsView.js b/webgoat-container/src/main/resources/static/js/goatApp/view/HelpControlsView.js
index 26f20f345..c317ba0f3 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/view/HelpControlsView.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/view/HelpControlsView.js
@@ -12,18 +12,14 @@ function($,_,Backbone) {
this.hasPlan = options.hasPlan;
this.hasSolution = options.hasSolution;
this.hasSource = options.hasSource;
- var self = this;
- Backbone.on('navigatedToPage', function(nav) {
- self.showHideHintsButton(nav)
- });
},
- showHideHintsButton: function(nav) {
- if (typeof nav['assignmentPath'] !== 'undefined') {
- this.$el.find('#show-hints-button').unbind().on('click',this.showHints.bind(this)).show();
- } else {
- $('#show-hints-button').hide();
- }
+ showHintsButton: function(nav) {
+ this.$el.find('#show-hints-button').unbind().on('click',this.showHints.bind(this)).show();
+ },
+
+ hideHintsButton: function(){
+ $('#show-hints-button').hide();
},
render:function(title) {
@@ -38,7 +34,7 @@ function($,_,Backbone) {
this.$el.find('#show-solution-button').unbind().on('click',_.bind(this.showSolution,this)).show();
}
- this.$el.find('#show-lesson-overview-button').unbind().on('click', _.bind(this.showLessonOverview, this)).show();
+ //this.$el.find('#show-lesson-overview-button').unbind().on('click', _.bind(this.showLessonOverview, this)).show();
this.$el.find('#restart-lesson-button').unbind().on('click',_.bind(this.restartLesson,this)).show();
},
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/HintView.js b/webgoat-container/src/main/resources/static/js/goatApp/view/HintView.js
index 042b17c54..85508c432 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/view/HintView.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/view/HintView.js
@@ -19,8 +19,10 @@ function($,
this.listenTo(this.collection,'loaded',this.onModelLoaded);
this.hideHints();
var self = this;
+ // different way to do this?
Backbone.on('navigatedToPage', function(nav){
- self.selectHints(nav)
+ self.selectHints(nav);
+ // end event delegation??
});
},
@@ -61,14 +63,25 @@ function($,
*
* @param nav the json structure for navigating
*/
- selectHints: function(nav) {
- this.curHint = 0;
- var assignmentPath = nav['assignmentPath'];
- if (assignmentPath != null) {
- this.hintsToShow = this.collection.getHintsForAssignment(assignmentPath);
+ filterHints: function(endpoints) {
+ this.hintsToShow = [];
+ _.each(endpoints, this.filterHint, this);
+
+ if (this.hintsToShow.length > 0) {
+ this.trigger('hints:showButton');
} else {
- this.hintsToShow = new Array();
- }
+ this.trigger('hints:hideButton');
+ }
+ },
+
+ filterHint: function(endpoint) {
+ var self = this;
+ _.each(this.collection.models, function(hintModel) {
+ if (endpoint.indexOf(hintModel.get('assignmentPath')) > -1) {
+ self.hintsToShow.push(hintModel.get('hint'));
+ }
+ });
+ console.log(this.hintsToShow);
},
onModelLoaded: function() {
@@ -97,7 +110,7 @@ function($,
if(this.hintsToShow.length == 0) {
// this.hideHints();
} else {
- this.$el.find('#lesson-hint-content').html(polyglot.t(this.hintsToShow[curHint].get('hint')));
+ this.$el.find('#lesson-hint-content').html(polyglot.t(this.hintsToShow[curHint]));
}
},
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js b/webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js
index 7f478dab2..beb0f6fa6 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js
@@ -3,13 +3,15 @@ define(['jquery',
'underscore',
'backbone',
'libs/jquery.form',
- 'goatApp/view/ErrorNotificationView'],
+ 'goatApp/view/ErrorNotificationView',
+ 'goatApp/view/PaginationControlView'],
function(
$,
_,
Backbone,
JQueryForm,
- ErrorNotificationView) {
+ ErrorNotificationView,
+ PaginationControlView) {
return Backbone.View.extend({
el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM
@@ -37,7 +39,7 @@ define(['jquery',
return -1;
},
- /* initial renering */
+ /* initial rendering */
render: function() {
this.$el.find('.lesson-content').html(this.model.get('content'));
this.$el.find('.attack-feedback').hide();
@@ -45,31 +47,17 @@ define(['jquery',
this.makeFormsAjax();
//this.ajaxifyAttackHref();
$(window).scrollTop(0); //work-around til we get the scroll down sorted out
- this.initPagination();
+ var startPageNum = this.model.get('pageNum');
+ this.initPagination(startPageNum);
},
- initPagination: function() {
+ initPagination: function(startPageNum) {
//get basic pagination info
- this.currentPage = 0;
this.$contentPages = this.$el.find('.lesson-page-wrapper');
- this.numPages = this.$contentPages.length;
-
- //
- this.addPaginationControls();
- if (this.numPages > 1) {
- //no animation on init
- this.$contentPages.hide();
- this.$el.find(this.$contentPages[this.currentPage]).show();
- this.showNextPageButton();
- this.hidePrevPageButton();
- } else if (this.numPages === 1) {
- this.hideNextPageButton();
- this.hidePrevPageButton();
- }
- },
-
- setCurrentPage: function (pageNum) {
- this.currentPage = (_.isNumber(pageNum) && pageNum < this.numPages) ? pageNum : 0;
+ var currentPage = (!isNaN(startPageNum) && startPageNum && startPageNum < this.$contentPages) ? startPageNum : 0;
+ //init views & pagination
+ this.showCurContentPage(currentPage);
+ this.paginationControlView = new PaginationControlView(this.$contentPages,this.model.get('lessonUrl'));
},
getCurrentPage: function () {
@@ -160,128 +148,29 @@ define(['jquery',
this.$curOutput.show(400)
},
- /* create, show & hide pagination controls */
-
- addPaginationControls: function() {
- var pagingControlsDiv;
- //this.$el.html();
- //prev
- var prevPageButton = $('',{class:'glyphicon-class glyphicon glyphicon-circle-arrow-left show-prev-page'});
- prevPageButton.unbind().on('click',this.decrementPageView.bind(this));
- //next
- var nextPageButton = $('',{class:'glyphicon-class glyphicon glyphicon-circle-arrow-right show-next-page'});
- nextPageButton.unbind().on('click',this.incrementPageView.bind(this));
- //add to DOM
- if (this.$el.find('#lesson-page-controls').length < 1) {
- pagingControlsDiv = $('',{class:'panel-body', id:'lesson-page-controls'});
- pagingControlsDiv.append(prevPageButton);
- pagingControlsDiv.append(nextPageButton);
- this.$el.find('.lesson-page-controls').append(pagingControlsDiv);
- }
-
- },
-
- showPrevPageButton: function() {
- $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').show();
- },
-
- hidePrevPageButton: function() {
- $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').hide();
- },
-
- showNextPageButton: function() {
- $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').show();
- },
-
- hideNextPageButton: function() {
- $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').hide();
- },
-
- /* increment, decrement & display handlers */
- incrementPageView: function() {
- if (this.currentPage < this.numPages -1) {
- this.currentPage++;
- window.location.href = this.model.get('lessonUrl') + '/' + this.currentPage;
- //this.showCurContentPage(true);Con
- }
-
- if (this.currentPage > 0) {
- this.showPrevPageButton();
- }
-
- if (this.currentPage >= this.numPages -1) {
- this.hideNextPageButton();
- this.showPrevPageButton;
- }
- },
-
- decrementPageView: function() {
- if (this.currentPage > 0) {
- this.currentPage--;
- window.location.href = this.model.get('lessonUrl') + '/' + this.currentPage;
- //this.showCurContentPage(false);
- }
-
- if (this.currentPage < this.numPages -1) {
- this.showNextPageButton();
- }
-
- if (this.currentPage == 0) {
- this.hidePrevPageButton();
- this.showNextPageButton()
- }
-
- },
-
- showCurContentPage: function(isIncrement) {
+ showCurContentPage: function(pageNum) {
this.$contentPages.hide();
- this.$el.find(this.$contentPages[this.currentPage]).show();
+ this.$el.find(this.$contentPages[pageNum]).show();
},
- findAssigmentEndpointOnPage: function(pageNumber) {
- var contentPage = this.$contentPages[this.currentPage];
- var form = $('form.attack-form', contentPage);
- var action = form.attr('action')
- if (action !== undefined) {
- return action;
+ findAssigmentEndpointsOnPage: function(pageNumber) {
+ var contentPage = this.$contentPages[pageNumber];
+ var endpoints = []; //going to assume uniqueness since these are assignments
+ var pageForms = $(contentPage).find('form.attack-form');
+ for (var i=0; i
1) {
- this.showNextPageButton();
- }
- return;
- }
- // > first page, but not last
- if (this.currentPage > 0 && this.currentPage < this.numPages -1) {
- this.showNextPageButton();
- this.showPrevPageButton();
- return;
- }
- // last page and more than one page
- if (this.currentPage === this.numPages -1 && this.numPages > 1) {
- this.hideNextPageButton();
- this.showPrevPageButton();
- return;
- }
-
+ this.paginationControlView.setCurrentPage(pageNum);//provides validation
+ this.showCurContentPage(this.paginationControlView.currentPage);
+ this.paginationControlView.render();
+ this.paginationControlView.hideShowNavButtons();
+ var assignmentPaths = this.findAssigmentEndpointsOnPage(pageNum);
+ this.trigger('endpoints:filtered',assignmentPaths);
},
/* for testing */
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/LessonOverviewView.js b/webgoat-container/src/main/resources/static/js/goatApp/view/LessonOverviewView.js
index f17b3e9aa..c073fbe9f 100644
--- a/webgoat-container/src/main/resources/static/js/goatApp/view/LessonOverviewView.js
+++ b/webgoat-container/src/main/resources/static/js/goatApp/view/LessonOverviewView.js
@@ -39,7 +39,7 @@ function($,
} else {
this.$el.show();
}
- this.showAssignments();
+ //this.showAssignments();
return this;
},
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/PaginationControlView.js b/webgoat-container/src/main/resources/static/js/goatApp/view/PaginationControlView.js
new file mode 100644
index 000000000..d156a6756
--- /dev/null
+++ b/webgoat-container/src/main/resources/static/js/goatApp/view/PaginationControlView.js
@@ -0,0 +1,186 @@
+define(['jquery',
+ 'underscore',
+ 'backbone',
+ 'goatApp/model/LessonOverviewModel',
+ 'text!templates/paging_controls.html'],
+ function ($,
+ _,
+ Backbone,
+ LessonOverviewModel,
+ PaginationTemplate) {
+ return Backbone.View.extend({
+ template: PaginationTemplate,
+ el: '#lesson-page-controls',
+
+ initialize: function ($contentPages,baseLessonUrl) {
+ this.$contentPages = $contentPages;
+ this.model = new LessonOverviewModel();
+ this.listenTo(this.model, 'change add remove update reset', this.render);
+ this.numPages = this.$contentPages.length;
+ this.baseUrl = baseLessonUrl;
+
+ this.model.fetch();
+ this.initPagination();
+ this.render();
+ },
+
+ render: function () {
+ this.parseLinks();
+ var t = _.template(this.template);
+ this.$el.html(t({'overview':this.lessonOverview}));
+ this.bindNavButtons();
+ this.hideShowNavButtons();
+ },
+
+ bindNavButtons: function() {
+ this.$el.find('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').unbind().on('click',this.incrementPageView.bind(this));
+ this.$el.find('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').unbind().on('click', this.decrementPageView.bind(this));
+ this.navButtonsBound = true;
+ },
+
+ parseLinks: function() {
+ var assignmentCount = this.$contentPages.find('.attack-container');
+ var solvedMap = {};
+ var pages = [];
+ // one pass on solved assignmets
+ _.each(this.model.toJSON(), function(assignment) {
+ if (assignment.solved) {
+ var key = assignment.assignment.path; //.replace(/\//g,'');
+ solvedMap[key] = assignment.assignment.name;
+ }
+ });
+
+ isAttackSolved = function (path) {
+ //strip
+ var newPath = path.replace(/^\/WebGoat/,'');
+ if (typeof solvedMap[newPath] !== 'undefined') {
+ return true;
+ }
+ return false;
+ };
+
+ var self = this;
+ var pages, pageClass, solved;
+ _.each(this.$contentPages,function(page,index) {
+ var curPageClass = (self.currentPage == index) ? ' cur-page' : '';
+
+ if ($(page).find('.attack-container').length < 1) { // no assignments [attacks]
+ pageClass = 'page-link';
+ pages.push({content:'content',pageClass:pageClass,curPageClass:curPageClass});
+ } else {
+ var $assignmentForms = $(page).find('.attack-container form');
+ // use for loop to avoid anonymous function scope hell
+ //var pageAssignments = {content:'attack',attacks:[]}
+ pageClass = 'attack-link'
+ var solvedClass = 'solved-true'
+ for (var i=0; i< $assignmentForms.length; i++) {
+ //normalize path
+ var action = $assignmentForms.attr('action');//.replace(/\//g,'');
+ if (action && isAttackSolved(action)) {
+ //pageClass = 'fa fa-check-square-o assignment-solved';
+ //pageAssignments.attacks.push({solved:true});
+ } else {
+ solvedClass = 'solved-false';
+
+ }
+ }
+ pages.push({solvedClass:solvedClass,content:'assignment',curPageClass:curPageClass,pageClass:pageClass});
+ }
+ });
+ //assign to the view
+ this.lessonOverview = {
+ baseUrl: this.baseUrl,
+ pages: pages
+ }
+ },
+
+ showPrevPageButton: function() {
+ $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').show();
+ },
+
+ hidePrevPageButton: function() {
+ $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').hide();
+ },
+
+ showNextPageButton: function() {
+ $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').show();
+ },
+
+ hideNextPageButton: function() {
+ $('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').hide();
+ },
+
+ initPagination: function() {
+ //track pagination state in this view ... start at 0
+ this.currentPage = 0;
+ },
+
+ setCurrentPage: function (pageNum) {
+ this.currentPage = (_.isNumber(pageNum) && pageNum < this.numPages) ? pageNum : 0;
+ },
+
+ /* increment, decrement & display handlers */
+ incrementPageView: function() {
+ if (this.currentPage < this.numPages -1) {
+ this.currentPage++;
+ window.location.href = this.baseUrl + '/' + this.currentPage;
+ }
+
+ if (this.currentPage > 0) {
+ this.showPrevPageButton();
+ }
+
+ if (this.currentPage >= this.numPages -1) {
+ this.hideNextPageButton();
+ this.showPrevPageButton;
+ }
+ this.render();
+ },
+
+ decrementPageView: function() {
+ if (this.currentPage > 0) {
+ this.currentPage--;
+ window.location.href = this.baseUrl + '/' + this.currentPage;
+ }
+
+ if (this.currentPage < this.numPages -1) {
+ this.showNextPageButton();
+ }
+
+ if (this.currentPage == 0) {
+ this.hidePrevPageButton();
+ this.showNextPageButton()
+ }
+ this.render();
+ },
+
+ hideShowNavButtons: function () {
+ //one page only
+ if (this.numPages === 1) {
+ this.hidePrevPageButton();
+ this.hideNextPageButton();
+ }
+ //first page
+ if (this.currentPage === 0) {
+ this.hidePrevPageButton();
+ if (this.numPages > 1) {
+ this.showNextPageButton();
+ }
+ return;
+ }
+ // > first page, but not last
+ if (this.currentPage > 0 && this.currentPage < this.numPages -1) {
+ this.showNextPageButton();
+ this.showPrevPageButton();
+ return;
+ }
+ // last page and more than one page
+ if (this.currentPage === this.numPages -1 && this.numPages > 1) {
+ this.hideNextPageButton();
+ this.showPrevPageButton();
+ return;
+ }
+
+ },
+ });
+ });
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/ScoreboardView.js b/webgoat-container/src/main/resources/static/js/goatApp/view/ScoreboardView.js
new file mode 100644
index 000000000..fb72d16aa
--- /dev/null
+++ b/webgoat-container/src/main/resources/static/js/goatApp/view/ScoreboardView.js
@@ -0,0 +1,32 @@
+define(['jquery',
+ 'underscore',
+ 'backbone',
+ 'goatApp/model/FlagsCollection',
+ 'text!templates/scoreboard.html'],
+function($,
+ _,
+ Backbone,
+ FlagsCollection,
+ ScoreboardTemplate) {
+ return Backbone.View.extend({
+ el:'#scoreboard',
+
+ initialize: function() {
+ this.template = ScoreboardTemplate,
+ this.collection = new FlagsCollection();
+ this.listenTo(this.collection,'reset',this.render)
+ this.collection.fetch({reset:true});
+ },
+
+ render: function() {
+ //this.$el.html('test');
+ var t = _.template(this.template);
+ this.$el.html(t({'rankings':this.collection.toJSON()}));
+ setTimeout(this.pollData.bind(this), 5000);
+ },
+
+ pollData: function() {
+ this.collection.fetch({reset:true});
+ }
+ });
+});
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/static/js/scoreboard.js b/webgoat-container/src/main/resources/static/js/scoreboard.js
new file mode 100644
index 000000000..f561853f2
--- /dev/null
+++ b/webgoat-container/src/main/resources/static/js/scoreboard.js
@@ -0,0 +1,47 @@
+//main.js
+/*
+/js
+js/main.js << main file for require.js
+--/libs/(jquery,backbone,etc.) << base libs
+--/goatApp/ << base dir for goat application, js-wise
+--/goatApp/model
+--/goatApp/view
+--/goatApp/support
+--/goatApp/controller
+*/
+
+require.config({
+ baseUrl: "js/",
+ paths: {
+ jquery: 'libs/jquery-2.2.4.min',
+ jqueryui: 'libs/jquery-ui-1.10.4',
+ underscore: 'libs/underscore-min',
+ backbone: 'libs/backbone-min',
+ text: 'libs/text',
+ templates: 'goatApp/templates',
+ polyglot: 'libs/polyglot.min'
+ },
+
+ map: {
+ 'libs/jquery-base' : {'jquery':'libs/jquery-2.2.4.min'},
+ 'libs/jquery-vuln' : {'jquery':'libs/jquery-2.1.4.min'}
+ },
+
+ shim: {
+ "jqueryui": {
+ exports:"$",
+ deps: ['jquery']
+ },
+ underscore: {
+ exports: "_"
+ },
+ backbone: {
+ deps: ['underscore', 'jquery'],
+ exports: 'Backbone'
+ }
+ }
+});
+
+require(['jquery','libs/jquery-base','libs/jquery-vuln','jqueryui', 'underscore','backbone','goatApp/scoreboardApp'], function($,jqueryBase,jqueryVuln,jqueryui,_,Backbone,ScoreboardApp){
+ ScoreboardApp.initApp();
+});
\ No newline at end of file
diff --git a/webgoat-container/src/main/resources/templates/main_new.html b/webgoat-container/src/main/resources/templates/main_new.html
index 925781ffa..e96256199 100644
--- a/webgoat-container/src/main/resources/templates/main_new.html
+++ b/webgoat-container/src/main/resources/templates/main_new.html
@@ -2,7 +2,7 @@
-
+
@@ -143,10 +143,7 @@
Show hints
- Lesson
- overview
-
+
Reset Lesson
@@ -172,7 +169,7 @@
-
+
diff --git a/webgoat-container/src/main/resources/templates/scoreboard.html b/webgoat-container/src/main/resources/templates/scoreboard.html
new file mode 100644
index 000000000..1e9d0a674
--- /dev/null
+++ b/webgoat-container/src/main/resources/templates/scoreboard.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
WebGoat
+
+
+
+
+
+
+
+
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/assignments/AssignmentEndpointTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/assignments/AssignmentEndpointTest.java
index 787711c67..1f9628fb0 100644
--- a/webgoat-container/src/test/java/org/owasp/webgoat/assignments/AssignmentEndpointTest.java
+++ b/webgoat-container/src/test/java/org/owasp/webgoat/assignments/AssignmentEndpointTest.java
@@ -30,18 +30,24 @@ import org.owasp.webgoat.i18n.Language;
import org.owasp.webgoat.i18n.Messages;
import org.owasp.webgoat.i18n.PluginMessages;
import org.owasp.webgoat.session.UserSessionData;
-import org.owasp.webgoat.session.UserTracker;
+import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.session.WebSession;
+import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import java.util.Locale;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
public class AssignmentEndpointTest {
@Mock
protected UserTracker userTracker;
@Mock
+ protected UserTrackerRepository userTrackerRepository;
+ @Mock
protected WebSession webSession;
@Mock
protected UserSessionData userSessionData;
@@ -56,7 +62,8 @@ public class AssignmentEndpointTest {
public void init(AssignmentEndpoint a) {
messages.setBasenames("classpath:/i18n/messages", "classpath:/i18n/WebGoatLabels");
- ReflectionTestUtils.setField(a, "userTracker", userTracker);
+ when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
+ ReflectionTestUtils.setField(a, "userTrackerRepository", userTrackerRepository);
ReflectionTestUtils.setField(a, "userSessionData", userSessionData);
ReflectionTestUtils.setField(a, "webSession", webSession);
ReflectionTestUtils.setField(a, "messages", pluginMessages);
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/service/LessonMenuServiceTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/service/LessonMenuServiceTest.java
new file mode 100644
index 000000000..d71126d82
--- /dev/null
+++ b/webgoat-container/src/test/java/org/owasp/webgoat/service/LessonMenuServiceTest.java
@@ -0,0 +1,91 @@
+package org.owasp.webgoat.service;
+
+import com.beust.jcommander.internal.Lists;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.owasp.webgoat.lessons.AbstractLesson;
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.NewLesson;
+import org.owasp.webgoat.session.Course;
+import org.owasp.webgoat.session.WebSession;
+import org.owasp.webgoat.users.LessonTracker;
+import org.owasp.webgoat.users.UserTracker;
+import org.owasp.webgoat.users.UserTrackerRepository;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+import static org.owasp.webgoat.service.LessonMenuService.URL_LESSONMENU_MVC;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+
+/**
+ * @author nbaars
+ * @since 4/16/17.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class LessonMenuServiceTest {
+
+ @Mock
+ private Course course;
+ @Mock
+ private UserTracker userTracker;
+ @Mock
+ private UserTrackerRepository userTrackerRepository;
+ @Mock
+ private WebSession webSession;
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ this.mockMvc = standaloneSetup(new LessonMenuService(course, webSession, userTrackerRepository)).build();
+ }
+
+ @Test
+ public void lessonsShouldBeOrdered() throws Exception {
+ NewLesson l1 = Mockito.mock(NewLesson.class);
+ NewLesson l2 = Mockito.mock(NewLesson.class);
+ when(l1.getTitle()).thenReturn("ZA");
+ when(l2.getTitle()).thenReturn("AA");
+ when(l1.getCategory()).thenReturn(Category.ACCESS_CONTROL);
+ when(l2.getCategory()).thenReturn(Category.ACCESS_CONTROL);
+ LessonTracker lessonTracker = Mockito.mock(LessonTracker.class);
+ when(lessonTracker.isLessonSolved()).thenReturn(false);
+ when(course.getLessons(any())).thenReturn(Lists.newArrayList(l1, l2));
+ when(course.getCategories()).thenReturn(Lists.newArrayList(Category.ACCESS_CONTROL));
+ when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
+ when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
+
+ mockMvc.perform(MockMvcRequestBuilders.get(URL_LESSONMENU_MVC))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[0].children[0].name", CoreMatchers.is("AA")))
+ .andExpect(jsonPath("$[0].children[1].name", CoreMatchers.is("ZA")));
+ }
+
+ @Test
+ public void lessonCompleted() throws Exception {
+ NewLesson l1 = Mockito.mock(NewLesson.class);
+ when(l1.getTitle()).thenReturn("ZA");
+ when(l1.getCategory()).thenReturn(Category.ACCESS_CONTROL);
+ LessonTracker lessonTracker = Mockito.mock(LessonTracker.class);
+ when(lessonTracker.isLessonSolved()).thenReturn(true);
+ when(course.getLessons(any())).thenReturn(Lists.newArrayList(l1));
+ when(course.getCategories()).thenReturn(Lists.newArrayList(Category.ACCESS_CONTROL));
+ when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
+ when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
+
+
+ mockMvc.perform(MockMvcRequestBuilders.get(URL_LESSONMENU_MVC))
+ .andExpect(status().isOk()).andDo(print())
+ .andExpect(jsonPath("$[0].children[0].complete", CoreMatchers.is(true)));
+ }
+}
\ No newline at end of file
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/service/LessonProgressServiceTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/service/LessonProgressServiceTest.java
index b89d4fa23..2ca3e9169 100644
--- a/webgoat-container/src/test/java/org/owasp/webgoat/service/LessonProgressServiceTest.java
+++ b/webgoat-container/src/test/java/org/owasp/webgoat/service/LessonProgressServiceTest.java
@@ -8,9 +8,10 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
-import org.owasp.webgoat.session.LessonTracker;
-import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession;
+import org.owasp.webgoat.users.LessonTracker;
+import org.owasp.webgoat.users.UserTracker;
+import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@@ -18,6 +19,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -63,15 +65,18 @@ public class LessonProgressServiceTest {
@Mock
private LessonTracker lessonTracker;
@Mock
+ private UserTrackerRepository userTrackerRepository;
+ @Mock
private WebSession websession;
@Before
public void setup() {
Assignment assignment = new Assignment("test", "test");
- when(userTracker.getLessonTracker(any())).thenReturn(lessonTracker);
+ when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
+ when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
when(websession.getCurrentLesson()).thenReturn(lesson);
when(lessonTracker.getLessonOverview()).thenReturn(Maps.newHashMap(assignment, true));
- this.mockMvc = MockMvcBuilders.standaloneSetup(new LessonProgressService(userTracker, websession)).build();
+ this.mockMvc = MockMvcBuilders.standaloneSetup(new LessonProgressService(userTrackerRepository, websession)).build();
}
@Test
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/service/ReportCardServiceTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/service/ReportCardServiceTest.java
index bd3685d86..e1b6f639f 100644
--- a/webgoat-container/src/test/java/org/owasp/webgoat/service/ReportCardServiceTest.java
+++ b/webgoat-container/src/test/java/org/owasp/webgoat/service/ReportCardServiceTest.java
@@ -8,14 +8,17 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.session.Course;
-import org.owasp.webgoat.session.LessonTracker;
-import org.owasp.webgoat.session.UserTracker;
+import org.owasp.webgoat.session.WebSession;
+import org.owasp.webgoat.users.LessonTracker;
+import org.owasp.webgoat.users.UserTracker;
+import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -33,10 +36,14 @@ public class ReportCardServiceTest {
private AbstractLesson lesson;
@Mock
private LessonTracker lessonTracker;
+ @Mock
+ private UserTrackerRepository userTrackerRepository;
+ @Mock
+ private WebSession websession;
@Before
public void setup() {
- this.mockMvc = standaloneSetup(new ReportCardService(userTracker, course)).build();
+ this.mockMvc = standaloneSetup(new ReportCardService(websession, userTrackerRepository, course)).build();
}
@Test
@@ -46,7 +53,8 @@ public class ReportCardServiceTest {
when(course.getTotalOfLessons()).thenReturn(1);
when(course.getTotalOfAssignments()).thenReturn(10);
when(course.getLessons()).thenReturn(Lists.newArrayList(lesson));
- when(userTracker.getLessonTracker(any())).thenReturn(lessonTracker);
+ when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
+ when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
mockMvc.perform(MockMvcRequestBuilders.get("/service/reportcard.mvc"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalNumberOfLessons", is(1)))
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/session/LessonTrackerTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/session/LessonTrackerTest.java
index 9a24193d4..194b30b2c 100644
--- a/webgoat-container/src/test/java/org/owasp/webgoat/session/LessonTrackerTest.java
+++ b/webgoat-container/src/test/java/org/owasp/webgoat/session/LessonTrackerTest.java
@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import org.junit.Test;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
+import org.owasp.webgoat.users.LessonTracker;
import java.util.List;
import java.util.Map;
@@ -69,5 +70,18 @@ public class LessonTrackerTest {
assertThat(lessonOverview.get(a2)).isFalse();
}
+ @Test
+ public void solvingSameAssignmentShouldNotAddItTwice() {
+ AbstractLesson lesson = mock(AbstractLesson.class);
+ Assignment a1 = new Assignment("a1", "a1");
+ List
assignments = Lists.newArrayList(a1);
+ when(lesson.getAssignments()).thenReturn(assignments);
+ LessonTracker lessonTracker = new LessonTracker(lesson);
+ lessonTracker.assignmentSolved("a1");
+ lessonTracker.assignmentSolved("a1");
+
+ assertThat(lessonTracker.getLessonOverview().size()).isEqualTo(1);
+ }
+
}
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/session/UserTrackerTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/session/UserTrackerTest.java
deleted file mode 100644
index 209c19a31..000000000
--- a/webgoat-container/src/test/java/org/owasp/webgoat/session/UserTrackerTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.owasp.webgoat.session;
-
-import com.google.common.collect.Lists;
-import org.junit.Before;
-import org.junit.Test;
-import org.owasp.webgoat.lessons.AbstractLesson;
-import org.owasp.webgoat.lessons.Assignment;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.UUID;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * ************************************************************************************************
- * 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.
- *
- *
- * @author nbaars
- * @version $Id: $Id
- * @since November 15, 2016
- */
-public class UserTrackerTest {
-
- private File home;
-
- @Before
- public void init() throws IOException {
- home = File.createTempFile("test", "test");
- home.deleteOnExit();
- }
-
- @Test
- public void writeAndRead() {
- UserTracker userTracker = new UserTracker(home.getParent(), "test");
- AbstractLesson lesson = mock(AbstractLesson.class);
- when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
- userTracker.getLessonTracker(lesson);
- userTracker.assignmentSolved(lesson, lesson.getAssignments().get(0).getName());
-
- userTracker = new UserTracker(home.getParent(), "test");
- userTracker.load();
- assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isTrue();
- }
-
- @Test
- public void assignmentFailedShouldIncrementAttempts() {
- UserTracker userTracker = new UserTracker(home.getParent(), UUID.randomUUID().toString());
- AbstractLesson lesson = mock(AbstractLesson.class);
- when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
- userTracker.getLessonTracker(lesson);
- userTracker.assignmentFailed(lesson);
- userTracker.assignmentFailed(lesson);
-
- assertThat(userTracker.getLessonTracker(lesson).getNumberOfAttempts()).isEqualTo(2);
- }
-
- @Test
- public void resetShouldClearSolvedAssignment() {
- UserTracker userTracker = new UserTracker(home.getParent(), "test");
- AbstractLesson lesson = mock(AbstractLesson.class);
- when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
- userTracker.getLessonTracker(lesson);
- userTracker.assignmentSolved(lesson, "assignment");
-
- assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isTrue();
- userTracker.reset(lesson);
- assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isFalse();
- }
-
- @Test
- public void totalAssignmentsSolved() {
- UserTracker userTracker = new UserTracker(home.getParent(), "test");
- AbstractLesson lesson = mock(AbstractLesson.class);
- when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
- userTracker.getLessonTracker(lesson);
- userTracker.assignmentSolved(lesson, "assignment");
-
- assertThat(userTracker.numberOfAssignmentsSolved()).isEqualTo(1);
- assertThat(userTracker.numberOfLessonsSolved()).isEqualTo(1);
- }
-}
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/users/UserServiceTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/users/UserServiceTest.java
index 15606b7e8..b4128f79a 100644
--- a/webgoat-container/src/test/java/org/owasp/webgoat/users/UserServiceTest.java
+++ b/webgoat-container/src/test/java/org/owasp/webgoat/users/UserServiceTest.java
@@ -14,11 +14,14 @@ public class UserServiceTest {
@Mock
private UserRepository userRepository;
+ @Mock
+ private UserTrackerRepository userTrackerRepository;
+
@Test(expected = UsernameNotFoundException.class)
public void shouldThrowExceptionWhenUserIsNotFound() {
when(userRepository.findByUsername(any())).thenReturn(null);
- UserService userService = new UserService(userRepository);
+ UserService userService = new UserService(userRepository, userTrackerRepository);
userService.loadUserByUsername("unknown");
}
diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/users/UserValidatorTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/users/UserValidatorTest.java
index 7968ed30f..c2f767e44 100644
--- a/webgoat-container/src/test/java/org/owasp/webgoat/users/UserValidatorTest.java
+++ b/webgoat-container/src/test/java/org/owasp/webgoat/users/UserValidatorTest.java
@@ -4,7 +4,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
-import org.owasp.webgoat.session.WebGoatUser;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
diff --git a/webgoat-container/src/test/scala/Simulation.scala b/webgoat-container/src/test/scala/Simulation.scala
new file mode 100644
index 000000000..4e8fe9b42
--- /dev/null
+++ b/webgoat-container/src/test/scala/Simulation.scala
@@ -0,0 +1,27 @@
+import io.gatling.core.Predef._
+import io.gatling.http.Predef._
+import org.apache.commons.lang3.RandomStringUtils
+
+import scala.concurrent.duration._
+
+class BasicSimulation extends Simulation {
+
+ val httpConf = http
+ .baseURL("http://localhost:8080/WebGoat/") // Here is the root for all relative URLs
+ .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
+
+ val scn = scenario("Register and automatic login").
+ exec(session =>
+ session.setAll(("username", RandomStringUtils.randomAlphabetic(10)))
+ )
+ .exec(
+ http("Test")
+ .post("register.mvc")
+ .formParam("username", "${username}")
+ .formParam("password", "${username}")
+ .formParam("matchingPassword", "${username}")
+ .formParam("agree", "agree")
+ )
+
+ setUp(scn.inject(atOnceUsers(100)).protocols(httpConf))
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/pom.xml b/webgoat-lessons/challenge/pom.xml
index a2cc9c6f4..04adf95cb 100644
--- a/webgoat-lessons/challenge/pom.xml
+++ b/webgoat-lessons/challenge/pom.xml
@@ -9,4 +9,30 @@
8.0-SNAPSHOT
+
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.7.0
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ 4.1.3.RELEASE
+ test
+
+
+ junit
+ junit
+ ${junit.version}
+ jar
+ test
+
+
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Challenge.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/ChallengeIntro.java
similarity index 88%
rename from webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Challenge.java
rename to webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/ChallengeIntro.java
index be24197ca..b8cde5103 100644
--- a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Challenge.java
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/ChallengeIntro.java
@@ -10,7 +10,7 @@ import java.util.List;
* @author nbaars
* @since 3/21/17.
*/
-public class Challenge extends NewLesson {
+public class ChallengeIntro extends NewLesson {
@Override
public Category getDefaultCategory() {
@@ -29,7 +29,7 @@ public class Challenge extends NewLesson {
@Override
public String getTitle() {
- return "challenge.title";
+ return "challenge0.title";
}
@Override
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Flag.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Flag.java
new file mode 100644
index 000000000..a1caa5266
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Flag.java
@@ -0,0 +1,75 @@
+package org.owasp.webgoat.plugin;
+
+import com.google.common.collect.Maps;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.owasp.webgoat.assignments.AttackResult;
+import org.owasp.webgoat.assignments.Endpoint;
+import org.owasp.webgoat.i18n.PluginMessages;
+import org.owasp.webgoat.session.WebSession;
+import org.owasp.webgoat.users.UserTracker;
+import org.owasp.webgoat.users.UserTrackerRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.annotation.PostConstruct;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.IntStream;
+
+/**
+ * @author nbaars
+ * @since 3/23/17.
+ */
+@Slf4j
+public class Flag extends Endpoint {
+
+ public static final Map FLAGS = Maps.newHashMap();
+ @Autowired
+ private UserTrackerRepository userTrackerRepository;
+ @Autowired
+ private WebSession webSession;
+ @Autowired
+ private PluginMessages pluginMessages;
+
+ @AllArgsConstructor
+ private class FlagPosted {
+ @Getter
+ private boolean lessonCompleted;
+ }
+
+ @PostConstruct
+ public void initFlags() {
+ IntStream.range(1, 7).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString()));
+ FLAGS.entrySet().stream().forEach(e -> log.debug("Flag {} {}", e.getKey(), e.getValue()));
+ }
+
+ @Override
+ public String getPath() {
+ return "challenge/flag";
+ }
+
+ @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public AttackResult postFlag(@RequestParam String flag) {
+ UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
+ String currentChallenge = webSession.getCurrentLesson().getName();
+ int challengeNumber = Integer.valueOf(currentChallenge.substring(currentChallenge.length() - 1, currentChallenge.length()));
+ String expectedFlag = FLAGS.get(challengeNumber);
+ final AttackResult attackResult;
+ if (expectedFlag.equals(flag)) {
+ userTracker.assignmentSolved(webSession.getCurrentLesson(), "Assignment" + challengeNumber);
+ attackResult = new AttackResult.AttackResultBuilder(pluginMessages).lessonCompleted(true, "challenge.flag.correct").build();
+ } else {
+ userTracker.assignmentFailed(webSession.getCurrentLesson());
+ attackResult = new AttackResult.AttackResultBuilder(pluginMessages).feedback("challenge.flag.incorrect").build();
+ }
+ userTrackerRepository.save(userTracker);
+ return attackResult;
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/SolutionConstants.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/SolutionConstants.java
new file mode 100644
index 000000000..86586d36b
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/SolutionConstants.java
@@ -0,0 +1,18 @@
+package org.owasp.webgoat.plugin;
+
+/**
+ * Interface with constants so we can easily change the flags
+ *
+ * @author nbaars
+ * @since 3/23/17.
+ */
+public interface SolutionConstants {
+
+ //TODO should be random generated when starting the server
+ String PASSWORD = "!!webgoat_admin_1234!!";
+ String SUPER_COUPON_CODE = "get_it_for_free";
+ String PASSWORD_TOM = "thisisasecretfortomonly";
+ String PASSWORD_LARRY = "larryknows";
+ String JWT_PASSWORD = "victory";
+
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge1/Assignment1.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge1/Assignment1.java
new file mode 100644
index 000000000..446111d22
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge1/Assignment1.java
@@ -0,0 +1,68 @@
+package org.owasp.webgoat.plugin.challenge1;
+
+import org.owasp.webgoat.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.assignments.AssignmentPath;
+import org.owasp.webgoat.assignments.AttackResult;
+import org.owasp.webgoat.plugin.Flag;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD;
+
+/**
+ * ************************************************************************************************
+ * 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.
+ *
+ *
+ * @author WebGoat
+ * @version $Id: $Id
+ * @since August 11, 2016
+ */
+@AssignmentPath("/challenge/1")
+public class Assignment1 extends AssignmentEndpoint {
+
+ @RequestMapping(method = RequestMethod.POST)
+ public
+ @ResponseBody
+ AttackResult completed(@RequestParam String username, @RequestParam String password, HttpServletRequest request) throws IOException {
+ boolean ipAddressKnown = true;
+ boolean passwordCorrect = "admin".equals(username) && PASSWORD.equals(password);
+ if (passwordCorrect && ipAddressKnown) {
+ return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(1)).build();
+ } else if (passwordCorrect) {
+ return failed().feedback("ip.address.unknown").build();
+ }
+ return failed().build();
+ }
+
+ public static boolean containsHeader(HttpServletRequest request) {
+ return StringUtils.hasText(request.getHeader("X-Forwarded-For"));
+
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge1/Challenge1.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge1/Challenge1.java
new file mode 100644
index 000000000..86364d124
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge1/Challenge1.java
@@ -0,0 +1,39 @@
+package org.owasp.webgoat.plugin.challenge1;
+
+import com.google.common.collect.Lists;
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.NewLesson;
+
+import java.util.List;
+
+/**
+ * @author nbaars
+ * @since 3/21/17.
+ */
+public class Challenge1 extends NewLesson {
+
+ @Override
+ public Category getDefaultCategory() {
+ return Category.CHALLENGE;
+ }
+
+ @Override
+ public List getHints() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public Integer getDefaultRanking() {
+ return 10;
+ }
+
+ @Override
+ public String getTitle() {
+ return "challenge1.title";
+ }
+
+ @Override
+ public String getId() {
+ return "Challenge1";
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/Assignment2.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/Assignment2.java
new file mode 100644
index 000000000..d46535589
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/Assignment2.java
@@ -0,0 +1,32 @@
+package org.owasp.webgoat.plugin.challenge2;
+
+import org.owasp.webgoat.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.assignments.AssignmentPath;
+import org.owasp.webgoat.assignments.AttackResult;
+import org.owasp.webgoat.plugin.Flag;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.io.IOException;
+
+import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
+
+/**
+ * @author nbaars
+ * @since 4/6/17.
+ */
+@AssignmentPath("/challenge/2")
+public class Assignment2 extends AssignmentEndpoint {
+
+ @RequestMapping(method = RequestMethod.POST)
+ public
+ @ResponseBody
+ AttackResult completed(@RequestParam String checkoutCode) throws IOException {
+ if (SUPER_COUPON_CODE.equals(checkoutCode)) {
+ return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(2)).build();
+ }
+ return failed().build();
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/Challenge2.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/Challenge2.java
new file mode 100644
index 000000000..94b1cf58b
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/Challenge2.java
@@ -0,0 +1,39 @@
+package org.owasp.webgoat.plugin.challenge2;
+
+import com.google.common.collect.Lists;
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.NewLesson;
+
+import java.util.List;
+
+/**
+ * @author nbaars
+ * @since 3/21/17.
+ */
+public class Challenge2 extends NewLesson {
+
+ @Override
+ public Category getDefaultCategory() {
+ return Category.CHALLENGE;
+ }
+
+ @Override
+ public List getHints() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public Integer getDefaultRanking() {
+ return 10;
+ }
+
+ @Override
+ public String getTitle() {
+ return "challenge2.title";
+ }
+
+ @Override
+ public String getId() {
+ return "Challenge2";
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/ShopEndpoint.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/ShopEndpoint.java
new file mode 100644
index 000000000..12c929492
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge2/ShopEndpoint.java
@@ -0,0 +1,68 @@
+package org.owasp.webgoat.plugin.challenge2;
+
+import com.beust.jcommander.internal.Lists;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
+
+/**
+ * @author nbaars
+ * @since 4/6/17.
+ */
+@RestController
+@RequestMapping("challenge-store")
+public class ShopEndpoint {
+
+ @AllArgsConstructor
+ private class CheckoutCodes {
+
+ @Getter
+ private List codes = Lists.newArrayList();
+
+ public Optional get(String code) {
+ return codes.stream().filter(c -> c.getCode().equals(code)).findFirst();
+ }
+ }
+
+ @AllArgsConstructor
+ @Getter
+ private class CheckoutCode {
+ private String code;
+ private int discount;
+ }
+
+ private CheckoutCodes checkoutCodes;
+
+ public ShopEndpoint() {
+ List codes = Lists.newArrayList();
+ codes.add(new CheckoutCode("webgoat", 25));
+ codes.add(new CheckoutCode("owasp", 25));
+ codes.add(new CheckoutCode("owasp-webgoat", 50));
+ this.checkoutCodes = new CheckoutCodes(codes);
+ }
+
+ @GetMapping(value = "/coupons/{code}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public CheckoutCode getDiscountCode(@PathVariable String code) {
+ if (SUPER_COUPON_CODE.equals(code)) {
+ return new CheckoutCode(SUPER_COUPON_CODE, 100);
+ }
+ return checkoutCodes.get(code).orElse(new CheckoutCode("no", 0));
+ }
+
+ @GetMapping(value = "/coupons", produces = MediaType.APPLICATION_JSON_VALUE)
+ public CheckoutCodes all() {
+ List all = Lists.newArrayList();
+ all.addAll(this.checkoutCodes.getCodes());
+ all.add(new CheckoutCode(SUPER_COUPON_CODE, 100));
+ return new CheckoutCodes(all);
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Assignment3.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Assignment3.java
new file mode 100644
index 000000000..ed32e2458
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Assignment3.java
@@ -0,0 +1,148 @@
+package org.owasp.webgoat.plugin.challenge3;
+
+import com.beust.jcommander.internal.Lists;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.EvictingQueue;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+import lombok.SneakyThrows;
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.owasp.webgoat.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.assignments.AssignmentPath;
+import org.owasp.webgoat.assignments.AttackResult;
+import org.owasp.webgoat.plugin.Flag;
+import org.owasp.webgoat.session.WebSession;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.annotation.PostConstruct;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Map;
+
+import static org.springframework.http.MediaType.ALL_VALUE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+
+/**
+ * @author nbaars
+ * @since 4/8/17.
+ */
+@AssignmentPath("/challenge/3")
+public class Assignment3 extends AssignmentEndpoint {
+
+ @Value("${webgoat.server.directory}")
+ private String webGoatHomeDirectory;
+ @Autowired
+ private WebSession webSession;
+ private static DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd, HH:mm:ss");
+
+ private static final Map> userComments = Maps.newHashMap();
+ private static final EvictingQueue comments = EvictingQueue.create(100);
+ private static final String secretContents = "Congratulations you may now collect your flag";
+
+ static {
+ comments.add(new Comment("webgoat", DateTime.now().toString(fmt), "Silly cat...."));
+ comments.add(new Comment("guest", DateTime.now().toString(fmt), "I think I will use this picture in one of my projects."));
+ comments.add(new Comment("guest", DateTime.now().toString(fmt), "Lol!! :-)."));
+ }
+
+ @PostConstruct
+ @SneakyThrows
+ public void copyFile() {
+ File targetDirectory = new File(webGoatHomeDirectory, "/challenges");
+ if (!targetDirectory.exists()) {
+ targetDirectory.mkdir();
+ }
+ Files.write(secretContents, new File(targetDirectory, "secret.txt"), Charset.defaultCharset());
+ }
+
+ @RequestMapping(method = GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public Collection retrieveComments() {
+ Collection allComments = Lists.newArrayList();
+ Collection xmlComments = userComments.get(webSession.getUserName());
+ if (xmlComments != null) {
+ allComments.addAll(xmlComments);
+ }
+ allComments.addAll(comments);
+ return allComments;
+ }
+
+ @RequestMapping(method = POST, consumes = ALL_VALUE, produces = APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public AttackResult createNewComment(@RequestBody String commentStr, @RequestHeader("Content-Type") String contentType) throws Exception {
+ Comment comment = null;
+ AttackResult attackResult = failed().build();
+ if (APPLICATION_JSON_VALUE.equals(contentType)) {
+ comment = parseJson(commentStr);
+ comment.setDateTime(DateTime.now().toString(fmt));
+ comment.setUser(webSession.getUserName());
+ comments.add(comment);
+ }
+ if (MediaType.APPLICATION_XML_VALUE.equals(contentType)) {
+ //Do not show these comments to all users
+ comment = parseXml(commentStr);
+ comment.setDateTime(DateTime.now().toString(fmt));
+ comment.setUser(webSession.getUserName());
+ EvictingQueue comments = userComments.getOrDefault(webSession.getUserName(), EvictingQueue.create(100));
+ comments.add(comment);
+ userComments.put(webSession.getUserName(), comments);
+ }
+ if (checkSolution(comment)) {
+ attackResult = success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(2)).build();
+ }
+ return attackResult;
+ }
+
+ private boolean checkSolution(Comment comment) {
+ if (StringUtils.equals(comment.getText(), secretContents)) {
+ comment.setText("Congratulations to " + webSession.getUserName() + " for finding the flag!!");
+ comments.add(comment);
+ return true;
+ }
+ return false;
+ }
+
+ public static Comment parseXml(String xml) throws Exception {
+ JAXBContext jc = JAXBContext.newInstance(Comment.class);
+
+ XMLInputFactory xif = XMLInputFactory.newFactory();
+ xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
+ xif.setProperty(XMLInputFactory.IS_VALIDATING, false);
+
+ xif.setProperty(XMLInputFactory.SUPPORT_DTD, true);
+ XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));
+
+ Unmarshaller unmarshaller = jc.createUnmarshaller();
+ return (Comment) unmarshaller.unmarshal(xsr);
+ }
+
+ private Comment parseJson(String comment) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ return mapper.readValue(comment, Comment.class);
+ } catch (IOException e) {
+ return new Comment();
+ }
+ }
+
+
+}
+
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Challenge3.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Challenge3.java
new file mode 100644
index 000000000..91a05d4ea
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Challenge3.java
@@ -0,0 +1,39 @@
+package org.owasp.webgoat.plugin.challenge3;
+
+import com.google.common.collect.Lists;
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.NewLesson;
+
+import java.util.List;
+
+/**
+ * @author nbaars
+ * @since 3/21/17.
+ */
+public class Challenge3 extends NewLesson {
+
+ @Override
+ public Category getDefaultCategory() {
+ return Category.CHALLENGE;
+ }
+
+ @Override
+ public List getHints() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public Integer getDefaultRanking() {
+ return 10;
+ }
+
+ @Override
+ public String getTitle() {
+ return "challenge3.title";
+ }
+
+ @Override
+ public String getId() {
+ return "Challenge3";
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Comment.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Comment.java
new file mode 100644
index 000000000..0ea3e0d07
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Comment.java
@@ -0,0 +1,24 @@
+package org.owasp.webgoat.plugin.challenge3;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author nbaars
+ * @since 4/8/17.
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@XmlRootElement
+public class Comment {
+ private String user;
+ private String dateTime;
+ private String text;
+}
+
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Assignment4.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Assignment4.java
new file mode 100644
index 000000000..199ac4d62
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Assignment4.java
@@ -0,0 +1,17 @@
+package org.owasp.webgoat.plugin.challenge4;
+
+import lombok.extern.slf4j.Slf4j;
+import org.owasp.webgoat.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.assignments.AssignmentPath;
+
+/**
+ * @author nbaars
+ * @since 5/3/17.
+ */
+@AssignmentPath("/challenge/4")
+@Slf4j
+public class Assignment4 extends AssignmentEndpoint {
+
+ //just empty, posting the flag will mark the challenge as done as well no need to specify an endpoint here
+
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Challenge4.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Challenge4.java
new file mode 100644
index 000000000..0e878d761
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Challenge4.java
@@ -0,0 +1,39 @@
+package org.owasp.webgoat.plugin.challenge4;
+
+import com.google.common.collect.Lists;
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.NewLesson;
+
+import java.util.List;
+
+/**
+ * @author nbaars
+ * @since 3/21/17.
+ */
+public class Challenge4 extends NewLesson {
+
+ @Override
+ public Category getDefaultCategory() {
+ return Category.CHALLENGE;
+ }
+
+ @Override
+ public List getHints() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public Integer getDefaultRanking() {
+ return 10;
+ }
+
+ @Override
+ public String getTitle() {
+ return "challenge4.title";
+ }
+
+ @Override
+ public String getId() {
+ return "Challenge4";
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Views.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Views.java
new file mode 100644
index 000000000..e9f47594c
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Views.java
@@ -0,0 +1,16 @@
+package org.owasp.webgoat.plugin.challenge4;
+
+/**
+ * @author nbaars
+ * @since 4/30/17.
+ */
+public class Views {
+ interface GuestView {
+ }
+
+ interface UserView extends GuestView {
+ }
+
+ interface AdminView extends UserView {
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Vote.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Vote.java
new file mode 100644
index 000000000..ccb51c3b1
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/Vote.java
@@ -0,0 +1,49 @@
+package org.owasp.webgoat.plugin.challenge4;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author nbaars
+ * @since 5/2/17.
+ */
+@Getter
+public class Vote {
+ @JsonView(Views.GuestView.class)
+ private final String title;
+ @JsonView(Views.GuestView.class)
+ private final String information;
+ @JsonView(Views.GuestView.class)
+ private final String imageSmall;
+ @JsonView(Views.GuestView.class)
+ private final String imageBig;
+ @JsonView(Views.UserView.class)
+ private int numberOfVotes;
+ @JsonView(Views.AdminView.class)
+ @Setter
+ private String flag;
+ @JsonView(Views.UserView.class)
+ private boolean votingAllowed = true;
+ @JsonView(Views.UserView.class)
+ private long average = 0;
+
+
+ public Vote(String title, String information, String imageSmall, String imageBig, int numberOfVotes, int totalVotes) {
+ this.title = title;
+ this.information = information;
+ this.imageSmall = imageSmall;
+ this.imageBig = imageBig;
+ this.numberOfVotes = numberOfVotes;
+ this.average = calculateStars(totalVotes);
+ }
+
+ public void incrementNumberOfVotes(int totalVotes) {
+ this.numberOfVotes = this.numberOfVotes + 1;
+ this.average = calculateStars(totalVotes);
+ }
+
+ private long calculateStars(int totalVotes) {
+ return Math.round(((double) numberOfVotes / (double) totalVotes) * 4);
+ }
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/VotesEndpoint.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/VotesEndpoint.java
new file mode 100644
index 000000000..619e35c13
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge4/VotesEndpoint.java
@@ -0,0 +1,124 @@
+package org.owasp.webgoat.plugin.challenge4;
+
+import com.google.common.collect.Maps;
+import io.jsonwebtoken.*;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.json.MappingJacksonValue;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.Comparator.comparingLong;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
+import static org.owasp.webgoat.plugin.Flag.FLAGS;
+import static org.owasp.webgoat.plugin.SolutionConstants.JWT_PASSWORD;
+
+/**
+ * @author nbaars
+ * @since 4/23/17.
+ */
+@RestController
+@RequestMapping("/votings")
+public class VotesEndpoint {
+
+ private static String validUsers = "TomJerrySylvester";
+
+ private static int totalVotes = 38929;
+ private Map votes = Maps.newHashMap();
+
+ @PostConstruct
+ public void initVotes() {
+ votes.put("Admin lost password", new Vote("Admin lost password",
+ "In this challenge you will need to help the admin and find the password in order to login",
+ "challenge1-small.png", "challenge1.png", 36000, totalVotes));
+ votes.put("Vote for your favourite",
+ new Vote("Vote for your favourite",
+ "In this challenge ...",
+ "challenge5-small.png", "challenge5.png", 30000, totalVotes));
+ votes.put("Get it for free",
+ new Vote("Get it for free",
+ "The objective for this challenge is to buy a Samsung phone for free.",
+ "challenge2-small.png", "challenge2.png", 20000, totalVotes));
+ votes.put("Photo comments",
+ new Vote("Photo comments",
+ "n this challenge you can comment on the photo you will need to find the flag somewhere.",
+ "challenge3-small.png", "challenge3.png", 10000, totalVotes));
+ }
+
+ @GetMapping("/login")
+ public void login(@RequestParam("user") String user, HttpServletResponse response) {
+ if (validUsers.contains(user)) {
+ Map claims = Maps.newHashMap();
+ claims.put("admin", "false");
+ claims.put("user", user);
+ String token = Jwts.builder()
+ .setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10)))
+ .setClaims(claims)
+ .signWith(SignatureAlgorithm.HS512, JWT_PASSWORD)
+ .compact();
+ Cookie cookie = new Cookie("access_token", token);
+ response.addCookie(cookie);
+ response.setStatus(HttpStatus.OK.value());
+ } else {
+ Cookie cookie = new Cookie("access_token", "");
+ response.addCookie(cookie);
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ }
+ }
+
+ @GetMapping
+ public MappingJacksonValue getVotes(@CookieValue(value = "access_token", required = false) String accessToken) {
+ MappingJacksonValue value = new MappingJacksonValue(votes.values().stream().sorted(comparingLong(Vote::getAverage).reversed()).collect(toList()));
+ if (StringUtils.isEmpty(accessToken)) {
+ value.setSerializationView(Views.GuestView.class);
+ } else {
+ try {
+ Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
+ Claims claims = (Claims) jwt.getBody();
+ String user = (String) claims.get("user");
+ boolean isAdmin = Boolean.valueOf((String) claims.get("admin"));
+ if ("Guest".equals(user) || !validUsers.contains(user)) {
+ value.setSerializationView(Views.GuestView.class);
+ } else {
+ ((Collection) value.getValue()).forEach(v -> v.setFlag(FLAGS.get(4)));
+ value.setSerializationView(isAdmin ? Views.AdminView.class : Views.UserView.class);
+ }
+ } catch (JwtException e) {
+ value.setSerializationView(Views.GuestView.class);
+ }
+ }
+ return value;
+ }
+
+ @PostMapping(value = "{title}")
+ @ResponseBody
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ public ResponseEntity> vote(@PathVariable String title, @CookieValue(value = "access_token", required = false) String accessToken) {
+ if (StringUtils.isEmpty(accessToken)) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ } else {
+ try {
+ Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
+ Claims claims = (Claims) jwt.getBody();
+ String user = (String) claims.get("user");
+ if (validUsers.contains(user)) {
+ ofNullable(votes.get(title)).ifPresent(v -> v.incrementNumberOfVotes(totalVotes));
+ return ResponseEntity.accepted().build();
+ } else {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ }
+ } catch (JwtException e) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ }
+ }
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Assignment5.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Assignment5.java
new file mode 100644
index 000000000..0d987e4a8
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Assignment5.java
@@ -0,0 +1,92 @@
+package org.owasp.webgoat.plugin.challenge5.challenge6;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.owasp.webgoat.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.assignments.AssignmentPath;
+import org.owasp.webgoat.assignments.AttackResult;
+import org.owasp.webgoat.plugin.Flag;
+import org.owasp.webgoat.session.DatabaseUtilities;
+import org.owasp.webgoat.session.WebSession;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.sql.*;
+
+import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD_TOM;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+
+/**
+ * @author nbaars
+ * @since 4/8/17.
+ */
+@AssignmentPath("/challenge/5")
+@Slf4j
+public class Assignment5 extends AssignmentEndpoint {
+
+ //Make it more random at runtime (good luck guessing)
+ private static final String USERS_TABLE_NAME = "challenge_users_" + RandomStringUtils.randomAlphabetic(16);
+
+ @Autowired
+ private WebSession webSession;
+
+ @RequestMapping(method = POST)
+ @ResponseBody
+ public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {
+ Connection connection = DatabaseUtilities.getConnection(webSession);
+ checkDatabase(connection);
+
+ PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = '" + username_login + "' and password = '" + password_login + "'");
+ ResultSet resultSet = statement.executeQuery();
+
+ if (resultSet.next()) {
+ return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(5)).build();
+ } else {
+ return failed().feedback("challenge.close").build();
+ }
+ }
+
+ private void checkDatabase(Connection connection) throws SQLException {
+ try {
+ Statement statement = connection.createStatement();
+ statement.execute("select 1 from " + USERS_TABLE_NAME);
+ } catch (SQLException e) {
+ createChallengeTable(connection);
+ }
+ }
+
+ private void createChallengeTable(Connection connection) {
+ Statement statement = null;
+ try {
+ statement = connection.createStatement();
+ String dropTable = "DROP TABLE " + USERS_TABLE_NAME;
+ statement.executeUpdate(dropTable);
+ } catch (SQLException e) {
+ log.info("Delete failed, this does not point to an error table might not have been present...");
+ }
+ log.debug("Challenge 5 - Creating tables for users {}", USERS_TABLE_NAME);
+ try {
+ String createTableStatement = "CREATE TABLE " + USERS_TABLE_NAME
+ + " (" + "userid varchar(250),"
+ + "email varchar(30),"
+ + "password varchar(30)"
+ + ")";
+ statement.executeUpdate(createTableStatement);
+
+ String insertData1 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('larry', 'larry@webgoat.org', 'larryknows')";
+ String insertData2 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('tom', 'tom@webgoat.org', '" + PASSWORD_TOM + "')";
+ String insertData3 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('alice', 'alice@webgoat.org', 'rt*(KJ()LP())$#**')";
+ String insertData4 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('eve', 'eve@webgoat.org', '**********')";
+ statement.executeUpdate(insertData1);
+ statement.executeUpdate(insertData2);
+ statement.executeUpdate(insertData3);
+ statement.executeUpdate(insertData4);
+ } catch (SQLException e) {
+ log.error("Unable create table", e);
+ }
+ }
+
+}
+
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Challenge5.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Challenge5.java
new file mode 100644
index 000000000..140162828
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Challenge5.java
@@ -0,0 +1,39 @@
+package org.owasp.webgoat.plugin.challenge5.challenge6;
+
+import com.google.common.collect.Lists;
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.NewLesson;
+
+import java.util.List;
+
+/**
+ * @author nbaars
+ * @since 3/21/17.
+ */
+public class Challenge5 extends NewLesson {
+
+ @Override
+ public Category getDefaultCategory() {
+ return Category.CHALLENGE;
+ }
+
+ @Override
+ public List getHints() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public Integer getDefaultRanking() {
+ return 10;
+ }
+
+ @Override
+ public String getTitle() {
+ return "challenge5.title";
+ }
+
+ @Override
+ public String getId() {
+ return "Challenge5";
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge6/Assignment6.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge6/Assignment6.java
new file mode 100644
index 000000000..743e5036f
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge6/Assignment6.java
@@ -0,0 +1,137 @@
+package org.owasp.webgoat.plugin.challenge6;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.owasp.webgoat.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.assignments.AssignmentPath;
+import org.owasp.webgoat.assignments.AttackResult;
+import org.owasp.webgoat.plugin.Flag;
+import org.owasp.webgoat.session.DatabaseUtilities;
+import org.owasp.webgoat.session.WebSession;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.sql.*;
+
+import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD_TOM;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+
+/**
+ * @author nbaars
+ * @since 4/8/17.
+ */
+@AssignmentPath("/challenge/6")
+@Slf4j
+public class Assignment6 extends AssignmentEndpoint {
+
+ //Make it more random at runtime (good luck guessing)
+ private static final String USERS_TABLE_NAME = "challenge_users_6" + RandomStringUtils.randomAlphabetic(16);
+
+ @Autowired
+ private WebSession webSession;
+
+ public Assignment6() {
+ log.info("Challenge 6 tablename is: {}", USERS_TABLE_NAME);
+ }
+
+ @PutMapping //assignment path is bounded to class so we use different http method :-)
+ @ResponseBody
+ public AttackResult registerNewUser(@RequestParam String username_reg, @RequestParam String email_reg, @RequestParam String password_reg) throws Exception {
+ AttackResult attackResult = checkArguments(username_reg, email_reg, password_reg);
+
+ if (attackResult == null) {
+ Connection connection = DatabaseUtilities.getConnection(webSession);
+ checkDatabase(connection);
+
+ String checkUserQuery = "select userid from " + USERS_TABLE_NAME + " where userid = '" + username_reg + "'";
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery(checkUserQuery);
+
+ if (resultSet.next()) {
+ attackResult = failed().feedback("user.exists").feedbackArgs(username_reg).build();
+ } else {
+ PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO " + USERS_TABLE_NAME + " VALUES (?, ?, ?)");
+ preparedStatement.setString(1, username_reg);
+ preparedStatement.setString(2, email_reg);
+ preparedStatement.setString(3, password_reg);
+ preparedStatement.execute();
+ attackResult = success().feedback("user.created").feedbackArgs(username_reg).build();
+ }
+ }
+ return attackResult;
+ }
+
+ private AttackResult checkArguments(String username_reg, String email_reg, String password_reg) {
+ if (StringUtils.isEmpty(username_reg) || StringUtils.isEmpty(email_reg) || StringUtils.isEmpty(password_reg)) {
+ return failed().feedback("input.invalid").build();
+ }
+ if (username_reg.length() > 250 || email_reg.length() > 30 || password_reg.length() > 30) {
+ return failed().feedback("input.invalid").build();
+ }
+ return null;
+ }
+
+ @RequestMapping(method = POST)
+ @ResponseBody
+ public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {
+ Connection connection = DatabaseUtilities.getConnection(webSession);
+ checkDatabase(connection);
+
+ PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = ? and password = ?");
+ statement.setString(1, username_login);
+ statement.setString(2, password_login);
+ ResultSet resultSet = statement.executeQuery();
+
+ if (resultSet.next() && "tom".equals(username_login)) {
+ return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(6)).build();
+ } else {
+ return failed().feedback("challenge.close").build();
+ }
+ }
+
+ private void checkDatabase(Connection connection) throws SQLException {
+ try {
+ Statement statement = connection.createStatement();
+ statement.execute("select 1 from " + USERS_TABLE_NAME);
+ } catch (SQLException e) {
+ createChallengeTable(connection);
+ }
+ }
+
+ private void createChallengeTable(Connection connection) {
+ Statement statement = null;
+ try {
+ statement = connection.createStatement();
+ String dropTable = "DROP TABLE " + USERS_TABLE_NAME;
+ statement.executeUpdate(dropTable);
+ } catch (SQLException e) {
+ log.info("Delete failed, this does not point to an error table might not have been present...");
+ }
+ log.debug("Challenge 6 - Creating tables for users {}", USERS_TABLE_NAME);
+ try {
+ String createTableStatement = "CREATE TABLE " + USERS_TABLE_NAME
+ + " (" + "userid varchar(250),"
+ + "email varchar(30),"
+ + "password varchar(30)"
+ + ")";
+ statement.executeUpdate(createTableStatement);
+
+ String insertData1 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('larry', 'larry@webgoat.org', 'larryknows')";
+ String insertData2 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('tom', 'tom@webgoat.org', '" + PASSWORD_TOM + "')";
+ String insertData3 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('alice', 'alice@webgoat.org', 'rt*(KJ()LP())$#**')";
+ String insertData4 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('eve', 'eve@webgoat.org', '**********')";
+ statement.executeUpdate(insertData1);
+ statement.executeUpdate(insertData2);
+ statement.executeUpdate(insertData3);
+ statement.executeUpdate(insertData4);
+ } catch (SQLException e) {
+ log.error("Unable create table", e);
+ }
+ }
+
+}
+
diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge6/Challenge6.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge6/Challenge6.java
new file mode 100644
index 000000000..f7b7b65f1
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge6/Challenge6.java
@@ -0,0 +1,39 @@
+package org.owasp.webgoat.plugin.challenge6;
+
+import com.google.common.collect.Lists;
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.NewLesson;
+
+import java.util.List;
+
+/**
+ * @author nbaars
+ * @since 3/21/17.
+ */
+public class Challenge6 extends NewLesson {
+
+ @Override
+ public Category getDefaultCategory() {
+ return Category.CHALLENGE;
+ }
+
+ @Override
+ public List getHints() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public Integer getDefaultRanking() {
+ return 10;
+ }
+
+ @Override
+ public String getTitle() {
+ return "challenge6.title";
+ }
+
+ @Override
+ public String getId() {
+ return "Challenge6";
+ }
+}
diff --git a/webgoat-lessons/challenge/src/main/resources/css/challenge2.css b/webgoat-lessons/challenge/src/main/resources/css/challenge2.css
new file mode 100644
index 000000000..038ee6d9d
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/css/challenge2.css
@@ -0,0 +1,33 @@
+ul > li{margin-right:25px;font-weight:lighter;cursor:pointer}
+li.active{border-bottom:3px solid silver;}
+
+.item-photo{display:flex;justify-content:center;align-items:center;border-right:1px solid #f6f6f6;}
+.menu-items{list-style-type:none;font-size:11px;display:inline-flex;margin-bottom:0px;margin-top:20px}
+.btn-success{width:100%;border-radius:0px;}
+.section{width:100%;margin-left:-15px;padding:2px;padding-left:15px;padding-right:15px;background:#f8f9f9}
+.title-price{margin-top:30px;margin-bottom:0px;color:black}
+.title-attr{margin-top:0px;margin-bottom:0px;color:black;}
+.btn-minus{cursor:pointer;font-size:7px;display:flex;align-items:center;padding:5px;padding-left:10px;padding-right:10px;border:1px solid gray;border-radius:2px;border-right:0px;}
+.btn-plus{cursor:pointer;font-size:7px;display:flex;align-items:center;padding:5px;padding-left:10px;padding-right:10px;border:1px solid gray;border-radius:2px;border-left:0px;}
+div.section > div {width:100%;display:inline-flex;}
+div.section > div > input {margin:0px;padding-left:5px;font-size:10px;padding-right:5px;max-width:18%;text-align:center;}
+.attr,.attr2{cursor:pointer;margin-right:5px;height:20px;font-size:11px;padding:2px;border:1px solid gray;border-radius:2px;}
+.attr.active,.attr2.active{ border:2px solid orange;}
+
+@media (max-width: 426px) {
+ .container {margin-top:0px !important;}
+ .container > .row{padding:0px !important;}
+ .container > .row > .col-xs-12.col-sm-5{
+ padding-right:0px ;
+ }
+ .container > .row > .col-xs-12.col-sm-9 > div > p{
+ padding-left:0px !important;
+ padding-right:0px !important;
+ }
+ .container > .row > .col-xs-12.col-sm-9 > div > ul{
+ padding-left:10px !important;
+
+ }
+ .section{width:104%;}
+ .menu-items{padding-left:0px;}
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/css/challenge3.css b/webgoat-lessons/challenge/src/main/resources/css/challenge3.css
new file mode 100644
index 000000000..3bc2ca4eb
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/css/challenge3.css
@@ -0,0 +1,75 @@
+/* Component: Posts */
+.post .post-heading {
+ height: 95px;
+ padding: 20px 15px;
+}
+.post .post-heading .avatar {
+ width: 60px;
+ height: 60px;
+ display: block;
+ margin-right: 15px;
+}
+.post .post-heading .meta .title {
+ margin-bottom: 0;
+}
+.post .post-heading .meta .title a {
+ color: black;
+}
+.post .post-heading .meta .title a:hover {
+ color: #aaaaaa;
+}
+.post .post-heading .meta .time {
+ margin-top: 8px;
+ color: #999;
+}
+.post .post-image .image {
+ width:20%;
+ height: 40%;
+}
+.post .post-description {
+ padding: 5px;
+}
+.post .post-footer {
+ border-top: 1px solid #ddd;
+ padding: 15px;
+}
+.post .post-footer .input-group-addon a {
+ color: #454545;
+}
+.post .post-footer .comments-list {
+ padding: 0;
+ margin-top: 20px;
+ list-style-type: none;
+}
+.post .post-footer .comments-list .comment {
+ display: block;
+ width: 100%;
+ margin: 20px 0;
+}
+.post .post-footer .comments-list .comment .avatar {
+ width: 35px;
+ height: 35px;
+}
+.post .post-footer .comments-list .comment .comment-heading {
+ display: block;
+ width: 100%;
+}
+.post .post-footer .comments-list .comment .comment-heading .user {
+ font-size: 14px;
+ font-weight: bold;
+ display: inline;
+ margin-top: 0;
+ margin-right: 10px;
+}
+.post .post-footer .comments-list .comment .comment-heading .time {
+ font-size: 12px;
+ color: #aaa;
+ margin-top: 0;
+ display: inline;
+}
+.post .post-footer .comments-list .comment .comment-body {
+ margin-left: 50px;
+}
+.post .post-footer .comments-list .comment > .comments-list {
+ margin-left: 50px;
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/css/challenge4.css b/webgoat-lessons/challenge/src/main/resources/css/challenge4.css
new file mode 100644
index 000000000..590e2a4b0
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/css/challenge4.css
@@ -0,0 +1,12 @@
+a.list-group-item {
+ height:auto;
+}
+a.list-group-item.active small {
+ color:#fff;
+}
+.stars {
+ margin:20px auto 1px;
+}
+.img-responsive {
+ min-width: 100%;
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/css/challenge6.css b/webgoat-lessons/challenge/src/main/resources/css/challenge6.css
new file mode 100644
index 000000000..6a8635ae6
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/css/challenge6.css
@@ -0,0 +1,96 @@
+.panel-login {
+ border-color: #ccc;
+ -webkit-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2);
+ -moz-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2);
+ box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2);
+}
+.panel-login>.panel-heading {
+ color: #00415d;
+ background-color: #fff;
+ border-color: #fff;
+ text-align:center;
+}
+.panel-login>.panel-heading a{
+ text-decoration: none;
+ color: #666;
+ font-weight: bold;
+ font-size: 15px;
+ -webkit-transition: all 0.1s linear;
+ -moz-transition: all 0.1s linear;
+ transition: all 0.1s linear;
+}
+.panel-login>.panel-heading a.active{
+ color: #029f5b;
+ font-size: 18px;
+}
+.panel-login>.panel-heading hr{
+ margin-top: 10px;
+ margin-bottom: 0px;
+ clear: both;
+ border: 0;
+ height: 1px;
+ background-image: -webkit-linear-gradient(left,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15),rgba(0, 0, 0, 0));
+ background-image: -moz-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0));
+ background-image: -ms-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0));
+ background-image: -o-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0));
+}
+.panel-login input[type="text"],.panel-login input[type="email"],.panel-login input[type="password"] {
+ height: 45px;
+ border: 1px solid #ddd;
+ font-size: 16px;
+ -webkit-transition: all 0.1s linear;
+ -moz-transition: all 0.1s linear;
+ transition: all 0.1s linear;
+}
+.panel-login input:hover,
+.panel-login input:focus {
+ outline:none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ border-color: #ccc;
+}
+.btn-login {
+ background-color: #59B2E0;
+ outline: none;
+ color: #fff;
+ font-size: 14px;
+ height: auto;
+ font-weight: normal;
+ padding: 14px 0;
+ text-transform: uppercase;
+ border-color: #59B2E6;
+}
+.btn-login:hover,
+.btn-login:focus {
+ color: #fff;
+ background-color: #53A3CD;
+ border-color: #53A3CD;
+}
+.forgot-password {
+ text-decoration: underline;
+ color: #888;
+}
+.forgot-password:hover,
+.forgot-password:focus {
+ text-decoration: underline;
+ color: #666;
+}
+
+.btn-register {
+ background-color: #1CB94E;
+ outline: none;
+ color: #fff;
+ font-size: 14px;
+ height: auto;
+ font-weight: normal;
+ padding: 14px 0;
+ text-transform: uppercase;
+ border-color: #1CB94A;
+}
+.btn-register:hover,
+.btn-register:focus {
+ color: #fff;
+ background-color: #1CA347;
+ border-color: #1CA347;
+}
diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge.html
index 00c0e2c2f..7771134ab 100644
--- a/webgoat-lessons/challenge/src/main/resources/html/Challenge.html
+++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge.html
@@ -1,12 +1,9 @@
-
+
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge1.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge1.html
new file mode 100644
index 000000000..18c1d47d9
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge1.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge2.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge2.html
new file mode 100644
index 000000000..777761ed1
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge2.html
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge3.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge3.html
new file mode 100644
index 000000000..62255ab95
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge3.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge4.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge4.html
new file mode 100644
index 000000000..f760beffe
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge4.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vote for your favorite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge5.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge5.html
new file mode 100644
index 000000000..149313275
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge5.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge6.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge6.html
new file mode 100644
index 000000000..f34af864e
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge6.html
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties b/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties
index cbae74dcb..9328177ef 100644
--- a/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties
+++ b/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties
@@ -1 +1,18 @@
-challenge.title=WebGoat Challenge
+challenge0.title=WebGoat Challenge
+challenge1.title=Admin lost password
+challenge2.title=Get it for free
+challenge3.title=Photo comments
+challenge4.title=Voting
+challenge5.title=Without password
+challenge6.title=Creating a new account
+challenge.solved=Congratulations, you solved the challenge. Here is your flag: {0}
+challenge.close=This is not the correct password for tom, please try again.
+
+user.exists=User {0} already exists please try to register with a different username.
+user.created=User {0} created, please proceed to the login page.
+input.invalid=Input for user, email and/or password is empty or too long, please fill in all field and/or limit all fields to 30 characters.
+
+challenge.flag.correct=Congratulations you have solved the challenge!!
+challenge.flag.incorrect=Sorry this is not the correct flag, please try again.
+
+ip.address.unknown=IP address unknown, e-mail has been sent.
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/images/avatar1.png b/webgoat-lessons/challenge/src/main/resources/images/avatar1.png
new file mode 100644
index 000000000..4ea864f90
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/avatar1.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/boss.jpg b/webgoat-lessons/challenge/src/main/resources/images/boss.jpg
new file mode 100644
index 000000000..3fcd1c36e
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/boss.jpg differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/cat.jpg b/webgoat-lessons/challenge/src/main/resources/images/cat.jpg
new file mode 100644
index 000000000..e0e1fb983
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/cat.jpg differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge1-small.png b/webgoat-lessons/challenge/src/main/resources/images/challenge1-small.png
new file mode 100644
index 000000000..a4fbc3470
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge1-small.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge1.png b/webgoat-lessons/challenge/src/main/resources/images/challenge1.png
new file mode 100644
index 000000000..0008ceb5e
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge1.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge2-small.png b/webgoat-lessons/challenge/src/main/resources/images/challenge2-small.png
new file mode 100644
index 000000000..777b5a093
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge2-small.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge2.png b/webgoat-lessons/challenge/src/main/resources/images/challenge2.png
new file mode 100644
index 000000000..d1eadfefe
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge2.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge3-small.png b/webgoat-lessons/challenge/src/main/resources/images/challenge3-small.png
new file mode 100644
index 000000000..daf7f7ebb
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge3-small.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge3.png b/webgoat-lessons/challenge/src/main/resources/images/challenge3.png
new file mode 100644
index 000000000..b271d4ea1
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge3.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge4-small.png b/webgoat-lessons/challenge/src/main/resources/images/challenge4-small.png
new file mode 100644
index 000000000..b9ddaa7e7
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge4-small.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge4.png b/webgoat-lessons/challenge/src/main/resources/images/challenge4.png
new file mode 100644
index 000000000..cb5301ac9
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge4.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge5-small.png b/webgoat-lessons/challenge/src/main/resources/images/challenge5-small.png
new file mode 100644
index 000000000..1aa84ab4c
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge5-small.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge5.png b/webgoat-lessons/challenge/src/main/resources/images/challenge5.png
new file mode 100644
index 000000000..e5d9a8108
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/challenge5.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/samsung-black.jpg b/webgoat-lessons/challenge/src/main/resources/images/samsung-black.jpg
new file mode 100644
index 000000000..7b0c1f809
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/samsung-black.jpg differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/samsung-grey.jpg b/webgoat-lessons/challenge/src/main/resources/images/samsung-grey.jpg
new file mode 100644
index 000000000..2dd9ea557
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/samsung-grey.jpg differ
diff --git a/webgoat-lessons/challenge/src/main/resources/images/webgoat2.png b/webgoat-lessons/challenge/src/main/resources/images/webgoat2.png
new file mode 100644
index 000000000..19b3f90f4
Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/webgoat2.png differ
diff --git a/webgoat-lessons/challenge/src/main/resources/js/bootstrap.min.js b/webgoat-lessons/challenge/src/main/resources/js/bootstrap.min.js
new file mode 100644
index 000000000..b04a0e82f
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap v3.1.1 (http://getbootstrap.com)
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('
').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown",h),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=" li:not(.divider):visible a",i=f.find("[role=menu]"+h+", [role=listbox]"+h);if(i.length){var j=i.index(i.filter(":focus"));38==b.keyCode&&j>0&&j--,40==b.keyCode&&j ').appendTo(document.body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());c.is("a")&&b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this,d=this.tip();this.setContent(),this.options.animation&&d.addClass("fade");var e="function"==typeof this.options.placement?this.options.placement.call(this,d[0],this.$element[0]):this.options.placement,f=/\s?auto?\s?/i,g=f.test(e);g&&(e=e.replace(f,"")||"top"),d.detach().css({top:0,left:0,display:"block"}).addClass(e),this.options.container?d.appendTo(this.options.container):d.insertAfter(this.$element);var h=this.getPosition(),i=d[0].offsetWidth,j=d[0].offsetHeight;if(g){var k=this.$element.parent(),l=e,m=document.documentElement.scrollTop||document.body.scrollTop,n="body"==this.options.container?window.innerWidth:k.outerWidth(),o="body"==this.options.container?window.innerHeight:k.outerHeight(),p="body"==this.options.container?0:k.offset().left;e="bottom"==e&&h.top+h.height+j-m>o?"top":"top"==e&&h.top-m-j<0?"bottom":"right"==e&&h.right+i>n?"left":"left"==e&&h.left-i
'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery);
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge2.js b/webgoat-lessons/challenge/src/main/resources/js/challenge2.js
new file mode 100644
index 000000000..78df37939
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/js/challenge2.js
@@ -0,0 +1,64 @@
+$(document).ready(function () {
+ //-- Click on detail
+ $("ul.menu-items > li").on("click", function () {
+ $("ul.menu-items > li").removeClass("active");
+ $(this).addClass("active");
+ })
+
+ $(".attr,.attr2").on("click", function () {
+ var clase = $(this).attr("class");
+
+ $("." + clase).removeClass("active");
+ $(this).addClass("active");
+ })
+
+ //-- Click on QUANTITY
+ $(".btn-minus").on("click", function () {
+ var now = $(".quantity").val();
+ if ($.isNumeric(now)) {
+ if (parseInt(now) - 1 > 0) {
+ now--;
+ }
+ $(".quantity").val(now);
+ $('#price').text(now * 899);
+ } else {
+ $(".quantity").val("1");
+ $('#price').text(899);
+ }
+ calculate();
+ })
+ $(".btn-plus").on("click", function () {
+ var now = $(".quantity").val();
+ if ($.isNumeric(now)) {
+ $(".quantity").val(parseInt(now) + 1);
+ } else {
+ $(".quantity").val("1");
+ }
+ calculate();
+ })
+ $(".checkoutCode").on("blur", function () {
+ var checkoutCode = $(".checkoutCode").val();
+ $.get("challenge-store/coupons/" + checkoutCode, function (result, status) {
+ var discount = result.discount;
+ if (discount > 0) {
+ $('#discount').text(discount);
+ calculate();
+ } else {
+ $('#discount').text(0);
+ calculate();
+ }
+ });
+ })
+
+ function calculate() {
+ var d = $('#discount').text();
+ var price = $('#price').val();
+ var quantity = parseInt($(".quantity").val());
+ if (d > 0) {
+ $('#price').text((quantity * (899 - (899 * d / 100))).toFixed(2));
+
+ } else {
+ $('#price').text(quantity * 899);
+ }
+ }
+})
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge3.js b/webgoat-lessons/challenge/src/main/resources/js/challenge3.js
new file mode 100644
index 000000000..fb902e050
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/js/challenge3.js
@@ -0,0 +1,45 @@
+$(document).ready(function () {
+ $("#postComment").on("click", function () {
+ var commentInput = $("#commentInput").val();
+ $.ajax({
+ type: 'POST',
+ url: 'challenge/3',
+ data: JSON.stringify({text: commentInput}),
+ contentType: "application/json",
+ dataType: 'json'
+ }).then(
+ function () {
+ getChallenges();
+ $("#commentInput").val('');
+ }
+ )
+ })
+
+ var html = '';
+
+ getChallenges();
+
+ function getChallenges() {
+ $("#list").empty();
+ $.get("challenge/3", function (result, status) {
+ for (var i = 0; i < result.length; i++) {
+ var comment = html.replace('USER', result[i].user);
+ comment = comment.replace('DATETIME', result[i].dateTime);
+ comment = comment.replace('COMMENT', result[i].text);
+ $("#list").append(comment);
+ }
+
+ });
+ }
+})
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge4.js b/webgoat-lessons/challenge/src/main/resources/js/challenge4.js
new file mode 100644
index 000000000..5c9d6a38d
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/js/challenge4.js
@@ -0,0 +1,84 @@
+$(document).ready(function () {
+ login('Guest');
+})
+
+function login(user) {
+ $("#name").text(user);
+ $.ajax({
+ url: "votings/login?user=" + user,
+ complete: function (result, status) {
+ getVotings();
+ }
+ });
+}
+
+var html = '' +
+ ' ' +
+ '' +
+ '
TITLE ' +
+ '
INFORMATION
' +
+ '
' +
+ '' +
+ '
NO_VOTES' +
+ ' votes ' +
+ ' ' +
+ '
Vote Now! ' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' +
+ '
Average AVERAGE / 4
' +
+ '
' +
+ '
' +
+ ' ';
+
+function getVotings() {
+ $("#votesList").empty();
+ $.get("votings/", function (result, status) {
+ for (var i = 0; i < result.length; i++) {
+ var voteTemplate = html.replace('IMAGE_SMALL', result[i].imageSmall);
+ if (i === 0) {
+ voteTemplate = voteTemplate.replace('ACTIVE', 'active');
+ voteTemplate = voteTemplate.replace('BUTTON', 'btn-default');
+ } else {
+ voteTemplate = voteTemplate.replace('ACTIVE', '');
+ voteTemplate = voteTemplate.replace('BUTTON', 'btn-primary');
+ }
+ voteTemplate = voteTemplate.replace(/TITLE/g, result[i].title);
+ voteTemplate = voteTemplate.replace('INFORMATION', result[i].information || '');
+ voteTemplate = voteTemplate.replace('NO_VOTES', result[i].numberOfVotes || '');
+ voteTemplate = voteTemplate.replace('AVERAGE', result[i].average || '');
+
+ var hidden = (result[i].numberOfVotes === undefined ? 'hidden' : '');
+ voteTemplate = voteTemplate.replace(/HIDDEN_VIEW_VOTES/g, hidden);
+ hidden = (result[i].average === undefined ? 'hidden' : '');
+ voteTemplate = voteTemplate.replace(/HIDDEN_VIEW_RATING/g, hidden);
+
+ $("#votesList").append(voteTemplate);
+ }
+ })
+}
+
+function vote(title) {
+ var user = $("#name").text();
+ if (user === 'Guest') {
+ alert("As a guest you are not allowed to vote, please login first.")
+ } else {
+ $.ajax({
+ type: 'POST',
+ url: 'votings/' + title
+ }).then(
+ function () {
+ getVotings();
+ }
+ )
+ }
+}
+
+
diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge6.js b/webgoat-lessons/challenge/src/main/resources/js/challenge6.js
new file mode 100644
index 000000000..9107e1176
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/js/challenge6.js
@@ -0,0 +1,18 @@
+$(function() {
+
+ $('#login-form-link').click(function(e) {
+ $("#login-form").delay(100).fadeIn(100);
+ $("#register-form").fadeOut(100);
+ $('#register-form-link').removeClass('active');
+ $(this).addClass('active');
+ e.preventDefault();
+ });
+ $('#register-form-link').click(function(e) {
+ $("#register-form").delay(100).fadeIn(100);
+ $("#login-form").fadeOut(100);
+ $('#login-form-link').removeClass('active');
+ $(this).addClass('active');
+ e.preventDefault();
+ });
+
+});
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_1.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_1.adoc
new file mode 100644
index 000000000..36e25fe5a
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_1.adoc
@@ -0,0 +1 @@
+The admin forgot where the password is stashed, can you help?
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_2.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_2.adoc
new file mode 100644
index 000000000..5a1e072f2
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_2.adoc
@@ -0,0 +1 @@
+No need to pay if you know the code ...
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_3.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_3.adoc
new file mode 100644
index 000000000..396cbfa0f
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_3.adoc
@@ -0,0 +1 @@
+Changing language can help you find the 'secret' file
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_4.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_4.adoc
new file mode 100644
index 000000000..883d4be45
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_4.adoc
@@ -0,0 +1 @@
+Try to change to a different user, maybe you can find the flag?
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_5.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_5.adoc
new file mode 100644
index 000000000..0102ca98f
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_5.adoc
@@ -0,0 +1 @@
+Can you login as Larry?
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_6.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_6.adoc
new file mode 100644
index 000000000..1e1e61ac2
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_6.adoc
@@ -0,0 +1 @@
+Can you login as Tom? It may be a little harder than it was for Larry.
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_content1.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_content1.adoc
deleted file mode 100644
index 987f45684..000000000
--- a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_content1.adoc
+++ /dev/null
@@ -1 +0,0 @@
-This is the challenge
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_introduction.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_introduction.adoc
new file mode 100644
index 000000000..03774d6f7
--- /dev/null
+++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_introduction.adoc
@@ -0,0 +1,23 @@
+=== Welcome to the WebGoat challenge (CTF)
+
+:hardbreaks:
+In this CTF you will need to solve a couple of challenges, each challenge will give you a flag which you will
+need to post in order to gain points.
+
+Flags have the following format: `a7179f89-906b-4fec-9d99-f15b796e7208`
+
+==== Rules
+
+- Do not try to hack the competition infrastructure. If you happen to find a bug or vulnerability please send us
+an e-mail.
+
+- Play fair, do not try sabotage other competing teams, or in any way hindering the progress of another team.
+
+- Brute forcing of challenges / flags is not allowed.
+
+:hardbreaks:
+*Have fun!!*
+Team WebGoat
+
+
+image::images/boss.jpg[]
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/plugin/Challenge/html/Challenge.html b/webgoat-lessons/challenge/src/main/resources/plugin/Challenge/html/Challenge.html
deleted file mode 100644
index 00c0e2c2f..000000000
--- a/webgoat-lessons/challenge/src/main/resources/plugin/Challenge/html/Challenge.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/plugin/Challenge/lessonPlans/en/Challenge_content1.adoc b/webgoat-lessons/challenge/src/main/resources/plugin/Challenge/lessonPlans/en/Challenge_content1.adoc
deleted file mode 100644
index 987f45684..000000000
--- a/webgoat-lessons/challenge/src/main/resources/plugin/Challenge/lessonPlans/en/Challenge_content1.adoc
+++ /dev/null
@@ -1 +0,0 @@
-This is the challenge
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/main/resources/plugin/i18n/WebGoatLabels.properties b/webgoat-lessons/challenge/src/main/resources/plugin/i18n/WebGoatLabels.properties
deleted file mode 100644
index cbae74dcb..000000000
--- a/webgoat-lessons/challenge/src/main/resources/plugin/i18n/WebGoatLabels.properties
+++ /dev/null
@@ -1 +0,0 @@
-challenge.title=WebGoat Challenge
diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge1/Assignment1Test.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge1/Assignment1Test.java
new file mode 100644
index 000000000..b496bc4e5
--- /dev/null
+++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge1/Assignment1Test.java
@@ -0,0 +1,76 @@
+package org.owasp.webgoat.plugin.challenge1;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.owasp.webgoat.assignments.AssignmentEndpointTest;
+import org.owasp.webgoat.plugin.Flag;
+import org.owasp.webgoat.plugin.SolutionConstants;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import java.net.InetAddress;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+
+/**
+ * @author nbaars
+ * @since 5/2/17.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class Assignment1Test extends AssignmentEndpointTest {
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ Assignment1 assignment1 = new Assignment1();
+ init(assignment1);
+ new Flag().initFlags();
+ this.mockMvc = standaloneSetup(assignment1).build();
+ }
+
+ @Test
+ public void success() throws Exception {
+ InetAddress addr = InetAddress.getLocalHost();
+ String host = addr.getHostAddress();
+ mockMvc.perform(MockMvcRequestBuilders.post("/challenge/1")
+ .header("X-Forwarded-For", host)
+ .param("username", "admin")
+ .param("password", SolutionConstants.PASSWORD))
+ .andExpect(jsonPath("$.feedback", CoreMatchers.containsString("flag: " + Flag.FLAGS.get(1))))
+ .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
+ }
+
+ @Test
+ public void wrongPassword() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.post("/challenge/1")
+ .param("username", "admin")
+ .param("password", "wrong"))
+ .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))))
+ .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+ }
+
+// @Test
+// public void correctPasswordXForwardHeaderMissing() throws Exception {
+// mockMvc.perform(MockMvcRequestBuilders.post("/challenge/1")
+// .param("username", "admin")
+// .param("password", SolutionConstants.PASSWORD))
+// .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("ip.address.unknown"))))
+// .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+// }
+
+// @Test
+// public void correctPasswordXForwardHeaderWrong() throws Exception {
+// mockMvc.perform(MockMvcRequestBuilders.post("/challenge/1")
+// .header("X-Forwarded-For", "127.0.1.2")
+// .param("username", "admin")
+// .param("password", SolutionConstants.PASSWORD))
+// .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("ip.address.unknown"))))
+// .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+// }
+
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/Assignment2Test.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/Assignment2Test.java
new file mode 100644
index 000000000..edf70df28
--- /dev/null
+++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/Assignment2Test.java
@@ -0,0 +1,49 @@
+package org.owasp.webgoat.plugin.challenge2;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.owasp.webgoat.assignments.AssignmentEndpointTest;
+import org.owasp.webgoat.plugin.Flag;
+import org.owasp.webgoat.plugin.SolutionConstants;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+
+/**
+ * @author nbaars
+ * @since 5/2/17.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class Assignment2Test extends AssignmentEndpointTest {
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ Assignment2 assignment2 = new Assignment2();
+ init(assignment2);
+ new Flag().initFlags();
+ this.mockMvc = standaloneSetup(assignment2).build();
+ }
+
+ @Test
+ public void success() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.post("/challenge/2")
+ .param("checkoutCode", SolutionConstants.SUPER_COUPON_CODE))
+ .andExpect(jsonPath("$.feedback", CoreMatchers.containsString("flag: " + Flag.FLAGS.get(2))))
+ .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
+ }
+
+ @Test
+ public void wrongCouponCode() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.post("/challenge/2")
+ .param("checkoutCode", "test"))
+ .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))))
+ .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+ }
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java
new file mode 100644
index 000000000..87710a6b7
--- /dev/null
+++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java
@@ -0,0 +1,60 @@
+package org.owasp.webgoat.plugin.challenge2;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+
+import static org.hamcrest.Matchers.is;
+import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+
+/**
+ * @author nbaars
+ * @since 5/2/17.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ShopEndpointTest {
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ ShopEndpoint shopEndpoint = new ShopEndpoint();
+ this.mockMvc = standaloneSetup(shopEndpoint).build();
+ }
+
+ @Test
+ public void getSuperCoupon() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/" + SUPER_COUPON_CODE))
+ .andExpect(jsonPath("$.code", CoreMatchers.is(SUPER_COUPON_CODE)))
+ .andExpect(jsonPath("$.discount", CoreMatchers.is(100)));
+ }
+
+ @Test
+ public void getCoupon() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/webgoat"))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(jsonPath("$.code", CoreMatchers.is("webgoat")))
+ .andExpect(jsonPath("$.discount", CoreMatchers.is(25)));
+ }
+
+ @Test
+ public void askForUnknownCouponCode() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/does-not-exists"))
+ .andExpect(jsonPath("$.code", CoreMatchers.is("no")))
+ .andExpect(jsonPath("$.discount", CoreMatchers.is(0)));
+ }
+
+ @Test
+ public void fetchAllTheCouponsShouldContainGetItForFree() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/"))
+ .andExpect(jsonPath("$.codes[3].code", is("get_it_for_free")));
+ }
+
+}
\ No newline at end of file
diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge4/VotesEndpointTest.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge4/VotesEndpointTest.java
new file mode 100644
index 000000000..b27519506
--- /dev/null
+++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge4/VotesEndpointTest.java
@@ -0,0 +1,163 @@
+package org.owasp.webgoat.plugin.challenge4;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.owasp.webgoat.plugin.Flag;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+
+import javax.servlet.http.Cookie;
+
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+
+/**
+ * @author nbaars
+ * @since 5/2/17.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class VotesEndpointTest {
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ VotesEndpoint votesEndpoint = new VotesEndpoint();
+ votesEndpoint.initVotes();
+ new Flag().initFlags();
+ this.mockMvc = standaloneSetup(votesEndpoint).build();
+ }
+
+ @Test
+ public void loginWithUnknownUser() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/login")
+ .param("user", "uknown"))
+ .andExpect(unauthenticated());
+ }
+
+ @Test
+ public void loginWithTomShouldGiveJwtToken() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/login")
+ .param("user", "Tom"))
+ .andExpect(status().isOk()).andExpect(cookie().exists("access_token"));
+ }
+
+ @Test
+ public void loginWithGuestShouldNotGiveJwtToken() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/login")
+ .param("user", "Guest"))
+ .andExpect(unauthenticated()).andExpect(cookie().value("access_token", ""));
+ }
+
+ @Test
+ public void userShouldSeeMore() throws Exception {
+ MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login")
+ .param("user", "Tom"))
+ .andExpect(status().isOk()).andExpect(cookie().exists("access_token")).andReturn();
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings")
+ .cookie(mvcResult.getResponse().getCookie("access_token")))
+ .andExpect(jsonPath("$.[*].numberOfVotes").exists());
+ }
+
+ @Test
+ public void guestShouldNotSeeNumberOfVotes() throws Exception {
+ MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login")
+ .param("user", "Guest"))
+ .andExpect(unauthenticated()).andExpect(cookie().exists("access_token")).andReturn();
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings")
+ .cookie(mvcResult.getResponse().getCookie("access_token")))
+ .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist());
+ }
+
+ @Test
+ public void adminShouldSeeFlags() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings")
+ .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJhZG1pbiI6InRydWUiLCJ1c2VyIjoiSmVycnkifQ.")))
+ .andExpect(jsonPath("$.[*].flag").isNotEmpty());
+ }
+
+ @Test
+ public void votingIsNotAllowedAsGuest() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.post("/votings/Get it for free"))
+ .andExpect(unauthenticated());
+ }
+
+ @Test
+ public void normalUserShouldBeAbleToVote() throws Exception {
+ MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login")
+ .param("user", "Tom"))
+ .andExpect(status().isOk()).andExpect(cookie().exists("access_token")).andReturn();
+ mockMvc.perform(MockMvcRequestBuilders.post("/votings/Get it for free")
+ .cookie(mvcResult.getResponse().getCookie("access_token")));
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
+ .cookie(mvcResult.getResponse().getCookie("access_token")))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(jsonPath("$..[?(@.title == 'Get it for free')].numberOfVotes", CoreMatchers.hasItem(20001)));
+ }
+
+ @Test
+ public void votingForUnknownLessonShouldNotCrash() throws Exception {
+ MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login")
+ .param("user", "Tom"))
+ .andExpect(status().isOk()).andExpect(cookie().exists("access_token")).andReturn();
+ mockMvc.perform(MockMvcRequestBuilders.post("/votings/UKNOWN_VOTE")
+ .cookie(mvcResult.getResponse().getCookie("access_token"))).andExpect(status().isAccepted());
+ }
+
+ @Test
+ public void votingWithInvalidToken() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.post("/votings/UKNOWN_VOTE")
+ .cookie(new Cookie("access_token", "abc"))).andExpect(unauthenticated());
+ }
+
+ @Test
+ public void gettingVotesWithInvalidToken() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
+ .cookie(new Cookie("access_token", "abc"))).andExpect(unauthenticated());
+ }
+
+ @Test
+ public void gettingVotesWithUnknownUserInToken() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
+ .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJhZG1pbiI6InRydWUiLCJ1c2VyIjoiVW5rbm93biJ9.")))
+ .andExpect(unauthenticated())
+ .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist());
+ }
+
+ @Test
+ public void gettingVotesForUnknownShouldWork() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
+ .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiVW5rbm93biJ9.")))
+ .andExpect(unauthenticated())
+ .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist());
+ }
+
+ @Test
+ public void gettingVotesForKnownWithoutAdminFieldShouldWork() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
+ .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiVG9tIn0.")))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.[*].numberOfVotes").exists());
+ }
+
+ @Test
+ public void gettingVotesWithEmptyToken() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
+ .cookie(new Cookie("access_token", "")))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist());
+ }
+
+ @Test
+ public void votingAsUnknownUserShouldNotBeAllowed() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.post("/votings/Get it for free")
+ .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiVW5rbm93biJ9.")))
+ .andExpect(unauthenticated());
+ }
+}
\ No newline at end of file
diff --git a/webgoat-lessons/http-proxies/src/main/resources/html/HttpProxies.html b/webgoat-lessons/http-proxies/src/main/resources/html/HttpProxies.html
index efdfb9e61..595c7f960 100644
--- a/webgoat-lessons/http-proxies/src/main/resources/html/HttpProxies.html
+++ b/webgoat-lessons/http-proxies/src/main/resources/html/HttpProxies.html
@@ -32,7 +32,7 @@
USER
' + + 'DATETIME
' + + 'COMMENT
' + + '