-
-
Notifications
You must be signed in to change notification settings - Fork 372
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add cli for scaffolding a new challenge
create the class file and asciidoc file Refs: #555
- Loading branch information
Nanne Baars
committed
May 5, 2023
1 parent
5ad18a7
commit 26bd277
Showing
7 changed files
with
251 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,3 +72,4 @@ js/node/ | |
js/node_modules/ | ||
node_modules | ||
.npm | ||
/cli/Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
use std::collections::BTreeMap; | ||
use std::fs::File; | ||
use std::io::Write; | ||
use std::path::PathBuf; | ||
|
||
use handlebars::Handlebars; | ||
use walkdir::WalkDir; | ||
|
||
use crate::{Difficulty, Platform, Technology}; | ||
|
||
pub struct Challenge { | ||
pub number: u8, | ||
pub technology: Technology, | ||
pub difficulty: Difficulty, | ||
pub project_directory: PathBuf, | ||
pub platform: Platform, | ||
} | ||
|
||
impl std::fmt::Display for Challenge { | ||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
write!(f, "Technology: {}, Difficulty: {}", self.technology, self.difficulty) | ||
} | ||
} | ||
|
||
pub fn create_challenge(challenge: &Challenge) { | ||
let challenge_number = &challenge.number.to_string(); | ||
let challenge_name = String::from("Challenge") + challenge_number + ".java"; | ||
let challenge_exists = check_challenge_exists(challenge); | ||
|
||
if challenge_exists { | ||
panic!("{:?} already exists", challenge_name); | ||
} | ||
|
||
println!("Creating challenge {} in {}", challenge_number, challenge.project_directory.display()); | ||
create_challenge_class_file(challenge, challenge_number, challenge_name); | ||
create_documentation_files(challenge, challenge_number); | ||
} | ||
|
||
fn create_documentation_files(challenge: &Challenge, challenge_number: &String) { | ||
let challenge_documentation_path = challenge.project_directory.join("src/main/resources/explanations/"); | ||
create_documentation_file(challenge_documentation_path.join(format!("challenge{}.adoc", challenge_number))); | ||
create_documentation_file(challenge_documentation_path.join(format!("challenge{}_hint.adoc", challenge_number))); | ||
create_documentation_file(challenge_documentation_path.join(format!("challenge{}_explanation.adoc", challenge_number))); | ||
} | ||
|
||
fn create_documentation_file(filename: PathBuf) { | ||
File::create(filename).expect("Unable to create challenge documentation file"); | ||
} | ||
|
||
fn create_challenge_class_file(challenge: &Challenge, challenge_number: &String, challenge_name: String) { | ||
const CHALLENGE_TEMPLATE: &str = "src/main/resources/challenge.hbs"; | ||
let challenge_source_path = challenge.project_directory.join("src/main/java/org/owasp/wrongsecrets/challenges"); | ||
|
||
let mut handlebars = Handlebars::new(); | ||
handlebars.register_template_file("challenge", challenge.project_directory.join(CHALLENGE_TEMPLATE)).unwrap(); | ||
let mut data = BTreeMap::new(); | ||
data.insert("challenge_number".to_string(), challenge_number); | ||
let challenge_source_content = handlebars.render("challenge", &data).expect("Unable to render challenge template"); | ||
let mut class_file = File::create(challenge_source_path.join(challenge.platform.to_string()).join(challenge_name)).expect("Unable to create challenge source file"); | ||
class_file.write(challenge_source_content.as_bytes()).expect("Unable to write challenge source file"); | ||
} | ||
|
||
//File API has `create_new` but it is still experimental in the nightly build, let loop and check if it exists for now | ||
fn check_challenge_exists(challenge: &Challenge) -> bool { | ||
let challenges_directory = challenge.project_directory.join("src/main/java/org/owasp/wrongsecrets/challenges"); | ||
let challenge_name = String::from("Challenge") + &challenge.number.to_string() + ".java"; | ||
|
||
let challenge_exists = WalkDir::new(challenges_directory) | ||
.into_iter() | ||
.filter_map(|e| e.ok()) | ||
.any(|e| { | ||
match e.file_name().to_str() { | ||
None => { false } | ||
Some(name) => { | ||
name.eq(challenge_name.as_str()) | ||
} | ||
} | ||
}); | ||
challenge_exists | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,78 @@ | ||
use clap::{arg, Command}; | ||
use std::path::PathBuf; | ||
|
||
use crate::enums::{Difficulty, Technology}; | ||
use clap::arg; | ||
use clap::{Parser, Subcommand}; | ||
|
||
use crate::challenge::Challenge; | ||
use crate::enums::{Difficulty, Platform, Technology}; | ||
|
||
mod enums; | ||
mod challenge; | ||
|
||
fn cli() -> Command { | ||
Command::new("cli") | ||
.about("A CLI for WrongSecrets") | ||
.subcommand_required(true) | ||
.arg_required_else_help(true) | ||
.allow_external_subcommands(true) | ||
.subcommand( | ||
Command::new("challenge") | ||
.about("Create a new challenge") | ||
.arg_required_else_help(true) | ||
.arg( | ||
arg!(--"difficulty" <DIFFICULTY>) | ||
.short('d') | ||
.num_args(0..=1) | ||
.value_parser(clap::builder::EnumValueParser::<Difficulty>::new()) | ||
.num_args(0..=1) | ||
.default_value("easy") | ||
) | ||
.arg( | ||
arg!(--"technology" <TECHNOLOGY>) | ||
.short('t') | ||
.value_parser(clap::builder::EnumValueParser::<Technology>::new()) | ||
.num_args(0..=1) | ||
.require_equals(true) | ||
.default_value("git") | ||
) | ||
) | ||
#[derive(Debug, Parser)] | ||
#[command(name = "cli")] | ||
#[command(about = "A CLI for WrongSecrets", long_about = None)] | ||
struct Cli { | ||
#[command(subcommand)] | ||
command: Commands, | ||
} | ||
|
||
fn main() { | ||
let matches = cli().get_matches(); | ||
#[derive(Debug, Subcommand)] | ||
enum Commands { | ||
#[command(arg_required_else_help = true, name = "challenge", about = "Create a new challenge")] | ||
ChallengeCommand { | ||
//We could infer this from the directory structure but another PR could already have added the challenge with this number | ||
#[arg( | ||
long, | ||
short, | ||
value_name = "NUMBER")] | ||
number: u8, | ||
#[arg( | ||
long, | ||
short, | ||
value_name = "DIFFICULTY", | ||
num_args = 0..=1, | ||
default_value_t = Difficulty::Easy, | ||
default_missing_value = "easy", | ||
value_enum | ||
)] | ||
difficulty: Difficulty, | ||
#[arg( | ||
long, | ||
short, | ||
value_name = "TECHNOLOGY", | ||
num_args = 0..=1, | ||
default_value_t = Technology::Git, | ||
default_missing_value = "git", | ||
value_enum | ||
)] | ||
technology: Technology, | ||
#[arg( | ||
long, | ||
short, | ||
value_name = "PLATFORM", | ||
num_args = 0..=1, | ||
value_enum | ||
)] | ||
platform: Platform, | ||
#[arg(required = true)] | ||
project_directory: PathBuf, | ||
} | ||
} | ||
|
||
match matches.subcommand() { | ||
Some(("challenge", sub_matches)) => { | ||
println!( | ||
"Create new challenge with difficulty: {}", | ||
sub_matches.get_one::<Difficulty>("difficulty").expect("") | ||
); | ||
fn main() { | ||
let args = Cli::parse(); | ||
match args.command { | ||
Commands::ChallengeCommand { | ||
number, | ||
difficulty, | ||
technology, | ||
platform, | ||
project_directory | ||
} => { | ||
project_directory.try_exists().expect("Unable to find project directory"); | ||
let challenge = Challenge { number, difficulty, technology, platform, project_directory }; | ||
challenge::create_challenge(&challenge); | ||
} | ||
_ => unreachable!() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package org.owasp.wrongsecrets.challenges.{{platform}}; | ||
|
||
import org.owasp.wrongsecrets.RuntimeEnvironment; | ||
import org.owasp.wrongsecrets.ScoreCard; | ||
import org.owasp.wrongsecrets.challenges.Challenge; | ||
import org.owasp.wrongsecrets.challenges.Difficulty; | ||
import org.owasp.wrongsecrets.challenges.Spoiler; | ||
import org.springframework.core.annotation.Order; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.List; | ||
|
||
{{ | ||
extra_imports | ||
}} | ||
|
||
@Component | ||
@Order(0) | ||
public class Challenge{{challenge_number}} extends Challenge { | ||
|
||
public Challenge{{challenge_number}}(ScoreCard scoreCard) { | ||
super(scoreCard); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public Spoiler spoiler() { | ||
return new Spoiler(getData()); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
protected boolean answerCorrect(String answer) { | ||
return getData().equals(answer); | ||
} | ||
|
||
@Override | ||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() { | ||
return List.of(DOCKER); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public int difficulty() { | ||
return Difficulty.{{difficulty}}; | ||
} | ||
|
||
@Override | ||
public String getTech() { | ||
return ChallengeTechnology.Tech{{technology}}.id; | ||
} | ||
|
||
@Override | ||
public boolean isLimitedWhenOnlineHosted() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean canRunInCTFMode() { | ||
return true; | ||
} | ||
|
||
private String getData() { | ||
return "<<replace with correct answer>>"; | ||
} | ||
} |