diff --git a/robot/README.md b/robot/README.md
deleted file mode 100644
index de0db8e7b..000000000
--- a/robot/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Install and use Robotframework
-
-## Install Chromedriver on Mac OS
-
- brew install cask chromedriver
- chromedriver --version
-
-Then see security settings and allow the file to run
-
-## Install
-
- pip3 install virtualenv --user
- python3 -m virtualenv .venv
- source .venv/bin/activate
- pip install --upgrade robotframework
- pip install --upgrade robotframework-SeleniumLibrary
- pip install --upgrade webdriver-manager
- brew upgrade
- robot --variable HEADLESS:"0" --variable ENDPOINT:"http://127.0.0.1:8080/WebGoat" goat.robot
-
-Make sure that the Chrome version, the webdriver version and all related components are up-to-date and compatible!
diff --git a/robot/goat.robot b/robot/goat.robot
deleted file mode 100644
index e0fd074ff..000000000
--- a/robot/goat.robot
+++ /dev/null
@@ -1,129 +0,0 @@
-*** Settings ***
-Documentation Setup WebGoat Robotframework tests
-Library SeleniumLibrary timeout=100 run_on_failure=Capture Page Screenshot
-Library String
-Library OperatingSystem
-
-Suite Setup Initial_Page ${ENDPOINT} ${BROWSER}
-Suite Teardown Close_Page
-
-*** Variables ***
-${BROWSER} chrome
-${SLEEP} 100
-${DELAY} 0.25
-${ENDPOINT} http://127.0.0.1:8080/WebGoat
-${ENDPOINT_WOLF} http://127.0.0.1:9090/WebWolf
-${USERNAME} robotuser
-${PASSWORD} password
-${HEADLESS} ${FALSE}
-
-*** Keywords ***
-Initial_Page
- [Documentation] Check the inital page
- [Arguments] ${ENDPOINT} ${BROWSER}
- Log To Console Start WebGoat UI Testing
- IF ${HEADLESS}
- Open Browser ${ENDPOINT} ${BROWSER} options=add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'});add_argument("-headless");add_argument("--start-maximized") alias=webgoat
- ELSE
- Open Browser ${ENDPOINT} ${BROWSER} options=add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'}) alias=webgoat
- END
- Switch Browser webgoat
- Maximize Browser Window
- Set Window Size ${1400} ${1000}
- Set Window Position ${0} ${0}
- Set Selenium Speed ${DELAY}
- Log To Console Start WebWolf UI Testing
- IF ${HEADLESS}
- Open Browser ${ENDPOINT_WOLF} ${BROWSER} options=add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'});add_argument("-headless");add_argument("--start-maximized") alias=webwolf
- ELSE
- Open Browser ${ENDPOINT_WOLF} ${BROWSER} options=add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'}) alias=webwolf
- END
- Switch Browser webwolf
- Maximize Browser Window
- Set Window Size ${1400} ${1000}
- Set Window Position ${500} ${0}
- Set Selenium Speed ${DELAY}
-
-Close_Page
- [Documentation] Closing the browser
- Log To Console ==> Stop WebGoat UI Testing
- IF ${HEADLESS}
- Switch Browser webgoat
- Close Browser
- Switch Browser webwolf
- Close Browser
- END
-
-*** Test Cases ***
-
-Check_Initial_Page
- [Tags] WebGoatTests
- Switch Browser webgoat
- Page Should Contain Username
- Click Button Sign in
- Page Should Contain Invalid username
- Click Link /WebGoat/registration
-
-Check_Registration_Page
- [Tags] WebGoatTests
- Page Should Contain Username
- Input Text username ${USERNAME}
- Input Text password ${PASSWORD}
- Input Text matchingPassword ${PASSWORD}
- Click Element agree
- Click Button Sign up
-
-Check_Welcome_Page
- [Tags] WebGoatTests
- Page Should Contain WebGoat
- Go To ${ENDPOINT}/login
- Page Should Contain Username
- Input Text username ${USERNAME}
- Input Text password ${PASSWORD}
- Click Button Sign in
- Page Should Contain WebGoat
-
-Check_Menu_Page
- [Tags] WebGoatTests
- Click Element css=a[category='Introduction']
- Click Element Introduction-WebGoat
- CLick Element Introduction-WebWolf
- Click Element css=a[category='General']
- CLick Element General-HTTPBasics
- Click Element xpath=//*[.='2']
- Input Text person ${USERNAME}
- Click Button Go!
- ${OUT_VALUE} Get Text xpath=//div[contains(@class, 'attack-feedback')]
- ${OUT_RESULT} Evaluate "resutobor" in """${OUT_VALUE}"""
- IF not ${OUT_RESULT}
- Fail "not ok"
- END
-
-Check_WebWolf
- Switch Browser webwolf
- location should be ${ENDPOINT_WOLF}/login
- Input Text username ${USERNAME}
- Input Text password ${PASSWORD}
- Click Button Sign In
- Go To ${ENDPOINT_WOLF}/mail
- Go To ${ENDPOINT_WOLF}/requests
- Go To ${ENDPOINT_WOLF}/files
-
-Check_JWT_Page
- Go To ${ENDPOINT_WOLF}/jwt
- Click Element token
- Wait Until Element Is Enabled token 5s
- Input Text token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Click Element secretKey
- Input Text secretKey none
- Sleep 2s # Pause before reading the result
- ${OUT_VALUE} Get Value xpath=//textarea[@id='token']
- Log To Console Found token ${OUT_VALUE}
- ${OUT_RESULT} Evaluate "ImuPnHvLdU7ULKfbD4aJU" in """${OUT_VALUE}"""
- Log To Console Found token ${OUT_RESULT}
- Capture Page Screenshot
-
-Check_Files_Page
- Go To ${ENDPOINT_WOLF}/files
- Choose File css:input[type="file"] ${CURDIR}/goat.robot
- Click Button Upload files
diff --git a/src/it/java/org/owasp/webgoat/ServerUrlConfig.java b/src/it/java/org/owasp/webgoat/ServerUrlConfig.java
new file mode 100644
index 000000000..4908c7a17
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/ServerUrlConfig.java
@@ -0,0 +1,36 @@
+package org.owasp.webgoat;
+
+public record ServerUrlConfig(String host, String port, String contextPath) {
+
+ public ServerUrlConfig {
+ contextPath = contextPath.replaceAll("/", "");
+ }
+
+ public String getBaseUrl() {
+ return "http://%s:%s".formatted(host, port);
+ }
+
+ public String url(String path) {
+ return "%s/%s".formatted(getFullUrl(), path);
+ }
+
+ private String getFullUrl() {
+ return "http://%s:%s/%s".formatted(host, port, contextPath);
+ }
+
+ public static ServerUrlConfig webGoat() {
+ return new ServerUrlConfig(
+ "localhost", env("WEBGOAT_PORT", "8080"), env("WEBGOAT_CONTEXT", "WebGoat"));
+ }
+
+ public static ServerUrlConfig webWolf() {
+ return new ServerUrlConfig(
+ "localhost", env("WEBWOLF_PORT", "9090"), env("WEBWOLF_CONTEXT", "WebWolf"));
+ }
+
+ private static String env(String variableName, String defaultValue) {
+ return System.getenv().getOrDefault(variableName, "").isEmpty()
+ ? defaultValue
+ : System.getenv(variableName);
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/AccessControlIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/AccessControlIntegrationTest.java
similarity index 98%
rename from src/it/java/org/owasp/webgoat/AccessControlIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/AccessControlIntegrationTest.java
index 1add0d725..788103a80 100644
--- a/src/it/java/org/owasp/webgoat/AccessControlIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/AccessControlIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
diff --git a/src/it/java/org/owasp/webgoat/CSRFIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/CSRFIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/CSRFIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/CSRFIntegrationTest.java
index ecff556d5..d6d89411a 100644
--- a/src/it/java/org/owasp/webgoat/CSRFIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/CSRFIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
diff --git a/src/it/java/org/owasp/webgoat/ChallengeIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/ChallengeIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/ChallengeIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/ChallengeIntegrationTest.java
index 917f8716b..408948d6e 100644
--- a/src/it/java/org/owasp/webgoat/ChallengeIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/ChallengeIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.junit.jupiter.api.Assertions.assertTrue;
diff --git a/src/it/java/org/owasp/webgoat/CryptoIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/CryptoIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/CryptoIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/CryptoIntegrationTest.java
index 857b4429a..eed36e4c4 100644
--- a/src/it/java/org/owasp/webgoat/CryptoIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/CryptoIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.junit.jupiter.api.Assertions.fail;
diff --git a/src/it/java/org/owasp/webgoat/DeserializationIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/DeserializationIntegrationTest.java
similarity index 96%
rename from src/it/java/org/owasp/webgoat/DeserializationIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/DeserializationIntegrationTest.java
index 1f1325524..a7f476c67 100644
--- a/src/it/java/org/owasp/webgoat/DeserializationIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/DeserializationIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import java.io.IOException;
import java.util.HashMap;
diff --git a/src/it/java/org/owasp/webgoat/GeneralLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/GeneralLessonIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/GeneralLessonIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/GeneralLessonIntegrationTest.java
index db8557dad..3a4a3e7ca 100644
--- a/src/it/java/org/owasp/webgoat/GeneralLessonIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/GeneralLessonIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
diff --git a/src/it/java/org/owasp/webgoat/IDORIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/IDORIntegrationTest.java
similarity index 98%
rename from src/it/java/org/owasp/webgoat/IDORIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/IDORIntegrationTest.java
index 4b75e5314..831352c8a 100644
--- a/src/it/java/org/owasp/webgoat/IDORIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/IDORIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
diff --git a/src/it/java/org/owasp/webgoat/IntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/IntegrationTest.java
similarity index 92%
rename from src/it/java/org/owasp/webgoat/IntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/IntegrationTest.java
index e115dc8bc..34a9faa67 100644
--- a/src/it/java/org/owasp/webgoat/IntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/IntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static io.restassured.RestAssured.given;
@@ -11,27 +11,20 @@ import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.owasp.webgoat.ServerUrlConfig;
import org.springframework.http.HttpStatus;
public abstract class IntegrationTest {
- private static String webGoatPort = System.getenv().getOrDefault("WEBGOAT_PORT", "8080");
- @Getter private static String webWolfPort = System.getenv().getOrDefault("WEBWOLF_PORT", "9090");
-
- @Getter
- private static String webWolfHost = System.getenv().getOrDefault("WEBWOLF_HOST", "127.0.0.1");
-
- private static String webGoatContext =
- System.getenv().getOrDefault("WEBGOAT_CONTEXT", "/WebGoat/");
- private static String webWolfContext =
- System.getenv().getOrDefault("WEBWOLF_CONTEXT", "/WebWolf/");
+ private final ServerUrlConfig webGoatUrlConfig = ServerUrlConfig.webGoat();
+ @Getter private final ServerUrlConfig webWolfUrlConfig = ServerUrlConfig.webWolf();
@Getter private String webGoatCookie;
@Getter private String webWolfCookie;
@Getter private final String user = "webgoat";
protected String url(String url) {
- return "http://localhost:%s%s%s".formatted(webGoatPort, webGoatContext, url);
+ return webGoatUrlConfig.url(url);
}
protected class WebWolfUrlBuilder {
@@ -40,8 +33,7 @@ public abstract class IntegrationTest {
private String path = null;
protected String build() {
- return "http://localhost:%s%s%s"
- .formatted(webWolfPort, webWolfContext, path != null ? path : "");
+ return webWolfUrlConfig.url(path != null ? path : "");
}
/**
diff --git a/src/it/java/org/owasp/webgoat/JWTLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/JWTLessonIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/JWTLessonIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/JWTLessonIntegrationTest.java
index e69f9690e..5ed408ed9 100644
--- a/src/it/java/org/owasp/webgoat/JWTLessonIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/JWTLessonIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
diff --git a/src/it/java/org/owasp/webgoat/LabelAndHintIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/LabelAndHintIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/LabelAndHintIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/LabelAndHintIntegrationTest.java
index 181504b8b..885d03278 100644
--- a/src/it/java/org/owasp/webgoat/LabelAndHintIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/LabelAndHintIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
diff --git a/src/it/java/org/owasp/webgoat/PasswordResetLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/PasswordResetLessonIntegrationTest.java
similarity index 96%
rename from src/it/java/org/owasp/webgoat/PasswordResetLessonIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/PasswordResetLessonIntegrationTest.java
index 9dd7476b5..80eeb14ab 100644
--- a/src/it/java/org/owasp/webgoat/PasswordResetLessonIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/PasswordResetLessonIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
@@ -136,7 +136,7 @@ public class PasswordResetLessonIntegrationTest extends IntegrationTest {
private void clickForgotEmailLink(String user) {
RestAssured.given()
.when()
- .header(HttpHeaders.HOST, String.format("%s:%s", getWebWolfHost(), getWebWolfPort()))
+ .header(HttpHeaders.HOST, String.format("%s:%s", "127.0.0.1", getWebWolfUrlConfig().port()))
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams("email", user)
diff --git a/src/it/java/org/owasp/webgoat/PathTraversalIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/PathTraversalIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/PathTraversalIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/PathTraversalIntegrationTest.java
index 6deecedd6..3d852e3a5 100644
--- a/src/it/java/org/owasp/webgoat/PathTraversalIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/PathTraversalIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
diff --git a/src/it/java/org/owasp/webgoat/ProgressRaceConditionIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/ProgressRaceConditionIntegrationTest.java
similarity index 98%
rename from src/it/java/org/owasp/webgoat/ProgressRaceConditionIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/ProgressRaceConditionIntegrationTest.java
index 07f56b966..9539a7bd0 100644
--- a/src/it/java/org/owasp/webgoat/ProgressRaceConditionIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/ProgressRaceConditionIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import io.restassured.RestAssured;
import io.restassured.response.Response;
diff --git a/src/it/java/org/owasp/webgoat/SSRFIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/SSRFIntegrationTest.java
similarity index 93%
rename from src/it/java/org/owasp/webgoat/SSRFIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/SSRFIntegrationTest.java
index ba94cbd4b..f67f43db9 100644
--- a/src/it/java/org/owasp/webgoat/SSRFIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/SSRFIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import java.util.HashMap;
import java.util.Map;
diff --git a/src/it/java/org/owasp/webgoat/SessionManagementIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/SessionManagementIntegrationTest.java
similarity index 97%
rename from src/it/java/org/owasp/webgoat/SessionManagementIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/SessionManagementIntegrationTest.java
index ba30a1324..49e193f66 100644
--- a/src/it/java/org/owasp/webgoat/SessionManagementIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/SessionManagementIntegrationTest.java
@@ -21,7 +21,7 @@
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
*/
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import java.util.Map;
import org.junit.jupiter.api.Test;
diff --git a/src/it/java/org/owasp/webgoat/SqlInjectionAdvancedIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/SqlInjectionAdvancedIntegrationTest.java
similarity index 98%
rename from src/it/java/org/owasp/webgoat/SqlInjectionAdvancedIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/SqlInjectionAdvancedIntegrationTest.java
index 11cbed2f8..b8e3e76e1 100644
--- a/src/it/java/org/owasp/webgoat/SqlInjectionAdvancedIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/SqlInjectionAdvancedIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import java.util.HashMap;
import java.util.Map;
diff --git a/src/it/java/org/owasp/webgoat/SqlInjectionLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/SqlInjectionLessonIntegrationTest.java
similarity index 98%
rename from src/it/java/org/owasp/webgoat/SqlInjectionLessonIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/SqlInjectionLessonIntegrationTest.java
index 661c70979..af7c0b5d5 100644
--- a/src/it/java/org/owasp/webgoat/SqlInjectionLessonIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/SqlInjectionLessonIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import java.util.HashMap;
import java.util.Map;
diff --git a/src/it/java/org/owasp/webgoat/SqlInjectionMitigationIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/SqlInjectionMitigationIntegrationTest.java
similarity index 98%
rename from src/it/java/org/owasp/webgoat/SqlInjectionMitigationIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/SqlInjectionMitigationIntegrationTest.java
index 1cc8b9501..aa8c95a3a 100644
--- a/src/it/java/org/owasp/webgoat/SqlInjectionMitigationIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/SqlInjectionMitigationIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.hamcrest.CoreMatchers.containsString;
diff --git a/src/it/java/org/owasp/webgoat/WebWolfIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/WebWolfIntegrationTest.java
similarity index 98%
rename from src/it/java/org/owasp/webgoat/WebWolfIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/WebWolfIntegrationTest.java
index 16d078db4..3a3757226 100644
--- a/src/it/java/org/owasp/webgoat/WebWolfIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/WebWolfIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import static org.junit.jupiter.api.Assertions.assertTrue;
diff --git a/src/it/java/org/owasp/webgoat/XSSIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/XSSIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/XSSIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/XSSIntegrationTest.java
index c3e391422..8a9d74584 100644
--- a/src/it/java/org/owasp/webgoat/XSSIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/XSSIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import io.restassured.RestAssured;
import java.util.HashMap;
diff --git a/src/it/java/org/owasp/webgoat/XXEIntegrationTest.java b/src/it/java/org/owasp/webgoat/integration/XXEIntegrationTest.java
similarity index 99%
rename from src/it/java/org/owasp/webgoat/XXEIntegrationTest.java
rename to src/it/java/org/owasp/webgoat/integration/XXEIntegrationTest.java
index 21598e575..938698276 100644
--- a/src/it/java/org/owasp/webgoat/XXEIntegrationTest.java
+++ b/src/it/java/org/owasp/webgoat/integration/XXEIntegrationTest.java
@@ -1,4 +1,4 @@
-package org.owasp.webgoat;
+package org.owasp.webgoat.integration;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/HttpBasicsLessonUITest.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/HttpBasicsLessonUITest.java
new file mode 100644
index 000000000..1c475b9c6
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/HttpBasicsLessonUITest.java
@@ -0,0 +1,65 @@
+package org.owasp.webgoat.playwright.webgoat;
+
+import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+
+import com.microsoft.playwright.*;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.owasp.webgoat.container.lessons.LessonName;
+import org.owasp.webgoat.playwright.webgoat.helpers.Authentication;
+import org.owasp.webgoat.playwright.webgoat.pages.HttpBasicsLessonPage;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class HttpBasicsLessonUITest extends PlaywrightTest {
+
+ private HttpBasicsLessonPage lessonPage;
+
+ @BeforeEach
+ void navigateToLesson(Browser browser) {
+ var lessonName = new LessonName("HttpBasics");
+ var page = Authentication.sylvester(browser);
+
+ this.lessonPage = new HttpBasicsLessonPage(page);
+ lessonPage.resetLesson(lessonName);
+ lessonPage.open(lessonName);
+ }
+
+ @Test
+ @Order(1)
+ void shouldShowDefaultPage() {
+ assertThat(lessonPage.getTitle()).hasText("HTTP Basics");
+ Assertions.assertThat(lessonPage.noAssignmentsCompleted()).isTrue();
+ Assertions.assertThat(lessonPage.numberOfAssignments()).isEqualTo(2);
+ }
+
+ @Test
+ @Order(2)
+ @DisplayName(
+ "When the user enters their name, the server should reverse it then the assignment should be"
+ + " solved")
+ void solvePage2() {
+ lessonPage.navigateTo(2);
+ lessonPage.getEnterYourName().fill("John Doe");
+ lessonPage.getGoButton().click();
+
+ assertThat(lessonPage.getAssignmentOutput())
+ .containsText("The server has reversed your name: eoD nhoJ");
+ Assertions.assertThat(lessonPage.isAssignmentSolved(2)).isTrue();
+ }
+
+ @Test
+ @Order(3)
+ @DisplayName("When the user enters nothing then the server should display an error message")
+ void invalidPage2() {
+ lessonPage.navigateTo(2);
+ lessonPage.getEnterYourName().fill("");
+ lessonPage.getGoButton().click();
+
+ assertThat(lessonPage.getAssignmentOutput()).containsText("Try again, name cannot be empty.");
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/LoginUITest.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/LoginUITest.java
new file mode 100644
index 000000000..1ed0e99b1
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/LoginUITest.java
@@ -0,0 +1,27 @@
+package org.owasp.webgoat.playwright.webgoat;
+
+import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+
+import com.microsoft.playwright.Browser;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.options.AriaRole;
+import org.junit.jupiter.api.Test;
+import org.owasp.webgoat.playwright.webgoat.helpers.Authentication;
+import org.owasp.webgoat.playwright.webgoat.pages.WebGoatLoginPage;
+
+class LoginUITest extends PlaywrightTest {
+
+ @Test
+ void loginLogout(Browser browser) {
+ var page = Authentication.tweety(browser);
+ var loginPage = new WebGoatLoginPage(page);
+ loginPage.open();
+ loginPage.login(Authentication.getTweety().name(), Authentication.getTweety().password());
+
+ // logout
+ page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("")).click();
+ page.getByRole(AriaRole.MENUITEM, new Page.GetByRoleOptions().setName("Logout")).click();
+
+ assertThat(loginPage.getSignInButton()).isVisible();
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/PlaywrightTest.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/PlaywrightTest.java
new file mode 100644
index 000000000..dda5f5da9
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/PlaywrightTest.java
@@ -0,0 +1,33 @@
+package org.owasp.webgoat.playwright.webgoat;
+
+import com.microsoft.playwright.Browser;
+import com.microsoft.playwright.junit.Options;
+import com.microsoft.playwright.junit.OptionsFactory;
+import com.microsoft.playwright.junit.UsePlaywright;
+import org.owasp.webgoat.ServerUrlConfig;
+
+@UsePlaywright(PlaywrightTest.WebGoatOptions.class)
+public class PlaywrightTest {
+
+ private static final ServerUrlConfig webGoatUrlConfig = ServerUrlConfig.webGoat();
+ private static final ServerUrlConfig webWolfUrlConfig = ServerUrlConfig.webWolf();
+
+ public static class WebGoatOptions implements OptionsFactory {
+ @Override
+ public Options getOptions() {
+ return new Options().setHeadless(true).setContextOptions(getContextOptions());
+ }
+ }
+
+ protected static Browser.NewContextOptions getContextOptions() {
+ return new Browser.NewContextOptions().setBaseURL(webGoatUrlConfig.getBaseUrl());
+ }
+
+ public static String webGoatUrl(String path) {
+ return webGoatUrlConfig.url(path);
+ }
+
+ public static String webWolfURL(String path) {
+ return webWolfUrlConfig.url(path);
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/helpers/Authentication.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/helpers/Authentication.java
new file mode 100644
index 000000000..424f78c63
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/helpers/Authentication.java
@@ -0,0 +1,61 @@
+package org.owasp.webgoat.playwright.webgoat.helpers;
+
+import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+
+import com.microsoft.playwright.Browser;
+import com.microsoft.playwright.Page;
+import lombok.Getter;
+import org.owasp.webgoat.playwright.webgoat.pages.RegistrationPage;
+import org.owasp.webgoat.playwright.webgoat.pages.WebGoatLoginPage;
+import org.owasp.webgoat.playwright.webwolf.pages.WebWolfLoginPage;
+
+/**
+ * Helper class to authenticate users in WebGoat and WebWolf.
+ *
+ * It provides two users: sylvester and tweety. The users are authenticated by logging in to
+ * WebGoat and WebWolf. Once authenticated, the user's authentication token is stored in the browser
+ * and reused for subsequent requests.
+ */
+public class Authentication {
+
+ public record User(String name, String password, String auth) {
+ boolean loggedIn() {
+ return auth != null;
+ }
+ }
+
+ @Getter private static User sylvester = new User("sylvester", "sylvester", null);
+ @Getter private static User tweety = new User("tweety", "tweety", null);
+
+ public static Page sylvester(Browser browser) {
+ User user = login(browser, sylvester);
+ return browser.newContext(new Browser.NewContextOptions().setStorageState(user.auth)).newPage();
+ }
+
+ public static Page tweety(Browser browser) {
+ User user = login(browser, tweety);
+ return browser.newContext(new Browser.NewContextOptions().setStorageState(user.auth)).newPage();
+ }
+
+ private static User login(Browser browser, User user) {
+ if (user.loggedIn()) {
+ return user;
+ }
+ var page = browser.newContext().newPage();
+ RegistrationPage registrationPage = new RegistrationPage(page);
+ registrationPage.open();
+ registrationPage.register(user.name, user.password);
+
+ WebGoatLoginPage loginPage = new WebGoatLoginPage(page);
+ loginPage.open();
+ loginPage.login(user.name, user.password);
+ assertThat(loginPage.getSignInButton()).not().isVisible();
+
+ WebWolfLoginPage webWolfLoginPage = new WebWolfLoginPage(page);
+ webWolfLoginPage.open();
+ webWolfLoginPage.login(user.name, user.password);
+ assertThat(loginPage.getSignInButton()).not().isVisible();
+
+ return new User(user.name, user.password, page.context().storageState());
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/HttpBasicsLessonPage.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/HttpBasicsLessonPage.java
new file mode 100644
index 000000000..bdb4c1811
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/HttpBasicsLessonPage.java
@@ -0,0 +1,24 @@
+package org.owasp.webgoat.playwright.webgoat.pages;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.options.AriaRole;
+import lombok.Getter;
+
+@Getter
+public class HttpBasicsLessonPage extends LessonPage {
+
+ private final Locator enterYourName;
+ private final Locator goButton;
+
+ public HttpBasicsLessonPage(Page page) {
+ super(page);
+ enterYourName = page.locator("input[name=\"person\"]");
+ goButton = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Go!"));
+ }
+
+ public Locator getTitle() {
+ return getPage()
+ .getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setName("HTTP Basics"));
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/LessonPage.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/LessonPage.java
new file mode 100644
index 000000000..222cf9522
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/LessonPage.java
@@ -0,0 +1,64 @@
+package org.owasp.webgoat.playwright.webgoat.pages;
+
+import static org.owasp.webgoat.playwright.webgoat.PlaywrightTest.webGoatUrl;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.options.AriaRole;
+import lombok.Getter;
+import org.assertj.core.api.Assertions;
+import org.owasp.webgoat.container.lessons.LessonName;
+
+@Getter
+class LessonPage {
+
+ private final Page page;
+
+ public LessonPage(Page page) {
+ this.page = page;
+ }
+
+ public void navigateTo(int pageNumber) {
+ page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("" + pageNumber)).click();
+ }
+
+ public void open(LessonName lessonName) {
+ page.navigate(webGoatUrl("start.mvc#lesson/%s".formatted(lessonName.lessonName())));
+ }
+
+ /**
+ * Force a reload for the UI to response, this is normally done by a JavaScript reloading every 5
+ * seconds
+ */
+ public void refreshPage() {
+ page.reload();
+ }
+
+ public void resetLesson(LessonName lessonName) {
+ Assertions.assertThat(
+ page.request()
+ .get(webGoatUrl("service/restartlesson.mvc/%s".formatted(lessonName)))
+ .ok())
+ .isTrue();
+ refreshPage();
+ }
+
+ public int numberOfAssignments() {
+ return page.locator(".attack-link.solved-false").count()
+ + page.locator(".attack-link.solved-true").count();
+ }
+
+ public boolean isAssignmentSolved(int pageNumber) {
+ var solvedAssignments = page.locator(".attack-link.solved-true");
+ solvedAssignments.waitFor();
+ return solvedAssignments.all().stream().anyMatch(l -> l.textContent().equals("" + pageNumber));
+ }
+
+ public boolean noAssignmentsCompleted() {
+ return page.locator(".attack-link.solved-true").count() == 0;
+ }
+
+ public Locator getAssignmentOutput() {
+ return page.locator("#lesson-content-wrapper");
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/RegistrationPage.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/RegistrationPage.java
new file mode 100644
index 000000000..8c1d1f380
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/RegistrationPage.java
@@ -0,0 +1,32 @@
+package org.owasp.webgoat.playwright.webgoat.pages;
+
+import static com.microsoft.playwright.options.AriaRole.BUTTON;
+import static org.owasp.webgoat.playwright.webgoat.PlaywrightTest.webGoatUrl;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.options.AriaRole;
+import lombok.Getter;
+
+public class RegistrationPage {
+
+ private final Page page;
+ @Getter private final Locator signUpButton;
+
+ public RegistrationPage(Page page) {
+ this.page = page;
+ this.signUpButton = this.page.getByRole(BUTTON, new Page.GetByRoleOptions().setName("Sign up"));
+ }
+
+ public void open() {
+ page.navigate(webGoatUrl("registration"));
+ }
+
+ public void register(String username, String password) {
+ page.getByPlaceholder("Username").fill(username);
+ page.getByLabel("Password", new Page.GetByLabelOptions().setExact(true)).fill(password);
+ page.getByLabel("Confirm password").fill(password);
+ page.getByLabel("Agree with the terms and").check();
+ page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")).click();
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/WebGoatLoginPage.java b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/WebGoatLoginPage.java
new file mode 100644
index 000000000..c0e4409cf
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webgoat/pages/WebGoatLoginPage.java
@@ -0,0 +1,29 @@
+package org.owasp.webgoat.playwright.webgoat.pages;
+
+import static com.microsoft.playwright.options.AriaRole.BUTTON;
+import static org.owasp.webgoat.playwright.webgoat.PlaywrightTest.webGoatUrl;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import lombok.Getter;
+
+public class WebGoatLoginPage {
+
+ private final Page page;
+ @Getter private final Locator signInButton;
+
+ public WebGoatLoginPage(Page page) {
+ this.page = page;
+ this.signInButton = this.page.getByRole(BUTTON, new Page.GetByRoleOptions().setName("Sign in"));
+ }
+
+ public void open() {
+ page.navigate(webGoatUrl("login"));
+ }
+
+ public void login(String username, String password) {
+ page.getByPlaceholder("Username").fill(username);
+ page.getByPlaceholder("Password").fill(password);
+ page.getByRole(BUTTON, new Page.GetByRoleOptions().setName("Sign in")).click();
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webwolf/JwtUITest.java b/src/it/java/org/owasp/webgoat/playwright/webwolf/JwtUITest.java
new file mode 100644
index 000000000..84c09fccd
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webwolf/JwtUITest.java
@@ -0,0 +1,32 @@
+package org.owasp.webgoat.playwright.webwolf;
+
+import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+
+import com.microsoft.playwright.Browser;
+import org.junit.jupiter.api.Test;
+import org.owasp.webgoat.playwright.webgoat.PlaywrightTest;
+import org.owasp.webgoat.playwright.webgoat.helpers.Authentication;
+
+class JwtUITest extends PlaywrightTest {
+
+ @Test
+ void shouldDecodeJwt(Browser browser) {
+ var page = Authentication.sylvester(browser);
+ var secretKey = "test";
+ var jwt =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
+
+ page.navigate(webWolfURL("jwt"));
+ page.getByPlaceholder("Enter your secret key").fill(secretKey);
+ page.getByPlaceholder("Paste token here").type(jwt);
+ assertThat(page.locator("#header"))
+ .hasValue("{\n \"alg\" : \"HS256\",\n \"typ\" : \"JWT\"\n}");
+ assertThat(page.locator("#payload"))
+ .hasValue(
+ "{\n"
+ + " \"iat\" : 1516239022,\n"
+ + " \"name\" : \"John Doe\",\n"
+ + " \"sub\" : \"1234567890\"\n"
+ + "}");
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webwolf/LoginUITest.java b/src/it/java/org/owasp/webgoat/playwright/webwolf/LoginUITest.java
new file mode 100644
index 000000000..c2e33a0c6
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webwolf/LoginUITest.java
@@ -0,0 +1,27 @@
+package org.owasp.webgoat.playwright.webwolf;
+
+import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+
+import com.microsoft.playwright.Browser;
+import org.junit.jupiter.api.Test;
+import org.owasp.webgoat.playwright.webgoat.PlaywrightTest;
+import org.owasp.webgoat.playwright.webgoat.helpers.Authentication;
+import org.owasp.webgoat.playwright.webwolf.pages.WebWolfLoginPage;
+
+public class LoginUITest extends PlaywrightTest {
+
+ @Test
+ void login(Browser browser) {
+ var page = Authentication.tweety(browser);
+ var loginPage = new WebWolfLoginPage(page);
+ loginPage.open();
+ loginPage.login(Authentication.getTweety().name(), Authentication.getTweety().password());
+
+ assertThat(loginPage.getSignInButton()).not().isVisible();
+
+ // logout
+ loginPage.logout();
+
+ assertThat(loginPage.getSignInButton()).isVisible();
+ }
+}
diff --git a/src/it/java/org/owasp/webgoat/playwright/webwolf/pages/WebWolfLoginPage.java b/src/it/java/org/owasp/webgoat/playwright/webwolf/pages/WebWolfLoginPage.java
new file mode 100644
index 000000000..55e73849e
--- /dev/null
+++ b/src/it/java/org/owasp/webgoat/playwright/webwolf/pages/WebWolfLoginPage.java
@@ -0,0 +1,37 @@
+package org.owasp.webgoat.playwright.webwolf.pages;
+
+import static com.microsoft.playwright.options.AriaRole.BUTTON;
+import static org.owasp.webgoat.playwright.webgoat.PlaywrightTest.webWolfURL;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.options.AriaRole;
+import lombok.Getter;
+
+public class WebWolfLoginPage {
+
+ private final Page page;
+ @Getter private final Locator signInButton;
+ private final Locator signOutButton;
+
+ public WebWolfLoginPage(Page page) {
+ this.page = page;
+ this.signInButton = this.page.getByRole(BUTTON, new Page.GetByRoleOptions().setName("Sign In"));
+ this.signOutButton =
+ this.page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Sign out"));
+ }
+
+ public void open() {
+ page.navigate(webWolfURL("login"));
+ }
+
+ public void login(String username, String password) {
+ page.getByPlaceholder("Username WebGoat").fill(username);
+ page.getByPlaceholder("Password WebGoat").fill(password);
+ signInButton.click();
+ }
+
+ public void logout() {
+ this.signOutButton.click();
+ }
+}
diff --git a/src/main/java/org/owasp/webgoat/container/lessons/Assignment.java b/src/main/java/org/owasp/webgoat/container/lessons/Assignment.java
index f0e15b171..3563a537e 100644
--- a/src/main/java/org/owasp/webgoat/container/lessons/Assignment.java
+++ b/src/main/java/org/owasp/webgoat/container/lessons/Assignment.java
@@ -51,7 +51,6 @@ public class Assignment {
private String name;
private String path;
- private boolean solved = false;
@Transient private List hints;
@@ -75,8 +74,4 @@ public class Assignment {
this.path = path;
this.hints = hints;
}
-
- public void solved() {
- this.solved = true;
- }
}
diff --git a/src/main/java/org/owasp/webgoat/container/lessons/LessonName.java b/src/main/java/org/owasp/webgoat/container/lessons/LessonName.java
index e68e84914..800c81c05 100644
--- a/src/main/java/org/owasp/webgoat/container/lessons/LessonName.java
+++ b/src/main/java/org/owasp/webgoat/container/lessons/LessonName.java
@@ -18,4 +18,9 @@ public record LessonName(String lessonName) {
lessonName = lessonName.substring(0, lessonName.indexOf(".lesson"));
}
}
+
+ @Override
+ public String toString() {
+ return lessonName;
+ }
}
diff --git a/src/main/java/org/owasp/webgoat/container/service/LessonProgressService.java b/src/main/java/org/owasp/webgoat/container/service/LessonProgressService.java
index 1c279de49..13da9fea0 100644
--- a/src/main/java/org/owasp/webgoat/container/service/LessonProgressService.java
+++ b/src/main/java/org/owasp/webgoat/container/service/LessonProgressService.java
@@ -10,7 +10,6 @@ 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.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
@@ -40,11 +39,9 @@ public class LessonProgressService {
var userProgress = userProgressRepository.findByUser(username);
var lesson = course.getLessonByName(lessonName);
- 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()))
+ .map(entry -> new LessonOverview(entry.getKey().getAssignment(), entry.getValue()))
.toList();
}
diff --git a/src/main/java/org/owasp/webgoat/container/users/AssignmentProgress.java b/src/main/java/org/owasp/webgoat/container/users/AssignmentProgress.java
new file mode 100644
index 000000000..a5ab07ab8
--- /dev/null
+++ b/src/main/java/org/owasp/webgoat/container/users/AssignmentProgress.java
@@ -0,0 +1,47 @@
+package org.owasp.webgoat.container.users;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToOne;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.owasp.webgoat.container.lessons.Assignment;
+import org.springframework.util.Assert;
+
+@Entity
+@EqualsAndHashCode
+public class AssignmentProgress {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Getter
+ @OneToOne(cascade = CascadeType.ALL)
+ private Assignment assignment;
+
+ @Getter private boolean solved;
+
+ protected AssignmentProgress() {}
+
+ public AssignmentProgress(Assignment assignment) {
+ this.assignment = assignment;
+ }
+
+ public boolean hasSameName(String name) {
+ Assert.notNull(name, "Name cannot be null");
+
+ return assignment.getName().equals(name);
+ }
+
+ public void solved() {
+ this.solved = true;
+ }
+
+ public void reset() {
+ this.solved = false;
+ }
+}
diff --git a/src/main/java/org/owasp/webgoat/container/users/LessonProgress.java b/src/main/java/org/owasp/webgoat/container/users/LessonProgress.java
index 6a0914f85..4cdeccf71 100644
--- a/src/main/java/org/owasp/webgoat/container/users/LessonProgress.java
+++ b/src/main/java/org/owasp/webgoat/container/users/LessonProgress.java
@@ -9,14 +9,12 @@ import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Version;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.Getter;
-import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Lesson;
/**
@@ -61,7 +59,7 @@ public class LessonProgress {
@Getter private String lessonName;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
- private final Set assignments = new HashSet<>();
+ private final Set assignments = new HashSet<>();
@Getter private int numberOfAttempts = 0;
@Version private Integer version;
@@ -72,11 +70,11 @@ public class LessonProgress {
public LessonProgress(Lesson lesson) {
lessonName = lesson.getId();
- assignments.addAll(lesson.getAssignments() == null ? List.of() : lesson.getAssignments());
+ assignments.addAll(lesson.getAssignments().stream().map(AssignmentProgress::new).toList());
}
- public Optional getAssignment(String name) {
- return assignments.stream().filter(a -> a.getName().equals(name)).findFirst();
+ private Optional getAssignment(String name) {
+ return assignments.stream().filter(a -> a.hasSameName(name)).findFirst();
}
/**
@@ -85,14 +83,14 @@ public class LessonProgress {
* @param solvedAssignment the assignment which the user solved
*/
public void assignmentSolved(String solvedAssignment) {
- getAssignment(solvedAssignment).ifPresent(Assignment::solved);
+ getAssignment(solvedAssignment).ifPresent(AssignmentProgress::solved);
}
/**
* @return did they user solved all solvedAssignments for the lesson?
*/
public boolean isLessonSolved() {
- return assignments.stream().allMatch(Assignment::isSolved);
+ return assignments.stream().allMatch(AssignmentProgress::isSolved);
}
/** Increase the number attempts to solve the lesson */
@@ -102,14 +100,14 @@ public class LessonProgress {
/** Reset the tracker. We do not reset the number of attempts here! */
void reset() {
- assignments.clear();
+ assignments.forEach(AssignmentProgress::reset);
}
/**
* @return list containing all the assignments solved or not
*/
- public Map getLessonOverview() {
- return assignments.stream().collect(Collectors.toMap(a -> a, Assignment::isSolved));
+ public Map getLessonOverview() {
+ return assignments.stream().collect(Collectors.toMap(a -> a, AssignmentProgress::isSolved));
}
long numberOfSolvedAssignments() {
diff --git a/src/main/java/org/owasp/webgoat/container/users/UserService.java b/src/main/java/org/owasp/webgoat/container/users/UserService.java
index af36a396f..0907c732a 100644
--- a/src/main/java/org/owasp/webgoat/container/users/UserService.java
+++ b/src/main/java/org/owasp/webgoat/container/users/UserService.java
@@ -31,6 +31,7 @@ public class UserService implements UserDetailsService {
throw new UsernameNotFoundException("User not found");
} else {
webGoatUser.createUser();
+ // TODO maybe better to use an event to initialize lessons to keep dependencies low
lessonInitializables.forEach(l -> l.initialize(webGoatUser));
}
return webGoatUser;
diff --git a/src/main/resources/db/container/V1__init.sql b/src/main/resources/db/container/V1__init.sql
index 76c72ae95..025c58565 100644
--- a/src/main/resources/db/container/V1__init.sql
+++ b/src/main/resources/db/container/V1__init.sql
@@ -2,13 +2,18 @@
-- For the normal WebGoat server there is a bean which already provided the schema (and creates it see DatabaseInitialization)
CREATE SCHEMA IF NOT EXISTS CONTAINER;
-create
- table CONTAINER.assignment
+create table CONTAINER.assignment
(
- solved boolean not null,
- id bigint generated by default as identity (start with 1),
- name varchar(255),
- path varchar(255),
+ id bigint generated by default as identity (start with 1),
+ name varchar(255),
+ path varchar(255),
+ primary key (id)
+);
+create table CONTAINER.assignment_progress
+(
+ solved boolean not null,
+ assignment_id bigint unique,
+ id bigint generated by default as identity (start with 1),
primary key (id)
);
create table CONTAINER.lesson_progress
@@ -55,8 +60,10 @@ create table CONTAINER.email
title VARCHAR(255)
);
+alter table CONTAINER.assignment_progress
+ add constraint FK7o6abukma83ku3xrge9sy0qnr foreign key (assignment_id) references CONTAINER.assignment;
alter table CONTAINER.lesson_progress_assignments
- add constraint FKbd9xavuwr1rxbcqhcu3jckyro foreign key (assignments_id) references CONTAINER.assignment;
+ add constraint FKrw89vmnela8kj0nbg1xdws5bt foreign key (assignments_id) references CONTAINER.assignment_progress;
alter table CONTAINER.lesson_progress_assignments
add constraint FKl8vg2qfqhmsnt18qqcyydq7iu foreign key (lesson_progress_id) references CONTAINER.lesson_progress;
alter table CONTAINER.user_progress_lesson_progress
diff --git a/src/test/java/org/owasp/webgoat/container/service/LessonProgressServiceTest.java b/src/test/java/org/owasp/webgoat/container/service/LessonProgressServiceTest.java
index c0408555f..fe17294f0 100644
--- a/src/test/java/org/owasp/webgoat/container/service/LessonProgressServiceTest.java
+++ b/src/test/java/org/owasp/webgoat/container/service/LessonProgressServiceTest.java
@@ -16,6 +16,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.Course;
+import org.owasp.webgoat.container.users.AssignmentProgress;
import org.owasp.webgoat.container.users.LessonProgress;
import org.owasp.webgoat.container.users.UserProgress;
import org.owasp.webgoat.container.users.UserProgressRepository;
@@ -68,10 +69,11 @@ class LessonProgressServiceTest {
@BeforeEach
void setup() {
Assignment assignment = new Assignment("test", "test", List.of());
+ AssignmentProgress assignmentProgress = new AssignmentProgress(assignment);
when(userProgressRepository.findByUser(any())).thenReturn(userProgress);
when(userProgress.getLessonProgress(any(Lesson.class))).thenReturn(lessonTracker);
when(course.getLessonByName(any())).thenReturn(lesson);
- when(lessonTracker.getLessonOverview()).thenReturn(Maps.newHashMap(assignment, true));
+ when(lessonTracker.getLessonOverview()).thenReturn(Maps.newHashMap(assignmentProgress, true));
this.mockMvc =
MockMvcBuilders.standaloneSetup(new LessonProgressService(userProgressRepository, course))
.build();
diff --git a/src/test/java/org/owasp/webgoat/container/session/LessonTrackerTest.java b/src/test/java/org/owasp/webgoat/container/session/LessonTrackerTest.java
index 873e758c5..28d28ef3d 100644
--- a/src/test/java/org/owasp/webgoat/container/session/LessonTrackerTest.java
+++ b/src/test/java/org/owasp/webgoat/container/session/LessonTrackerTest.java
@@ -11,6 +11,7 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Lesson;
+import org.owasp.webgoat.container.users.AssignmentProgress;
import org.owasp.webgoat.container.users.LessonProgress;
/**
@@ -67,9 +68,8 @@ class LessonTrackerTest {
LessonProgress lessonTracker = new LessonProgress(lesson);
lessonTracker.assignmentSolved("a1");
- Map lessonOverview = lessonTracker.getLessonOverview();
- assertThat(lessonOverview.get(a1)).isTrue();
- assertThat(lessonOverview.get(a2)).isFalse();
+ Map lessonOverview = lessonTracker.getLessonOverview();
+ assertThat(lessonOverview.values()).containsExactlyInAnyOrder(true, false);
}
@Test