From 3ad51e6d6b6367306f6f1142636d081d4905fbfd Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Thu, 4 Nov 2021 17:04:23 +0100 Subject: [PATCH] Rewrite lesson to be self-contained and not depend on the core of WebGoat for fetching users Split the assignment into 2 assignments --- .../org/owasp/webgoat/users/WebGoatUser.java | 5 +- .../org/owasp/webgoat/AccessControlTest.java | 103 +++++++++------ .../owasp/webgoat/missing_ac/DisplayUser.java | 43 ++----- .../MissingAccessControlUserRepository.java | 45 +++++++ .../webgoat/missing_ac/MissingFunctionAC.java | 5 +- .../MissingFunctionACHiddenMenus.java | 6 - .../missing_ac/MissingFunctionACUsers.java | 81 ++++++------ .../missing_ac/MissingFunctionACYourHash.java | 24 ++-- .../MissingFunctionACYourHashAdmin.java | 60 +++++++++ .../org/owasp/webgoat/missing_ac/User.java | 15 +++ .../org/owasp/webgoat/missing_ac/Users.java | 117 ------------------ .../src/main/resources/css/ac.css | 4 + .../db/migration/V2021_11_03_1__ac.sql | 9 ++ .../resources/html/MissingFunctionAC.html | 29 ++++- .../resources/i18n/WebGoatLabels.properties | 14 +-- .../en/missing-function-ac-03-users.adoc | 2 +- .../missing-function-ac-04-users-fixed.adoc | 5 + .../webgoat/missing_ac/DisplayUserTest.java | 21 ++-- .../MissingFunctionACUsersTest.java | 64 +++++----- .../MissingFunctionACYourHashAdminTest.java | 44 +++++++ .../MissingFunctionYourHashTest.java | 44 +++---- 21 files changed, 409 insertions(+), 331 deletions(-) create mode 100644 webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingAccessControlUserRepository.java create mode 100644 webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdmin.java create mode 100644 webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/User.java delete mode 100644 webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/Users.java create mode 100644 webgoat-lessons/missing-function-ac/src/main/resources/css/ac.css create mode 100644 webgoat-lessons/missing-function-ac/src/main/resources/db/migration/V2021_11_03_1__ac.sql create mode 100644 webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-04-users-fixed.adoc create mode 100644 webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdminTest.java diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/WebGoatUser.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/WebGoatUser.java index 23fcae34d..e6e720c36 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/users/WebGoatUser.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/WebGoatUser.java @@ -34,15 +34,14 @@ public class WebGoatUser implements UserDetails { } public WebGoatUser(String username, String password) { - this.username = username; - this.password = password; - createUser(); + this(username, password, ROLE_USER); } public WebGoatUser(String username, String password, String role) { this.username = username; this.password = password; this.role = role; + createUser(); } diff --git a/webgoat-integration-tests/src/test/java/org/owasp/webgoat/AccessControlTest.java b/webgoat-integration-tests/src/test/java/org/owasp/webgoat/AccessControlTest.java index da39a8c5f..cc51704b2 100644 --- a/webgoat-integration-tests/src/test/java/org/owasp/webgoat/AccessControlTest.java +++ b/webgoat-integration-tests/src/test/java/org/owasp/webgoat/AccessControlTest.java @@ -1,54 +1,87 @@ package org.owasp.webgoat; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; + import java.util.HashMap; import java.util.Map; -import org.junit.jupiter.api.Test; - -import io.restassured.RestAssured; -import io.restassured.http.ContentType; -import lombok.Data; - public class AccessControlTest extends IntegrationTest { - - @Test + + @Test public void testLesson() { - startLesson("MissingFunctionAC"); - - Map params = new HashMap<>(); - params.clear(); - params.put("hiddenMenu1", "Users"); - params.put("hiddenMenu2", "Config"); - - - checkAssignment(url("/WebGoat/access-control/hidden-menu"), params, true); - String userHash = + startLesson("MissingFunctionAC"); + assignment1(); + assignment2(); + assignment3(); + + checkResults("/access-control"); + } + + private void assignment3() { + //direct call should fail if user has not been created + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .contentType(ContentType.JSON) + .get(url("/WebGoat/access-control/users-admin-fix")) + .then() + .statusCode(HttpStatus.SC_FORBIDDEN); + + //create user + var userTemplate = """ + {"username":"%s","password":"%s","admin": "true"} + """; + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .contentType(ContentType.JSON) + .body(String.format(userTemplate, getWebgoatUser(), getWebgoatUser())) + .post(url("/WebGoat/access-control/users")) + .then() + .statusCode(HttpStatus.SC_OK); + + //get the users + var userHash = RestAssured.given() .when() .relaxedHTTPSValidation() .cookie("JSESSIONID", getWebGoatCookie()) - .contentType(ContentType.JSON) - .get(url("/WebGoat/users")) + .contentType(ContentType.JSON) + .get(url("/WebGoat/access-control/users-admin-fix")) .then() .statusCode(200) .extract() .jsonPath() - .get("find { it.username == \"" + getWebgoatUser() + "\" }.userHash"); - - params.clear(); - params.put("userHash", userHash); - checkAssignment(url("/WebGoat/access-control/user-hash"), params, true); - - - checkResults("/access-control"); + .get("find { it.username == \"Jerry\" }.userHash"); + + checkAssignment(url("/WebGoat/access-control/user-hash-fix"), Map.of("userHash", userHash), true); } - - @Data - public class Item { - private String username; - private boolean admin; - private String userHash; + + private void assignment2() { + var userHash = + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .contentType(ContentType.JSON) + .get(url("/WebGoat/access-control/users")) + .then() + .statusCode(200) + .extract() + .jsonPath() + .get("find { it.username == \"Jerry\" }.userHash"); + + checkAssignment(url("/WebGoat/access-control/user-hash"), Map.of("userHash", userHash), true); + } + + private void assignment1() { + var params = Map.of("hiddenMenu1", "Users", "hiddenMenu2", "Config"); + checkAssignment(url("/WebGoat/access-control/hidden-menu"), params, true); } - } diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/DisplayUser.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/DisplayUser.java index f96360c8c..bb07cf284 100644 --- a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/DisplayUser.java +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/DisplayUser.java @@ -1,8 +1,8 @@ package org.owasp.webgoat.missing_ac; -import org.owasp.webgoat.users.WebGoatUser; -import org.springframework.security.core.GrantedAuthority; +import lombok.Getter; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Base64; @@ -31,55 +31,32 @@ import java.util.Base64; * projects. *

*/ - +@Getter public class DisplayUser { //intended to provide a display version of WebGoatUser for admins to view user attributes - private String username; private boolean admin; private String userHash; - public DisplayUser(WebGoatUser user) { + public DisplayUser(User user, String passwordSalt) { this.username = user.getUsername(); - this.admin = false; + this.admin = user.isAdmin(); - for (GrantedAuthority authority : user.getAuthorities()) { - this.admin = (authority.getAuthority().contains("WEBGOAT_ADMIN")) ? true : false; - } - - // create userHash on the fly - //TODO: persist userHash try { - this.userHash = genUserHash(user.getUsername(), user.getPassword()); + this.userHash = genUserHash(user.getUsername(), user.getPassword(), passwordSalt); } catch (Exception ex) { - //TODO: implement better fallback operation this.userHash = "Error generating user hash"; } - } - protected String genUserHash(String username, String password) throws Exception { + protected String genUserHash(String username, String password, String passwordSalt) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); // salting is good, but static & too predictable ... short too for a salt - String salted = password + "DeliberatelyInsecure1234" + username; + String salted = password + passwordSalt + username; //md.update(salted.getBytes("UTF-8")); // Change this to "UTF-16" if needed - byte[] hash = md.digest(salted.getBytes("UTF-8")); - String encoded = Base64.getEncoder().encodeToString(hash); - return encoded; - } - - - public String getUsername() { - return username; - } - - public boolean isAdmin() { - return admin; - } - - public String getUserHash() { - return userHash; + byte[] hash = md.digest(salted.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(hash); } } diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingAccessControlUserRepository.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingAccessControlUserRepository.java new file mode 100644 index 000000000..51a6fe726 --- /dev/null +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingAccessControlUserRepository.java @@ -0,0 +1,45 @@ +package org.owasp.webgoat.missing_ac; + +import org.owasp.webgoat.LessonDataSource; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +@Component +public class MissingAccessControlUserRepository { + + private final NamedParameterJdbcTemplate jdbcTemplate; + private final RowMapper mapper = (rs, rowNum) -> new User(rs.getString("username"), rs.getString("password"), rs.getBoolean("admin")); + + public MissingAccessControlUserRepository(LessonDataSource lessonDataSource) { + this.jdbcTemplate = new NamedParameterJdbcTemplate(lessonDataSource); + } + + public List findAllUsers() { + return jdbcTemplate.query("select username, password, admin from access_control_users", mapper); + } + + public User findByUsername(String username) { + var users = jdbcTemplate.query("select username, password, admin from access_control_users where username=:username", + new MapSqlParameterSource().addValue("username", username), + mapper); + if (CollectionUtils.isEmpty(users)) { + return null; + } + return users.get(0); + } + + public User save(User user) { + jdbcTemplate.update("INSERT INTO access_control_users(username, password, admin) VALUES(:username,:password,:admin)", + new MapSqlParameterSource() + .addValue("username", user.getUsername()) + .addValue("password", user.getPassword()) + .addValue("admin", user.isAdmin())); + return user; + } + +} diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionAC.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionAC.java index b126b44f9..8a6900c9b 100644 --- a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionAC.java +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionAC.java @@ -27,7 +27,10 @@ import org.owasp.webgoat.lessons.Lesson; import org.springframework.stereotype.Component; @Component -public class MissingFunctionAC extends Lesson { +public class MissingFunctionAC extends Lesson { + + public static final String PASSWORD_SALT_SIMPLE = "DeliberatelyInsecure1234"; + public static final String PASSWORD_SALT_ADMIN = "DeliberatelyInsecure1235"; @Override public Category getDefaultCategory() { diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACHiddenMenus.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACHiddenMenus.java index baa487694..d588da180 100644 --- a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACHiddenMenus.java +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACHiddenMenus.java @@ -25,8 +25,6 @@ package org.owasp.webgoat.missing_ac; import org.owasp.webgoat.assignments.AssignmentEndpoint; import org.owasp.webgoat.assignments.AssignmentHints; import org.owasp.webgoat.assignments.AttackResult; -import org.owasp.webgoat.session.UserSessionData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @@ -37,10 +35,6 @@ import org.springframework.web.bind.annotation.RestController; @RestController @AssignmentHints({"access-control.hidden-menus.hint1","access-control.hidden-menus.hint2","access-control.hidden-menus.hint3"}) public class MissingFunctionACHiddenMenus extends AssignmentEndpoint { - //UserSessionData is bound to session and can be used to persist data across multiple assignments - @Autowired - UserSessionData userSessionData; - @PostMapping(path = "/access-control/hidden-menu", produces = {"application/json"}) @ResponseBody diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsers.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsers.java index 82de67200..64fb15015 100644 --- a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsers.java +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsers.java @@ -22,79 +22,82 @@ package org.owasp.webgoat.missing_ac; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.owasp.webgoat.users.UserService; -import org.owasp.webgoat.users.WebGoatUser; -import org.springframework.beans.factory.annotation.Autowired; +import org.owasp.webgoat.session.WebSession; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; -import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; + +import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN; +import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE; /** * Created by jason on 1/5/17. */ - @Controller +@AllArgsConstructor @Slf4j public class MissingFunctionACUsers { - // this will actually put controllers on the /WebGoat/* path ... the jsp for list_users restricts what can be seen, but the add_user is not controlled carefully - @Autowired - private UserService userService; + private final MissingAccessControlUserRepository userRepository; + private final WebSession webSession; - @RequestMapping(path = {"users"}, method = RequestMethod.GET) - public ModelAndView listUsers(HttpServletRequest request) { + @GetMapping(path = {"access-control/users"}) + public ModelAndView listUsers() { ModelAndView model = new ModelAndView(); model.setViewName("list_users"); - List allUsers = userService.getAllUsers(); - model.addObject("numUsers",allUsers.size()); + List allUsers = userRepository.findAllUsers(); + model.addObject("numUsers", allUsers.size()); //add display user objects in place of direct users List displayUsers = new ArrayList<>(); - for (WebGoatUser user : allUsers) { - displayUsers.add(new DisplayUser(user)); + for (User user : allUsers) { + displayUsers.add(new DisplayUser(user, PASSWORD_SALT_SIMPLE)); } - model.addObject("allUsers",displayUsers); + model.addObject("allUsers", displayUsers); return model; } - @RequestMapping(path = {"users", "/"}, method = RequestMethod.GET,consumes = "application/json") + @GetMapping(path = {"access-control/users"}, consumes = "application/json") @ResponseBody - public List usersService(HttpServletRequest request) { - - List allUsers = userService.getAllUsers(); - List displayUsers = new ArrayList<>(); - for (WebGoatUser user : allUsers) { - displayUsers.add(new DisplayUser(user)); - } - return displayUsers; + public ResponseEntity> usersService() { + return ResponseEntity.ok(userRepository.findAllUsers().stream().map(user -> new DisplayUser(user, PASSWORD_SALT_SIMPLE)).collect(Collectors.toList())); } - @RequestMapping(path = {"users","/"}, method = RequestMethod.POST, consumes = "application/json", produces = "application/json") + @GetMapping(path = {"access-control/users-admin-fix"}, consumes = "application/json") @ResponseBody - //@PreAuthorize() - public WebGoatUser addUser(@RequestBody WebGoatUser newUser) { + public ResponseEntity> usersFixed() { + var currentUser = userRepository.findByUsername(webSession.getUserName()); + if (currentUser != null && currentUser.isAdmin()) { + return ResponseEntity.ok(userRepository.findAllUsers().stream().map(user -> new DisplayUser(user, PASSWORD_SALT_ADMIN)).collect(Collectors.toList())); + } + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + @PostMapping(path = {"access-control/users", "access-control/users-admin-fix"}, consumes = "application/json", produces = "application/json") + @ResponseBody + public User addUser(@RequestBody User newUser) { try { - userService.addUser(newUser.getUsername(),newUser.getPassword()); - return userService.loadUserByUsername(newUser.getUsername()); + userRepository.save(newUser); + return newUser; } catch (Exception ex) { log.error("Error creating new User", ex); - //TODO: implement error handling ... - } finally { - // no streams or other resources opened ... nothing to do, right? + return null; } - return null; + + //@RequestMapping(path = {"user/{username}","/"}, method = RequestMethod.DELETE, consumes = "application/json", produces = "application/json") + //TODO implement delete method with id param and authorization + } - - //@RequestMapping(path = {"user/{username}","/"}, method = RequestMethod.DELETE, consumes = "application/json", produces = "application/json") - //TODO implement delete method with id param and authorization - } diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHash.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHash.java index d5a8920cd..41815d07f 100644 --- a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHash.java +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHash.java @@ -22,35 +22,33 @@ package org.owasp.webgoat.missing_ac; +import lombok.RequiredArgsConstructor; import org.owasp.webgoat.assignments.AssignmentEndpoint; import org.owasp.webgoat.assignments.AssignmentHints; import org.owasp.webgoat.assignments.AttackResult; -import org.owasp.webgoat.users.UserService; -import org.owasp.webgoat.users.WebGoatUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE; + @RestController -@AssignmentHints({"access-control.hash.hint1","access-control.hash.hint2","access-control.hash.hint3", - "access-control.hash.hint4","access-control.hash.hint5","access-control.hash.hint6","access-control.hash.hint7", - "access-control.hash.hint8","access-control.hash.hint9","access-control.hash.hint10","access-control.hash.hint11","access-control.hash.hint12"}) +@AssignmentHints({"access-control.hash.hint1", "access-control.hash.hint2", "access-control.hash.hint3", "access-control.hash.hint4", "access-control.hash.hint5"}) +@RequiredArgsConstructor public class MissingFunctionACYourHash extends AssignmentEndpoint { - @Autowired - private UserService userService; + private final MissingAccessControlUserRepository userRepository; + @PostMapping(path = "/access-control/user-hash", produces = {"application/json"}) @ResponseBody - public AttackResult completed(String userHash) { - String currentUser = getWebSession().getUserName(); - WebGoatUser user = userService.loadUserByUsername(currentUser); - DisplayUser displayUser = new DisplayUser(user); + public AttackResult simple(String userHash) { + User user = userRepository.findByUsername("Jerry"); + DisplayUser displayUser = new DisplayUser(user, PASSWORD_SALT_SIMPLE); if (userHash.equals(displayUser.getUserHash())) { return success(this).feedback("access-control.hash.success").build(); } else { - return failed(this).feedback("access-control.hash.close").build(); + return failed(this).build(); } } } diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdmin.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdmin.java new file mode 100644 index 000000000..d0c03e070 --- /dev/null +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdmin.java @@ -0,0 +1,60 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.missing_ac; + +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentHints; +import org.owasp.webgoat.assignments.AttackResult; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN; + +@RestController +@AssignmentHints({"access-control.hash.hint6", "access-control.hash.hint7", + "access-control.hash.hint8", "access-control.hash.hint9", "access-control.hash.hint10", "access-control.hash.hint11", "access-control.hash.hint12"}) +public class MissingFunctionACYourHashAdmin extends AssignmentEndpoint { + + private final MissingAccessControlUserRepository userRepository; + + public MissingFunctionACYourHashAdmin(MissingAccessControlUserRepository userRepository) { + this.userRepository = userRepository; + } + + @PostMapping(path = "/access-control/user-hash-fix", produces = {"application/json"}) + @ResponseBody + public AttackResult admin(String userHash) { + //current user should be in the DB + //if not admin then return 403 + + var user = userRepository.findByUsername("Jerry"); + var displayUser = new DisplayUser(user, PASSWORD_SALT_ADMIN); + if (userHash.equals(displayUser.getUserHash())) { + return success(this).feedback("access-control.hash.success").build(); + } else { + return failed(this).feedback("access-control.hash.close").build(); + } + } + +} diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/User.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/User.java new file mode 100644 index 000000000..5025a7d82 --- /dev/null +++ b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/User.java @@ -0,0 +1,15 @@ +package org.owasp.webgoat.missing_ac; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class User { + + private String username; + private String password; + private boolean admin; +} diff --git a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/Users.java b/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/Users.java deleted file mode 100644 index 460bcae58..000000000 --- a/webgoat-lessons/missing-function-ac/src/main/java/org/owasp/webgoat/missing_ac/Users.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ - * - * Copyright (c) 2002 - 2019 Bruce Mayhew - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; if - * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * Getting Source ============== - * - * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. - */ - -package org.owasp.webgoat.missing_ac; - -import org.owasp.webgoat.LessonDataSource; -import org.owasp.webgoat.session.UserSessionData; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.HashMap; - -public class Users { - - private UserSessionData userSessionData; - private LessonDataSource dataSource; - - public Users(UserSessionData userSessionData, LessonDataSource dataSource) { - this.userSessionData = userSessionData; - this.dataSource = dataSource; - } - - @GetMapping(produces = {"application/json"}) - @ResponseBody - protected HashMap getUsers() { - - try (Connection connection = dataSource.getConnection()) { - String query = "SELECT * FROM user_data"; - - try { - Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, - ResultSet.CONCUR_READ_ONLY); - ResultSet results = statement.executeQuery(query); - HashMap allUsersMap = new HashMap(); - - if ((results != null) && (results.first() == true)) { - while (results.next()) { - HashMap userMap = new HashMap<>(); - userMap.put("first", results.getString(1)); - userMap.put("last", results.getString(2)); - userMap.put("cc", results.getString(3)); - userMap.put("ccType", results.getString(4)); - userMap.put("cookie", results.getString(5)); - userMap.put("loginCount", Integer.toString(results.getInt(6))); - allUsersMap.put(results.getInt(0), userMap); - } - userSessionData.setValue("allUsers", allUsersMap); - return allUsersMap; - - } - } catch (SQLException sqle) { - sqle.printStackTrace(); - HashMap errMap = new HashMap() {{ - put("err", sqle.getErrorCode() + "::" + sqle.getMessage()); - }}; - - return new HashMap() {{ - put(0, errMap); - }}; - } catch (Exception e) { - e.printStackTrace(); - HashMap errMap = new HashMap() {{ - put("err", e.getMessage() + "::" + e.getCause()); - }}; - e.printStackTrace(); - return new HashMap() {{ - put(0, errMap); - }}; - - - } finally { - try { - if (connection != null) { - connection.close(); - } - } catch (SQLException sqle) { - sqle.printStackTrace(); - } - } - - } catch (Exception e) { - e.printStackTrace(); - HashMap errMap = new HashMap() {{ - put("err", e.getMessage() + "::" + e.getCause()); - }}; - e.printStackTrace(); - return new HashMap<>() {{ - put(0, errMap); - }}; - - } - return null; - } -} diff --git a/webgoat-lessons/missing-function-ac/src/main/resources/css/ac.css b/webgoat-lessons/missing-function-ac/src/main/resources/css/ac.css new file mode 100644 index 000000000..853250ac2 --- /dev/null +++ b/webgoat-lessons/missing-function-ac/src/main/resources/css/ac.css @@ -0,0 +1,4 @@ +.hidden-menu-item { + display:none; + visibility:hidden; +} \ No newline at end of file diff --git a/webgoat-lessons/missing-function-ac/src/main/resources/db/migration/V2021_11_03_1__ac.sql b/webgoat-lessons/missing-function-ac/src/main/resources/db/migration/V2021_11_03_1__ac.sql new file mode 100644 index 000000000..7a7b09b58 --- /dev/null +++ b/webgoat-lessons/missing-function-ac/src/main/resources/db/migration/V2021_11_03_1__ac.sql @@ -0,0 +1,9 @@ +CREATE TABLE access_control_users( + username varchar(40), + password varchar(40), + admin boolean +); + +INSERT INTO access_control_users VALUES ('Tom', 'qwertyqwerty1234', false); +INSERT INTO access_control_users VALUES ('Jerry', 'doesnotreallymatter', true); +INSERT INTO access_control_users VALUES ('Sylvester', 'testtesttest', false); diff --git a/webgoat-lessons/missing-function-ac/src/main/resources/html/MissingFunctionAC.html b/webgoat-lessons/missing-function-ac/src/main/resources/html/MissingFunctionAC.html index c3465584c..49c42e774 100644 --- a/webgoat-lessons/missing-function-ac/src/main/resources/html/MissingFunctionAC.html +++ b/webgoat-lessons/missing-function-ac/src/main/resources/html/MissingFunctionAC.html @@ -5,6 +5,7 @@

+
@@ -17,7 +18,6 @@ +
+ +
+ +
+
+
+ +

Your Hash:

+
+ + +
+ +
+
+
+ +
+ diff --git a/webgoat-lessons/missing-function-ac/src/main/resources/i18n/WebGoatLabels.properties b/webgoat-lessons/missing-function-ac/src/main/resources/i18n/WebGoatLabels.properties index 4533fe073..f727c90ce 100644 --- a/webgoat-lessons/missing-function-ac/src/main/resources/i18n/WebGoatLabels.properties +++ b/webgoat-lessons/missing-function-ac/src/main/resources/i18n/WebGoatLabels.properties @@ -11,15 +11,15 @@ access-control.hidden-menus.hint3=Look for something a super-user or administato access-control.hash.success=Congrats! You really succeeded when you added the user. access-control.hash.close=Keep trying, this one may take several attempts & steps to achieve. See the hints for help. -access-control.hash.hint1=There is an easier way and a 'harder' way to achieve this, the easier way involves one simple change in a GET request. -access-control.hash.hint2= If you haven't found the hidden menus from the earlier exercise, go do that first. -access-control.hash.hint3=When you look at the users page, there is a hint that more info is viewable by a given role. -access-control.hash.hint4=For the easy way, have you tried tampering the GET request? Different content-types? -access-control.hash.hint5=For the 'easy' way, modify the GET request to /users to include 'Content-Type: application/json' +access-control.hash.hint1=This assignment involves one simple change in a GET request. +access-control.hash.hint2=If you haven't found the hidden menus from the earlier exercise, go do that first. +access-control.hash.hint3=When you look at the users page, there is a hint that more info is viewable by a given role. +access-control.hash.hint4=Have you tried tampering the GET request? Different content-types? +access-control.hash.hint5=Modify the GET request to `/access-control/users` to include 'Content-Type: application/json' access-control.hash.hint6=Now for the harder way ... it builds on the easier way access-control.hash.hint7=If the request to view users, were a 'service' or 'RESTful' endpoint, what would be different about it? access-control.hash.hint8=If you're still looking for hints ... try changing the Content-type header as in the GET request. access-control.hash.hint9=You also need to deliver a proper payload for the request (look at how registration works). This should be formatted in line with the content-type you just defined. -access-control.hash.hint10=You will want to add WEBGOAT_ADMIN for the user's role. Yes, you'd have to guess/fuzz this in a real-world setting. -access-control.hash.hint11=OK, here it is. First, create an admin user ... Change the method to POST, change the content-type to "application/json". And your payload should look something like: {"username":"newUser2","password":"newUser12","matchingPassword":"newUser12","role":"WEBGOAT_ADMIN"} +access-control.hash.hint10=You will want to add your own username with an admin role. Yes, you'd have to guess/fuzz this in a real-world setting. +access-control.hash.hint11=OK, here it is. First, create an admin user ... Change the method to POST, change the content-type to "application/json". And your payload should look something like: {"username":"newUser2","password":"newUser12","admin": "true"} access-control.hash.hint12=Now log in as that user and bring up WebGoat/users. Copy your hash and log back in to your original account and input it there to get credit. diff --git a/webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-03-users.adoc b/webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-03-users.adoc index 739f15dbf..96afc21db 100644 --- a/webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-03-users.adoc +++ b/webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-03-users.adoc @@ -12,4 +12,4 @@ It will likely take multiple steps and multiple attempts to get this one: - You'll need to do some guessing too. - You may need to use another browser/account along the way. -Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'Hash' for your user account. \ No newline at end of file +Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'hash' for Jerry's account. \ No newline at end of file diff --git a/webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-04-users-fixed.adoc b/webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-04-users-fixed.adoc new file mode 100644 index 000000000..50f1af292 --- /dev/null +++ b/webgoat-lessons/missing-function-ac/src/main/resources/lessonPlans/en/missing-function-ac-04-users-fixed.adoc @@ -0,0 +1,5 @@ +== The company fixed the problem, right? + +The company found out the endpoint was a bit too open, they made an emergency fixed and not only admin users can list all users. + +Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'hash' for Jerry's account. diff --git a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/DisplayUserTest.java b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/DisplayUserTest.java index 2b10d7b75..a4bf62910 100644 --- a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/DisplayUserTest.java +++ b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/DisplayUserTest.java @@ -22,23 +22,22 @@ package org.owasp.webgoat.missing_ac; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.owasp.webgoat.users.WebGoatUser; -@ExtendWith(MockitoExtension.class) -public class DisplayUserTest { +import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE; + +class DisplayUserTest { @Test - public void TestDisplayUserCreation() { - DisplayUser displayUser = new DisplayUser(new WebGoatUser("user1","password1")); - assert(!displayUser.isAdmin()); + void testDisplayUserCreation() { + DisplayUser displayUser = new DisplayUser(new User("user1", "password1", true), PASSWORD_SALT_SIMPLE); + Assertions.assertThat(displayUser.isAdmin()).isTrue(); } @Test - public void TesDisplayUserHash() { - DisplayUser displayUser = new DisplayUser(new WebGoatUser("user1","password1")); - assert(displayUser.getUserHash().equals("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc=")); + void testDisplayUserHash() { + DisplayUser displayUser = new DisplayUser(new User("user1", "password1", false), PASSWORD_SALT_SIMPLE); + Assertions.assertThat(displayUser.getUserHash()).isEqualTo("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc="); } } diff --git a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsersTest.java b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsersTest.java index 0046c6c79..4917549eb 100644 --- a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsersTest.java +++ b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACUsersTest.java @@ -28,53 +28,53 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.owasp.webgoat.users.UserService; -import org.owasp.webgoat.users.WebGoatUser; -import org.springframework.test.util.ReflectionTestUtils; +import org.owasp.webgoat.plugins.LessonTest; +import org.owasp.webgoat.session.WebSession; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import java.util.ArrayList; -import java.util.List; - +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; -@ExtendWith(MockitoExtension.class) -public class MissingFunctionACUsersTest { - private MockMvc mockMvc; - @Mock - private UserService userService; +class MissingFunctionACUsersTest extends LessonTest { + + @Autowired + private MissingFunctionAC ac; @BeforeEach - public void setup() { - MissingFunctionACUsers usersController = new MissingFunctionACUsers(); - this.mockMvc = standaloneSetup(usersController).build(); - ReflectionTestUtils.setField(usersController,"userService",userService); - when(userService.getAllUsers()).thenReturn(getUsersList()); + void setup() { + when(webSession.getCurrentLesson()).thenReturn(ac); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test - public void TestContentTypeApplicationJSON () throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/users") - .header("Content-type","application/json")) + void getUsers() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/access-control/users") + .header("Content-type", "application/json")) .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].username", CoreMatchers.is("user1"))) - .andExpect(jsonPath("$[0].userHash",CoreMatchers.is("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc="))) - .andExpect(jsonPath("$[1].admin",CoreMatchers.is(true))); - + .andExpect(jsonPath("$[0].username", CoreMatchers.is("Tom"))) + .andExpect(jsonPath("$[0].userHash", CoreMatchers.is("Mydnhcy00j2b0m6SjmPz6PUxF9WIeO7tzm665GiZWCo="))) + .andExpect(jsonPath("$[0].admin", CoreMatchers.is(false))); } - private List getUsersList() { - List tempUsers = new ArrayList<>(); - tempUsers.add(new WebGoatUser("user1","password1")); - tempUsers.add(new WebGoatUser("user2","password2","WEBGOAT_ADMIN")); - tempUsers.add(new WebGoatUser("user3","password3", "WEBGOAT_USER")); - return tempUsers; + @Test + void addUser() throws Exception { + var user = """ + {"username":"newUser","password":"newUser12","admin": "true"} + """; + mockMvc.perform(MockMvcRequestBuilders.post("/access-control/users") + .header("Content-type", "application/json").content(user)) + .andExpect(status().isOk()); + + mockMvc.perform(MockMvcRequestBuilders.get("/access-control/users") + .header("Content-type", "application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()", is(4))); } - - - } diff --git a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdminTest.java b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdminTest.java new file mode 100644 index 000000000..637bf1a37 --- /dev/null +++ b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionACYourHashAdminTest.java @@ -0,0 +1,44 @@ +package org.owasp.webgoat.missing_ac; + +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.owasp.webgoat.plugins.LessonTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.mockito.Mockito.when; +import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class MissingFunctionACYourHashAdminTest extends LessonTest { + + @Autowired + private MissingFunctionAC ac; + + @BeforeEach + public void setup() { + when(webSession.getCurrentLesson()).thenReturn(ac); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @Test + void solve() throws Exception { + var userHash = new DisplayUser(new User("Jerry", "doesnotreallymatter", true), PASSWORD_SALT_ADMIN).getUserHash(); + mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash-fix") + .param("userHash", userHash)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Congrats! You really succeeded when you added the user."))) + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true))); + } + + @Test + void wrongUserHash() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash-fix") + .param("userHash", "wrong")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false))); + } +} \ No newline at end of file diff --git a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionYourHashTest.java b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionYourHashTest.java index f39077ffb..cc07f2e36 100644 --- a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionYourHashTest.java +++ b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/missing_ac/MissingFunctionYourHashTest.java @@ -25,56 +25,40 @@ package org.owasp.webgoat.missing_ac; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.junit.jupiter.MockitoExtension; -import org.owasp.webgoat.assignments.AssignmentEndpointTest; -import org.owasp.webgoat.users.UserService; +import org.owasp.webgoat.plugins.LessonTest; import org.owasp.webgoat.users.WebGoatUser; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; -@ExtendWith(MockitoExtension.class) -public class MissingFunctionYourHashTest extends AssignmentEndpointTest { - private MockMvc mockMvc; - private DisplayUser mockDisplayUser; - - @Mock - protected UserService userService; +class MissingFunctionYourHashTest extends LessonTest { + @Autowired + private MissingFunctionAC ac; @BeforeEach - public void setUp() { - MissingFunctionACYourHash yourHashTest = new MissingFunctionACYourHash(); - init(yourHashTest); - this.mockMvc = standaloneSetup(yourHashTest).build(); - this.mockDisplayUser = new DisplayUser(new WebGoatUser("user", "userPass")); - ReflectionTestUtils.setField(yourHashTest, "userService", userService); - when(userService.loadUserByUsername(any())).thenReturn(new WebGoatUser("user", "userPass")); + public void setup() { + when(webSession.getCurrentLesson()).thenReturn(ac); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test - public void HashDoesNotMatch() throws Exception { + void hashDoesNotMatch() throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash") .param("userHash", "42")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts"))) .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false))); } @Test - public void hashMatches() throws Exception { + void hashMatches() throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash") - .param("userHash", "2340928sadfajsdalsNfwrBla=")) + .param("userHash", "SVtOlaa+ER+w2eoIIVE5/77umvhcsh5V8UyDLUa1Itg=")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts"))) - .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false))); + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true))); } }