■ ■ ■ ■ ■ ■
src/main/java/org/owasp/wrongsecrets/challenges/ChallengesAPIController.java
| 1 | + | package org.owasp.wrongsecrets.challenges; |
| 2 | + | |
| 3 | + | import com.nimbusds.jose.shaded.json.JSONArray; |
| 4 | + | import com.nimbusds.jose.shaded.json.JSONObject; |
| 5 | + | import lombok.extern.slf4j.Slf4j; |
| 6 | + | import org.asciidoctor.Asciidoctor; |
| 7 | + | import org.asciidoctor.OptionsBuilder; |
| 8 | + | import org.owasp.wrongsecrets.RuntimeEnvironment; |
| 9 | + | import org.owasp.wrongsecrets.ScoreCard; |
| 10 | + | import org.owasp.wrongsecrets.asciidoc.TemplateGenerator; |
| 11 | + | import org.springframework.beans.factory.annotation.Autowired; |
| 12 | + | import org.springframework.http.MediaType; |
| 13 | + | import org.springframework.util.ResourceUtils; |
| 14 | + | import org.springframework.web.bind.annotation.GetMapping; |
| 15 | + | import org.springframework.web.bind.annotation.RestController; |
| 16 | + | |
| 17 | + | import java.io.BufferedReader; |
| 18 | + | import java.io.IOException; |
| 19 | + | import java.io.InputStreamReader; |
| 20 | + | import java.util.ArrayList; |
| 21 | + | import java.util.List; |
| 22 | + | |
| 23 | + | @Slf4j |
| 24 | + | @RestController |
| 25 | + | public class ChallengesAPIController { |
| 26 | + | |
| 27 | + | private final ScoreCard scoreCard; |
| 28 | + | private final List<ChallengeUI> challenges; |
| 29 | + | |
| 30 | + | private final List<String> descriptions; |
| 31 | + | |
| 32 | + | private final List<String> hints; |
| 33 | + | |
| 34 | + | private final TemplateGenerator templateGenerator; |
| 35 | + | |
| 36 | + | public ChallengesAPIController(ScoreCard scoreCard, List<ChallengeUI> challenges, RuntimeEnvironment runtimeEnvironment, TemplateGenerator templateGenerator) { |
| 37 | + | this.scoreCard = scoreCard; |
| 38 | + | this.challenges = challenges; |
| 39 | + | this.descriptions = new ArrayList<>(); |
| 40 | + | this.hints = new ArrayList<>(); |
| 41 | + | this.templateGenerator = templateGenerator; |
| 42 | + | } |
| 43 | + | |
| 44 | + | |
| 45 | + | @GetMapping(value = {"/api/Challenges", "/api/challenges"}, produces = MediaType.APPLICATION_JSON_VALUE) |
| 46 | + | public String getChallenges() { |
| 47 | + | if (descriptions.size() == 0) { |
| 48 | + | initiaLizeHintsAndDescriptions(); |
| 49 | + | } |
| 50 | + | JSONObject json = new JSONObject(); |
| 51 | + | JSONArray jsonArray = new JSONArray(); |
| 52 | + | for (int i = 0; i < challenges.size(); i++) { |
| 53 | + | JSONObject jsonChallenge = new JSONObject(); |
| 54 | + | jsonChallenge.put("id", i); |
| 55 | + | jsonChallenge.put("name", challenges.get(i).getName()); |
| 56 | + | jsonChallenge.put("key", challenges.get(i).getExplanation()); |
| 57 | + | jsonChallenge.put("category", getCategory(challenges.get(i))); |
| 58 | + | jsonChallenge.put("description", descriptions.get(i)); |
| 59 | + | jsonChallenge.put("hint", hints.get(i)); |
| 60 | + | jsonChallenge.put("solved", scoreCard.getChallengeCompleted(challenges.get(i).getChallenge())); |
| 61 | + | jsonChallenge.put("disabledEnv", getDisabledEnv(challenges.get(i))); |
| 62 | + | jsonChallenge.put("difficulty", challenges.get(i).getChallenge().difficulty()); |
| 63 | + | jsonArray.add(jsonChallenge); |
| 64 | + | } |
| 65 | + | json.put("status", "success"); |
| 66 | + | json.put("data", jsonArray); |
| 67 | + | String result = json.toJSONString(); |
| 68 | + | log.info("returning {}", result); |
| 69 | + | return result; |
| 70 | + | } |
| 71 | + | |
| 72 | + | private String getCategory(ChallengeUI challengeUI) { |
| 73 | + | return switch (challengeUI.getChallenge().supportedRuntimeEnvironments().get(0)) { |
| 74 | + | case DOCKER, HEROKU_DOCKER -> "Docker"; |
| 75 | + | case GCP, AWS, AZURE -> "Cloud"; |
| 76 | + | case VAULT -> "Vault"; |
| 77 | + | case K8S -> "Kubernetes"; |
| 78 | + | }; |
| 79 | + | } |
| 80 | + | |
| 81 | + | private void initiaLizeHintsAndDescriptions() { |
| 82 | + | log.info("Initialize hints and descriptions"); |
| 83 | + | challenges.forEach(challengeUI -> { //note requires mvn install to generate the html files! |
| 84 | + | try { |
| 85 | + | String hint = templateGenerator.generate("explanations/" + challengeUI.getExplanation() + "_hint"); |
| 86 | + | hints.add(hint); |
| 87 | + | String description = templateGenerator.generate("explanations/" + challengeUI.getExplanation()); |
| 88 | + | descriptions.add(description); |
| 89 | + | } catch (IOException e) { |
| 90 | + | String rawHint = extractResource("classpath:explanations/" + challengeUI.getExplanation() + "_hint.adoc"); |
| 91 | + | String hint = Asciidoctor.Factory.create().convert(rawHint, OptionsBuilder.options().build()); |
| 92 | + | hints.add(hint); |
| 93 | + | String rawDescription = extractResource("classpath:explanations/" + challengeUI.getExplanation() + ".adoc"); |
| 94 | + | String description = Asciidoctor.Factory.create().convert(rawDescription, OptionsBuilder.options().build()); |
| 95 | + | descriptions.add(description); |
| 96 | + | throw new RuntimeException(e); |
| 97 | + | } |
| 98 | + | |
| 99 | + | }); |
| 100 | + | } |
| 101 | + | |
| 102 | + | private String extractResource(String resourceName) { |
| 103 | + | try { |
| 104 | + | var resource = ResourceUtils.getURL(resourceName); |
| 105 | + | final StringBuilder resourceStringbuilder = new StringBuilder(); |
| 106 | + | new BufferedReader( |
| 107 | + | new InputStreamReader(resource.openStream()) |
| 108 | + | ).lines().forEach(s -> { |
| 109 | + | resourceStringbuilder.append(s); |
| 110 | + | }); |
| 111 | + | return resourceStringbuilder.toString(); |
| 112 | + | } catch (IOException e) { |
| 113 | + | throw new RuntimeException(e); |
| 114 | + | } |
| 115 | + | } |
| 116 | + | |
| 117 | + | private String getDisabledEnv(ChallengeUI challenge) { |
| 118 | + | if (!challenge.getChallenge().supportedRuntimeEnvironments().contains(RuntimeEnvironment.Environment.DOCKER)) { |
| 119 | + | return "Docker"; |
| 120 | + | } |
| 121 | + | return null; |
| 122 | + | } |
| 123 | + | } |
| 124 | + | |