feat: Introduce Playwright for UI testing
Instead of using Robot Framework which does not run during a `mvn install`. Playwright seems to be the better approach. We can now write them as normal JUnit test and they are executed during a build. Additionally this PR solves some interesting bugs found during writing Playwright tests: - A reset of a lesson removes all assignments as a result another user wouldn't see any assignments - If someone solves an assignment the assignment automatically got solved for a new user since the assignment included the `solved` flag which immediately got copied to new lesson progress. - Introduction of assignment progress linking a assignment not directly to all users.
This commit is contained in:
parent
9d5ab5fb21
commit
8e45316638
61
.github/workflows/test.yml
vendored
61
.github/workflows/test.yml
vendored
@ -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'
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -57,3 +57,8 @@ TestClass.class
|
||||
/.gitconfig
|
||||
|
||||
webgoat.gitconfig
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
21
pom.xml
21
pom.xml
@ -97,10 +97,12 @@
|
||||
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
|
||||
<waittimeForServerStart>60</waittimeForServerStart>
|
||||
<webdriver.version>5.9.2</webdriver.version>
|
||||
<webgoat.context>/</webgoat.context>
|
||||
<webgoat.context>/WebGoat</webgoat.context>
|
||||
<webgoat.port>8080</webgoat.port>
|
||||
<webgoat.sslenabled>false</webgoat.sslenabled>
|
||||
<webjars-locator-core.version>0.59</webjars-locator-core.version>
|
||||
<webwolf.context>/</webwolf.context>
|
||||
<webwolf.context>/WebWolf</webwolf.context>
|
||||
<webwolf.port>9090</webwolf.port>
|
||||
<wiremock.version>3.10.0</wiremock.version>
|
||||
<xml-resolver.version>1.2</xml-resolver.version>
|
||||
<xstream.version>1.4.5</xstream.version>
|
||||
@ -217,6 +219,11 @@
|
||||
<artifactId>jruby</artifactId>
|
||||
<version>9.4.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>1.49.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
@ -400,6 +407,11 @@
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-properties-migrator</artifactId>
|
||||
@ -486,7 +498,7 @@
|
||||
<logback.configurationFile>${basedir}/src/test/resources/logback-test.xml</logback.configurationFile>
|
||||
</systemPropertyVariables>
|
||||
<argLine>-Xmx512m</argLine>
|
||||
<includes>org/owasp/webgoat/*Test</includes>
|
||||
<includes>org/owasp/webgoat/integration/*Test, org/owasp/webgoat/playwright/**/*Test</includes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
@ -517,6 +529,7 @@
|
||||
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED</argLine>
|
||||
<excludes>
|
||||
<exclude>**/*IntegrationTest.java</exclude>
|
||||
<exclude>**/*UITest.java</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@ -702,7 +715,7 @@
|
||||
</arguments>
|
||||
<waitForInterrupt>false</waitForInterrupt>
|
||||
<waitAfterLaunch>${waittimeForServerStart}</waitAfterLaunch>
|
||||
<healthCheckUrl>http://127.0.0.1:${webgoat.port}${webgoat.context}login</healthCheckUrl>
|
||||
<healthCheckUrl>http://127.0.0.1:${webgoat.port}${webgoat.context}/login</healthCheckUrl>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
|
@ -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!
|
129
robot/goat.robot
129
robot/goat.robot
@ -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
|
36
src/it/java/org/owasp/webgoat/ServerUrlConfig.java
Normal file
36
src/it/java/org/owasp/webgoat/ServerUrlConfig.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
@ -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 : "");
|
||||
}
|
||||
|
||||
/**
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
@ -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)
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.response.Response;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import java.util.HashMap;
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat;
|
||||
package org.owasp.webgoat.integration;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
@ -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.");
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
* <p>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());
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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"
|
||||
+ "}");
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -51,7 +51,6 @@ public class Assignment {
|
||||
|
||||
private String name;
|
||||
private String path;
|
||||
private boolean solved = false;
|
||||
|
||||
@Transient private List<String> hints;
|
||||
|
||||
@ -75,8 +74,4 @@ public class Assignment {
|
||||
this.path = path;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
public void solved() {
|
||||
this.solved = true;
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,9 @@ public record LessonName(String lessonName) {
|
||||
lessonName = lessonName.substring(0, lessonName.indexOf(".lesson"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lessonName;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<Assignment> assignments = new HashSet<>();
|
||||
private final Set<AssignmentProgress> 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<Assignment> getAssignment(String name) {
|
||||
return assignments.stream().filter(a -> a.getName().equals(name)).findFirst();
|
||||
private Optional<AssignmentProgress> 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<Assignment, Boolean> getLessonOverview() {
|
||||
return assignments.stream().collect(Collectors.toMap(a -> a, Assignment::isSolved));
|
||||
public Map<AssignmentProgress, Boolean> getLessonOverview() {
|
||||
return assignments.stream().collect(Collectors.toMap(a -> a, AssignmentProgress::isSolved));
|
||||
}
|
||||
|
||||
long numberOfSolvedAssignments() {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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<Assignment, Boolean> lessonOverview = lessonTracker.getLessonOverview();
|
||||
assertThat(lessonOverview.get(a1)).isTrue();
|
||||
assertThat(lessonOverview.get(a2)).isFalse();
|
||||
Map<AssignmentProgress, Boolean> lessonOverview = lessonTracker.getLessonOverview();
|
||||
assertThat(lessonOverview.values()).containsExactlyInAnyOrder(true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user