diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index f6ea94ad6..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: "UI-Test" -on: - pull_request: - paths-ignore: - - 'LICENSE' - - 'docs/**' - push: - tags-ignore: - - 'v*' - paths-ignore: - - '.txt' - - '*.MD' - - '*.md' - - 'LICENSE' - - 'docs/**' - -jobs: - build: - runs-on: ubuntu-latest - # display name of the job - name: "Robot framework test" - steps: - # Uses an default action to checkout the code - - uses: actions/checkout@v4.1.6 - # Uses an action to add Python to the VM - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.7' - architecture: x64 - # Uses an action to add JDK 23 to the VM (and mvn?) - - name: set up JDK 23 - uses: actions/setup-java@v4.2.1 - with: - distribution: 'temurin' - java-version: 23 - architecture: x64 - cache: 'maven' - - uses: BSFishy/pip-action@v1 - with: - packages: | - robotframework - robotframework-SeleniumLibrary - webdriver-manager - selenium==4.9.1 - # TODO https://github.com/robotframework/SeleniumLibrary/issues/1835 - - name: Run with Maven - run: mvn --no-transfer-progress spring-boot:run & - - name: Wait to start - uses: ifaxity/wait-on-action@v1 - with: - resource: http://127.0.0.1:8080/WebGoat - - name: Test with Robotframework - run: python3 -m robot --variable HEADLESS:"1" --outputdir robotreport robot/goat.robot - # send report to forks only due to limits on permission tokens - - name: Send report to commit - if: github.repository != 'WebGoat/WebGoat' && github.event_name == 'push' - uses: joonvena/robotframework-reporter-action@v2.2 - with: - gh_access_token: ${{ secrets.GITHUB_TOKEN }} - report_path: 'robotreport' diff --git a/.gitignore b/.gitignore index 06de08b13..93ef4a097 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,8 @@ TestClass.class /.gitconfig webgoat.gitconfig +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/pom.xml b/pom.xml index 6facc958c..beb3e6314 100644 --- a/pom.xml +++ b/pom.xml @@ -97,10 +97,12 @@ 3.1.2.RELEASE 60 5.9.2 - / + /WebGoat + 8080 false 0.59 - / + /WebWolf + 9090 3.10.0 1.2 1.4.5 @@ -217,6 +219,11 @@ jruby 9.4.9.0 + + com.microsoft.playwright + playwright + 1.49.0 + @@ -400,6 +407,11 @@ rest-assured test + + com.microsoft.playwright + playwright + test + org.springframework.boot spring-boot-properties-migrator @@ -486,7 +498,7 @@ ${basedir}/src/test/resources/logback-test.xml -Xmx512m - org/owasp/webgoat/*Test + org/owasp/webgoat/integration/*Test, org/owasp/webgoat/playwright/**/*Test @@ -517,6 +529,7 @@ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED **/*IntegrationTest.java + **/*UITest.java @@ -702,7 +715,7 @@ false ${waittimeForServerStart} - http://127.0.0.1:${webgoat.port}${webgoat.context}login + http://127.0.0.1:${webgoat.port}${webgoat.context}/login 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