diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/VotesEndpoint.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/VotesEndpoint.java index 109780cd3..b233bb509 100644 --- a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/VotesEndpoint.java +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/VotesEndpoint.java @@ -1,10 +1,7 @@ package org.owasp.webgoat.plugin.challenge5; import com.google.common.collect.Maps; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwt; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.*; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -39,7 +36,7 @@ public class VotesEndpoint { private Map votes = Maps.newHashMap(); @PostConstruct - private void initVotes() { + public void initVotes() { votes.put("Admin lost password", new Vote("Admin lost password", "In this challenge you will need to help the admin and find the password in order to login", "challenge1-small.png", "challenge1.png", 36000, totalVotes)); @@ -89,13 +86,13 @@ public class VotesEndpoint { Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user"); boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); - if ("Guest".equals(user)) { + if ("Guest".equals(user) || !validUsers.contains(user)) { value.setSerializationView(Views.GuestView.class); } else { - ((Collection)value.getValue()).forEach(v -> v.setFlag(FLAGS.get(5))); + ((Collection) value.getValue()).forEach(v -> v.setFlag(FLAGS.get(5))); value.setSerializationView(isAdmin ? Views.AdminView.class : Views.UserView.class); } - } catch (IllegalArgumentException e) { + } catch (JwtException e) { value.setSerializationView(Views.GuestView.class); } } @@ -109,13 +106,17 @@ public class VotesEndpoint { if (StringUtils.isEmpty(accessToken)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } else { - Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); - Claims claims = (Claims) jwt.getBody(); - String user = (String) claims.get("user"); - if (validUsers.contains(user)) { - ofNullable(votes.get(title)).ifPresent(v -> v.incrementNumberOfVotes(totalVotes)); - return ResponseEntity.accepted().build(); - } else { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + if (validUsers.contains(user)) { + ofNullable(votes.get(title)).ifPresent(v -> v.incrementNumberOfVotes(totalVotes)); + return ResponseEntity.accepted().build(); + } else { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } catch (JwtException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java index 1fb4e73d1..87710a6b7 100644 --- a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java +++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java @@ -32,7 +32,6 @@ public class ShopEndpointTest { @Test public void getSuperCoupon() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/" + SUPER_COUPON_CODE)) - .andDo(MockMvcResultHandlers.print()) .andExpect(jsonPath("$.code", CoreMatchers.is(SUPER_COUPON_CODE))) .andExpect(jsonPath("$.discount", CoreMatchers.is(100))); } @@ -48,7 +47,6 @@ public class ShopEndpointTest { @Test public void askForUnknownCouponCode() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/does-not-exists")) - .andDo(MockMvcResultHandlers.print()) .andExpect(jsonPath("$.code", CoreMatchers.is("no"))) .andExpect(jsonPath("$.discount", CoreMatchers.is(0))); } @@ -56,7 +54,6 @@ public class ShopEndpointTest { @Test public void fetchAllTheCouponsShouldContainGetItForFree() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/")) - .andDo(MockMvcResultHandlers.print()) .andExpect(jsonPath("$.codes[3].code", is("get_it_for_free"))); } diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge5/VotesEndpointTest.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge5/VotesEndpointTest.java new file mode 100644 index 000000000..876bcfdac --- /dev/null +++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge5/VotesEndpointTest.java @@ -0,0 +1,163 @@ +package org.owasp.webgoat.plugin.challenge5; + +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.owasp.webgoat.plugin.Flag; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; + +import javax.servlet.http.Cookie; + +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; + +/** + * @author nbaars + * @since 5/2/17. + */ +@RunWith(MockitoJUnitRunner.class) +public class VotesEndpointTest { + + private MockMvc mockMvc; + + @Before + public void setup() { + VotesEndpoint votesEndpoint = new VotesEndpoint(); + votesEndpoint.initVotes(); + new Flag().initFlags(); + this.mockMvc = standaloneSetup(votesEndpoint).build(); + } + + @Test + public void loginWithUnknownUser() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/login") + .param("user", "uknown")) + .andExpect(unauthenticated()); + } + + @Test + public void loginWithTomShouldGiveJwtToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/login") + .param("user", "Tom")) + .andExpect(status().isOk()).andExpect(cookie().exists("access_token")); + } + + @Test + public void loginWithGuestShouldNotGiveJwtToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/login") + .param("user", "Guest")) + .andExpect(unauthenticated()).andExpect(cookie().value("access_token", "")); + } + + @Test + public void userShouldSeeMore() throws Exception { + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login") + .param("user", "Tom")) + .andExpect(status().isOk()).andExpect(cookie().exists("access_token")).andReturn(); + mockMvc.perform(MockMvcRequestBuilders.get("/votings") + .cookie(mvcResult.getResponse().getCookie("access_token"))) + .andExpect(jsonPath("$.[*].numberOfVotes").exists()); + } + + @Test + public void guestShouldNotSeeNumberOfVotes() throws Exception { + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login") + .param("user", "Guest")) + .andExpect(unauthenticated()).andExpect(cookie().exists("access_token")).andReturn(); + mockMvc.perform(MockMvcRequestBuilders.get("/votings") + .cookie(mvcResult.getResponse().getCookie("access_token"))) + .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist()); + } + + @Test + public void adminShouldSeeFlags() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings") + .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJhZG1pbiI6InRydWUiLCJ1c2VyIjoiSmVycnkifQ."))) + .andExpect(jsonPath("$.[*].flag").isNotEmpty()); + } + + @Test + public void votingIsNotAllowedAsGuest() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/votings/Get it for free")) + .andExpect(unauthenticated()); + } + + @Test + public void normalUserShouldBeAbleToVote() throws Exception { + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login") + .param("user", "Tom")) + .andExpect(status().isOk()).andExpect(cookie().exists("access_token")).andReturn(); + mockMvc.perform(MockMvcRequestBuilders.post("/votings/Get it for free") + .cookie(mvcResult.getResponse().getCookie("access_token"))); + mockMvc.perform(MockMvcRequestBuilders.get("/votings/") + .cookie(mvcResult.getResponse().getCookie("access_token"))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$..[?(@.title == 'Get it for free')].numberOfVotes", CoreMatchers.hasItem(20001))); + } + + @Test + public void votingForUnknownLessonShouldNotCrash() throws Exception { + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/votings/login") + .param("user", "Tom")) + .andExpect(status().isOk()).andExpect(cookie().exists("access_token")).andReturn(); + mockMvc.perform(MockMvcRequestBuilders.post("/votings/UKNOWN_VOTE") + .cookie(mvcResult.getResponse().getCookie("access_token"))).andExpect(status().isAccepted()); + } + + @Test + public void votingWithInvalidToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/votings/UKNOWN_VOTE") + .cookie(new Cookie("access_token", "abc"))).andExpect(unauthenticated()); + } + + @Test + public void gettingVotesWithInvalidToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/") + .cookie(new Cookie("access_token", "abc"))).andExpect(unauthenticated()); + } + + @Test + public void gettingVotesWithUnknownUserInToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/") + .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJhZG1pbiI6InRydWUiLCJ1c2VyIjoiVW5rbm93biJ9."))) + .andExpect(unauthenticated()) + .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist()); + } + + @Test + public void gettingVotesForUnknownShouldWork() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/") + .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiVW5rbm93biJ9."))) + .andExpect(unauthenticated()) + .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist()); + } + + @Test + public void gettingVotesForKnownWithoutAdminFieldShouldWork() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/") + .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiVG9tIn0."))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[*].numberOfVotes").exists()); + } + + @Test + public void gettingVotesWithEmptyToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/votings/") + .cookie(new Cookie("access_token", ""))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[*].numberOfVotes").doesNotExist()); + } + + @Test + public void votingAsUnknownUserShouldNotBeAllowed() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/votings/Get it for free") + .cookie(new Cookie("access_token", "eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiVW5rbm93biJ9."))) + .andExpect(unauthenticated()); + } +} \ No newline at end of file