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:
@ -51,6 +51,7 @@ public class Assignment {
|
||||
|
||||
private String name;
|
||||
private String path;
|
||||
private boolean solved = false;
|
||||
|
||||
@Transient private List<String> hints;
|
||||
|
||||
@ -74,4 +75,8 @@ public class Assignment {
|
||||
this.path = path;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
public void solved() {
|
||||
this.solved = true;
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,9 @@
|
||||
|
||||
package org.owasp.webgoat.container.lessons;
|
||||
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.container.assignments.AssignmentHints;
|
||||
@ -35,45 +32,91 @@ import org.owasp.webgoat.container.assignments.AttackResult;
|
||||
import org.owasp.webgoat.container.session.Course;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class CourseConfiguration {
|
||||
|
||||
private final List<Lesson> lessons;
|
||||
private final List<AssignmentEndpoint> assignments;
|
||||
private final Map<String, List<AssignmentEndpoint>> assignmentsByPackage;
|
||||
|
||||
public CourseConfiguration(List<Lesson> lessons, List<AssignmentEndpoint> assignments) {
|
||||
this.lessons = lessons;
|
||||
this.assignments = assignments;
|
||||
assignmentsByPackage =
|
||||
this.assignments.stream().collect(groupingBy(a -> a.getClass().getPackageName()));
|
||||
}
|
||||
|
||||
private void attachToLessonInParentPackage(
|
||||
AssignmentEndpoint assignmentEndpoint, String packageName) {
|
||||
if (packageName.equals("org.owasp.webgoat.lessons")) {
|
||||
throw new IllegalStateException(
|
||||
"No lesson found for assignment: '%s'"
|
||||
.formatted(assignmentEndpoint.getClass().getSimpleName()));
|
||||
}
|
||||
lessons.stream()
|
||||
.filter(l -> l.getClass().getPackageName().equals(packageName))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
l -> l.addAssignment(toAssignment(assignmentEndpoint)),
|
||||
() ->
|
||||
attachToLessonInParentPackage(
|
||||
assignmentEndpoint, packageName.substring(0, packageName.lastIndexOf("."))));
|
||||
}
|
||||
|
||||
/**
|
||||
* For each assignment endpoint, find the lesson in the same package or if not found, find the
|
||||
* lesson in the parent package
|
||||
*/
|
||||
private void attachToLesson(AssignmentEndpoint assignmentEndpoint) {
|
||||
lessons.stream()
|
||||
.filter(
|
||||
l ->
|
||||
l.getClass()
|
||||
.getPackageName()
|
||||
.equals(assignmentEndpoint.getClass().getPackageName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
l -> l.addAssignment(toAssignment(assignmentEndpoint)),
|
||||
() -> {
|
||||
var assignmentPackageName = assignmentEndpoint.getClass().getPackageName();
|
||||
attachToLessonInParentPackage(
|
||||
assignmentEndpoint,
|
||||
assignmentPackageName.substring(0, assignmentPackageName.lastIndexOf(".")));
|
||||
});
|
||||
}
|
||||
|
||||
private Assignment toAssignment(AssignmentEndpoint endpoint) {
|
||||
return new Assignment(
|
||||
endpoint.getClass().getSimpleName(),
|
||||
getPath(endpoint.getClass()),
|
||||
getHints(endpoint.getClass()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Course course() {
|
||||
lessons.stream().forEach(l -> l.setAssignments(createAssignment(l)));
|
||||
assignments.stream().forEach(this::attachToLesson);
|
||||
|
||||
// Check if all assignments are attached to a lesson
|
||||
var assignmentsAttachedToLessons =
|
||||
lessons.stream().mapToInt(l -> l.getAssignments().size()).sum();
|
||||
Assert.isTrue(
|
||||
assignmentsAttachedToLessons == assignments.size(),
|
||||
"Not all assignments are attached to a lesson, please check the configuration. The"
|
||||
+ " following assignments are not attached to any lesson: "
|
||||
+ findDiff());
|
||||
return new Course(lessons);
|
||||
}
|
||||
|
||||
private List<Assignment> createAssignment(Lesson lesson) {
|
||||
var endpoints = assignmentsByPackage.get(lesson.getClass().getPackageName());
|
||||
if (CollectionUtils.isEmpty(endpoints)) {
|
||||
log.warn("Lesson: {} has no endpoints, is this intentionally?", lesson.getTitle());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return endpoints.stream()
|
||||
.map(
|
||||
e ->
|
||||
new Assignment(
|
||||
e.getClass().getSimpleName(), getPath(e.getClass()), getHints(e.getClass())))
|
||||
.toList();
|
||||
private List<String> findDiff() {
|
||||
var matchedToLessons =
|
||||
lessons.stream().flatMap(l -> l.getAssignments().stream()).map(a -> a.getName()).toList();
|
||||
var allAssignments = assignments.stream().map(a -> a.getClass().getSimpleName()).toList();
|
||||
|
||||
var diff = new ArrayList<>(allAssignments);
|
||||
diff.removeAll(matchedToLessons);
|
||||
return diff;
|
||||
}
|
||||
|
||||
private String getPath(Class<? extends AssignmentEndpoint> e) {
|
||||
|
@ -6,7 +6,7 @@ import org.owasp.webgoat.container.users.WebGoatUser;
|
||||
* Interface for initialization of a lesson. It is called when a new user is added to WebGoat and
|
||||
* when a users reset a lesson. Make sure to clean beforehand and then re-initialize the lesson.
|
||||
*/
|
||||
public interface Initializeable {
|
||||
public interface Initializable {
|
||||
|
||||
void initialize(WebGoatUser webGoatUser);
|
||||
default void initialize(WebGoatUser webGoatUser) {}
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
|
||||
package org.owasp.webgoat.container.lessons;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@ -30,13 +31,10 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public abstract class Lesson {
|
||||
|
||||
private static int count = 1;
|
||||
private Integer id = null;
|
||||
private List<Assignment> assignments;
|
||||
private List<Assignment> assignments = new ArrayList<>();
|
||||
|
||||
/** Constructor for the Lesson object */
|
||||
protected Lesson() {
|
||||
id = ++count;
|
||||
public void addAssignment(Assignment assignment) {
|
||||
this.assignments.add(assignment);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,9 +42,9 @@ public abstract class Lesson {
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String getName() {
|
||||
public LessonName getName() {
|
||||
String className = getClass().getName();
|
||||
return className.substring(className.lastIndexOf('.') + 1);
|
||||
return new LessonName(className.substring(className.lastIndexOf('.') + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,6 +114,10 @@ public abstract class Lesson {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used in Thymeleaf to construct the HTML to load the lesson content from. See
|
||||
* lesson_content.html
|
||||
*/
|
||||
public final String getPackage() {
|
||||
var packageName = this.getClass().getPackageName();
|
||||
// package name is the direct package name below lessons (any subpackage will be removed)
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.owasp.webgoat.container.lessons;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Wrapper class for the name of a lesson. This class is used to ensure that the lesson name is not
|
||||
* null and does not contain the ".lesson" suffix. The front-end passes the lesson name as a string
|
||||
* to the back-end, which then creates a new LessonName object with the lesson name as a parameter.
|
||||
* The constructor of the LessonName class checks if the lesson name is null and removes the
|
||||
* ".lesson" suffix if it is present.
|
||||
*
|
||||
* @param lessonName
|
||||
*/
|
||||
public record LessonName(String lessonName) {
|
||||
public LessonName {
|
||||
Assert.notNull(lessonName, "Lesson name cannot be null");
|
||||
if (lessonName.contains(".lesson")) {
|
||||
lessonName = lessonName.substring(0, lessonName.indexOf(".lesson"));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user