Remove WebGoat session object (#1929)

* refactor: modernize code

* refactor: move to Tomcat

* chore: bump to Spring Boot 3.3.3

* refactor: use Testcontainers to run integration tests

* refactor: lesson/assignment progress

* chore: format code

* refactor: first step into removing base class for assignment

Always been a bit of an ugly construction, as none of the dependencies are clear. The constructors are hidden due to autowiring the base class. This PR removes two of the fields.

As a bonus we now wire the authentication principal directly in the controllers.

* refactor: use authentication principal directly.

* refactor: pass lesson to the endpoints

No more need to get the current lesson set in a session. The lesson is now passed to the endpoints.

* fix: Testcontainers cannot run on Windows host in Github actions.

Since we have Windows specific paths let's run it standalone for now. We need to run these tests on Docker as well (for now disabled)
This commit is contained in:
Nanne Baars
2024-10-26 10:54:21 +02:00
committed by GitHub
parent cb7c508046
commit ab068901f1
156 changed files with 1076 additions and 1235 deletions

View File

@ -10,26 +10,24 @@ import java.util.Collection;
import java.util.List;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Hint;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.session.Course;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* HintService class.
*
* @author rlawson
* @version $Id: $Id
*/
@RestController
public class HintService {
public static final String URL_HINTS_MVC = "/service/hint.mvc";
private final WebSession webSession;
private final List<Hint> allHints;
public HintService(WebSession webSession) {
this.webSession = webSession;
public HintService(Course course) {
this.allHints =
course.getLessons().stream()
.flatMap(lesson -> lesson.getAssignments().stream())
.map(this::createHint)
.flatMap(Collection::stream)
.toList();
}
/**
@ -40,15 +38,7 @@ public class HintService {
@GetMapping(path = URL_HINTS_MVC, produces = "application/json")
@ResponseBody
public List<Hint> getHints() {
Lesson l = webSession.getCurrentLesson();
return createAssignmentHints(l);
}
private List<Hint> createAssignmentHints(Lesson l) {
if (l != null) {
return l.getAssignments().stream().map(this::createHint).flatMap(Collection::stream).toList();
}
return List.of();
return allHints;
}
private List<Hint> createHint(Assignment a) {

View File

@ -1,33 +1,24 @@
package org.owasp.webgoat.container.service;
import lombok.AllArgsConstructor;
import org.owasp.webgoat.container.lessons.Lesson;
import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.lessons.LessonInfoModel;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.owasp.webgoat.container.lessons.LessonName;
import org.owasp.webgoat.container.session.Course;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* LessonInfoService class.
*
* @author dm
* @version $Id: $Id
*/
@RestController
@AllArgsConstructor
@RequiredArgsConstructor
public class LessonInfoService {
private final WebSession webSession;
private final Course course;
/**
* getLessonInfo.
*
* @return a {@link LessonInfoModel} object.
*/
@RequestMapping(path = "/service/lessoninfo.mvc", produces = "application/json")
public @ResponseBody LessonInfoModel getLessonInfo() {
Lesson lesson = webSession.getCurrentLesson();
@GetMapping(path = "/service/lessoninfo.mvc/{lesson}")
public @ResponseBody LessonInfoModel getLessonInfo(
@PathVariable("lesson") LessonName lessonName) {
var lesson = course.getLessonByName(lessonName);
return new LessonInfoModel(lesson.getTitle(), false, false, false);
}
}

View File

@ -32,13 +32,13 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Category;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.lessons.LessonMenuItem;
import org.owasp.webgoat.container.lessons.LessonMenuItemType;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.LessonProgress;
import org.owasp.webgoat.container.users.UserProgress;
import org.owasp.webgoat.container.users.UserProgressRepository;
@ -59,7 +59,6 @@ public class LessonMenuService {
public static final String URL_LESSONMENU_MVC = "/service/lessonmenu.mvc";
private final Course course;
private final WebSession webSession;
private UserProgressRepository userTrackerRepository;
@Value("#{'${exclude.categories}'.split(',')}")
@ -74,10 +73,13 @@ public class LessonMenuService {
* @return a {@link java.util.List} object.
*/
@RequestMapping(path = URL_LESSONMENU_MVC, produces = "application/json")
public @ResponseBody List<LessonMenuItem> showLeftNav() {
public @ResponseBody List<LessonMenuItem> showLeftNav(@CurrentUsername String username) {
// TODO: this looks way too complicated. Either we save it incorrectly or we miss something to
// easily find out
// if a lesson if solved or not.
List<LessonMenuItem> menu = new ArrayList<>();
List<Category> categories = course.getCategories();
UserProgress userTracker = userTrackerRepository.findByUser(webSession.getUserName());
UserProgress userTracker = userTrackerRepository.findByUser(username);
for (Category category : categories) {
if (excludeCategories.contains(category.name())) {
@ -102,7 +104,7 @@ public class LessonMenuService {
lessonItem.setComplete(lessonSolved);
categoryItem.addChild(lessonItem);
}
categoryItem.getChildren().sort((o1, o2) -> o1.getRanking() - o2.getRanking());
categoryItem.getChildren().sort(Comparator.comparingInt(LessonMenuItem::getRanking));
menu.add(categoryItem);
}
return menu;

View File

@ -4,11 +4,15 @@ import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.lessons.LessonName;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
/**
@ -20,8 +24,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequiredArgsConstructor
public class LessonProgressService {
private final UserProgressRepository userTrackerRepository;
private final WebSession webSession;
private final UserProgressRepository userProgressRepository;
private final Course course;
/**
* Endpoint for fetching the complete lesson overview which informs the user about whether all the
@ -29,19 +33,19 @@ public class LessonProgressService {
*
* @return list of assignments
*/
@RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json")
@GetMapping(value = "/service/lessonoverview.mvc/{lesson}")
@ResponseBody
public List<LessonOverview> lessonOverview() {
var userTracker = userTrackerRepository.findByUser(webSession.getUserName());
var currentLesson = webSession.getCurrentLesson();
public List<LessonOverview> lessonOverview(
@PathVariable("lesson") LessonName lessonName, @CurrentUsername String username) {
var userProgress = userProgressRepository.findByUser(username);
var lesson = course.getLessonByName(lessonName);
if (currentLesson != null) {
var lessonTracker = userTracker.getLessonProgress(currentLesson);
return lessonTracker.getLessonOverview().entrySet().stream()
.map(entry -> new LessonOverview(entry.getKey(), entry.getValue()))
.toList();
}
return List.of();
Assert.isTrue(lesson != null, "Lesson not found: " + lessonName);
var lessonProgress = userProgress.getLessonProgress(lesson);
return lessonProgress.getLessonOverview().entrySet().stream()
.map(entry -> new LessonOverview(entry.getKey(), entry.getValue()))
.toList();
}
@AllArgsConstructor

View File

@ -1,34 +0,0 @@
package org.owasp.webgoat.container.service;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* LessonTitleService class.
*
* @author dm
* @version $Id: $Id
*/
@Controller
public class LessonTitleService {
private final WebSession webSession;
public LessonTitleService(final WebSession webSession) {
this.webSession = webSession;
}
/**
* Returns the title for the current attack
*
* @return a {@link java.lang.String} object.
*/
@RequestMapping(path = "/service/lessontitle.mvc", produces = "application/html")
public @ResponseBody String showPlan() {
Lesson lesson = webSession.getCurrentLesson();
return lesson != null ? lesson.getTitle() : "";
}
}

View File

@ -29,14 +29,17 @@ import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.owasp.webgoat.container.lessons.Initializeable;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.CurrentUser;
import org.owasp.webgoat.container.lessons.Initializable;
import org.owasp.webgoat.container.lessons.LessonName;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.UserProgress;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
@Controller
@ -44,25 +47,25 @@ import org.springframework.web.bind.annotation.ResponseStatus;
@Slf4j
public class RestartLessonService {
private final WebSession webSession;
private final Course course;
private final UserProgressRepository userTrackerRepository;
private final Function<String, Flyway> flywayLessons;
private final List<Initializeable> lessonsToInitialize;
private final List<Initializable> lessonsToInitialize;
@RequestMapping(path = "/service/restartlesson.mvc", produces = "text/text")
@GetMapping(path = "/service/restartlesson.mvc/{lesson}")
@ResponseStatus(value = HttpStatus.OK)
public void restartLesson() {
Lesson al = webSession.getCurrentLesson();
log.debug("Restarting lesson: " + al);
public void restartLesson(
@PathVariable("lesson") LessonName lessonName, @CurrentUser WebGoatUser user) {
var lesson = course.getLessonByName(lessonName);
UserProgress userTracker = userTrackerRepository.findByUser(webSession.getUserName());
userTracker.reset(al);
UserProgress userTracker = userTrackerRepository.findByUser(user.getUsername());
userTracker.reset(lesson);
userTrackerRepository.save(userTracker);
var flyway = flywayLessons.apply(webSession.getUserName());
var flyway = flywayLessons.apply(user.getUsername());
flyway.clean();
flyway.migrate();
lessonsToInitialize.forEach(i -> i.initialize(webSession.getUser()));
lessonsToInitialize.forEach(i -> i.initialize(user));
}
}

View File

@ -7,8 +7,9 @@
package org.owasp.webgoat.container.service;
import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.CurrentUser;
import org.owasp.webgoat.container.i18n.Messages;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@ -17,17 +18,17 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequiredArgsConstructor
public class SessionService {
private final WebSession webSession;
private final RestartLessonService restartLessonService;
private final Messages messages;
@RequestMapping(path = "/service/enable-security.mvc", produces = "application/json")
@ResponseBody
public String applySecurity() {
webSession.toggleSecurity();
restartLessonService.restartLesson();
public String applySecurity(@CurrentUser WebGoatUser user) {
// webSession.toggleSecurity();
// restartLessonService.restartLesson(user);
var msg = webSession.isSecurityEnabled() ? "security.enabled" : "security.disabled";
return messages.getMessage(msg);
// TODO disabled for now
// var msg = webSession.isSecurityEnabled() ? "security.enabled" : "security.disabled";
return messages.getMessage("Not working...");
}
}