Skip to content

Commit

Permalink
Rework divisions (#8)
Browse files Browse the repository at this point in the history
* Only one division per team
* Only team captain must fulfill division requirements
* In rust challenge point calculation and custom scoring functions
* Custom flag functions
* Email partial partials instead of full reload
  • Loading branch information
mbund authored Oct 13, 2024
1 parent 40f43c1 commit 380735e
Show file tree
Hide file tree
Showing 43 changed files with 1,088 additions and 1,339 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deploy-demo.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Deploy Demo to Fly.io
on:
workflow_dispatch:
push:
branches:
- main
Expand Down
11 changes: 9 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions examples/demo/challenges/my-first-misc/challenge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ description: |
flag: flag{this_is_a_fake_flag}
category: misc
author: mbund
points: dynamic
files:
- src: hello.py
dst: main.py
- url: https://upload.wikimedia.org/wikipedia/commons/1/14/Symmetries_of_square.svg
dst: statue.svg
dst: quadrilaterals.svg
ticket_template: |
# Misc template
healthscript: https://example.com
3 changes: 1 addition & 2 deletions examples/demo/challenges/my-first-pwn/challenge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ description: asjdlfhljasdfhjls
flag: flag{this_is_a_fake_flag}
category: pwn
author: mbund
points: dynamic
files:
- src: hello.py
dst: main.py
- url: https://demo.ctfd.io/files/5bb49e6f5bb44f2eaa4c2e4ee76d685c/statue.jpg
- url: https://upload.wikimedia.org/wikipedia/commons/f/f6/Great_Sphinx_of_Giza_-_20080716a.jpg
dst: statue.jpg
ticket_template: |
# Web template
3 changes: 1 addition & 2 deletions examples/demo/challenges/my-first-web/challenge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ description: |
flag: flag{this_is_a_fake_flag}
category: web
author: mbund
points: dynamic
files:
- src: hello.py
dst: main.py
- url: https://upload.wikimedia.org/wikipedia/commons/1/14/Symmetries_of_square.svg
dst: statue.svg
dst: quadrilaterals.svg
ticket_template: |
# Web template
healthscript: https://example.com
83 changes: 50 additions & 33 deletions examples/demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{path::PathBuf, sync::Arc, time::Duration};
use std::{collections::BTreeMap, path::PathBuf, sync::Arc, time::Duration};

use chrono::prelude::*;
use fake::{
Expand All @@ -12,7 +12,7 @@ use rand::{
Rng,
};
use sha2::{Digest, Sha256};
use tokio::sync::RwLock;
use tokio::sync::{Mutex, RwLock};
use tracing_subscriber::EnvFilter;

use rhombus::{
Expand All @@ -25,6 +25,7 @@ use rhombus::{
libsql::{LibSQL, LibSQLConnection},
provider::{Connection, Database},
},
routes::challenges::ChallengePoints,
settings::Settings,
},
libsql::params,
Expand Down Expand Up @@ -128,8 +129,11 @@ impl Plugin for DemoPlugin {
};

randomize_jwt(context.settings.clone()).await;

solver(libsql.clone(), context.db.clone());
solver(
libsql.clone(),
context.db.clone(),
context.score_type_map.clone(),
);
team_creator(libsql.clone(), context.db.clone());

let plugin_state = DemoState::new(libsql.clone());
Expand Down Expand Up @@ -181,21 +185,6 @@ async fn set_user_to_bot(libsql: Arc<LibSQL>, user_id: i64) -> Result<()> {
Ok(())
}

async fn join_to_divisons(
db: Connection,
division_ids: &[i64],
user_id: i64,
team_id: i64,
) -> Result<()> {
for division_id in division_ids {
db.set_user_division(user_id, team_id, *division_id, true)
.await?;
db.set_team_division(team_id, *division_id, true).await?;
}

Ok(())
}

async fn create_team(libsql: Arc<LibSQL>, db: Connection) -> Result<()> {
let dummy_user = create_dummy_user();

Expand All @@ -208,11 +197,9 @@ async fn create_team(libsql: Arc<LibSQL>, db: Connection) -> Result<()> {
.collect::<Vec<_>>();

let mut rng = rand::rngs::OsRng;
let num_divisions = rng.gen_range(1..=division_ids.len().max(1));
let division_ids = division_ids
.choose_multiple(&mut rng, num_divisions)
.copied()
.collect::<Vec<_>>();
let Some(division_id) = division_ids.choose(&mut rng) else {
return Ok(());
};

let Some((user_id, team_id)) = db
.upsert_user_by_credentials(
Expand All @@ -225,7 +212,10 @@ async fn create_team(libsql: Arc<LibSQL>, db: Connection) -> Result<()> {
panic!()
};

join_to_divisons(db.clone(), &division_ids, user_id, team_id).await?;
let team = db.get_team_from_id(team_id).await?;
let now = Utc::now();
db.set_team_division(team_id, team.division_id, *division_id, now)
.await?;
set_user_to_bot(libsql.clone(), user_id).await?;

let num_members = rng.gen_range(0..3);
Expand All @@ -242,21 +232,26 @@ async fn create_team(libsql: Arc<LibSQL>, db: Connection) -> Result<()> {
else {
continue;
};
join_to_divisons(db.clone(), &division_ids, user_id, team_id).await?;
set_user_to_bot(libsql.clone(), user_id).await?;
db.add_user_to_team(user_id, team_id, None).await?;
}

Ok(())
}

async fn solve_challenge(libsql: Arc<LibSQL>, db: Connection) -> Result<()> {
async fn solve_challenge(
libsql: Arc<LibSQL>,
db: Connection,
score_type_map: Arc<Mutex<BTreeMap<String, Box<dyn ChallengePoints + Send + Sync>>>>,
) -> Result<()> {
let conn = libsql.connect().await?;

if let Some((user_id, team_id)) = conn
if let Some((user_id, team_id, division_id)) = conn
.query(
"
SELECT id, team_id FROM rhombus_user
SELECT rhombus_user.id, rhombus_user.team_id, rhombus_team.division_id
FROM rhombus_user
JOIN rhombus_team ON rhombus_user.team_id = rhombus_team.id
WHERE is_bot = 1
ORDER BY RANDOM()
LIMIT 1
Expand All @@ -266,23 +261,45 @@ async fn solve_challenge(libsql: Arc<LibSQL>, db: Connection) -> Result<()> {
.await?
.next()
.await?
.map(|row| (row.get::<i64>(0).unwrap(), row.get::<i64>(1).unwrap()))
.map(|row| {
(
row.get::<i64>(0).unwrap(),
row.get::<i64>(1).unwrap(),
row.get::<i64>(2).unwrap(),
)
})
{
let mut rng = rand::rngs::OsRng;
if let Some(challenge) = db.get_challenges().await?.challenges.choose(&mut rng) {
db.solve_challenge(user_id, team_id, challenge).await?;
let user = db.get_user_from_id(user_id).await?;
let team = db.get_team_from_id(team_id).await?;
let next_points = score_type_map
.lock()
.await
.get(challenge.score_type.as_str())
.unwrap()
.next(&user, &team, challenge)
.await
.unwrap();

db.solve_challenge(user_id, team_id, division_id, challenge, next_points)
.await?;
tracing::info!(user_id, challenge_id = challenge.id, "Solved challenge");
}
}

Ok(())
}

fn solver(libsql: Arc<LibSQL>, db: Connection) {
fn solver(
libsql: Arc<LibSQL>,
db: Connection,
score_type_map: Arc<Mutex<BTreeMap<String, Box<dyn ChallengePoints + Send + Sync>>>>,
) {
tokio::task::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(10)).await;
_ = solve_challenge(libsql.clone(), db.clone()).await;
_ = solve_challenge(libsql.clone(), db.clone(), score_type_map.clone()).await;
}
});
}
Expand Down
5 changes: 3 additions & 2 deletions examples/standalone/challenges/my-first-pwn/challenge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ description: |
flag: flag{this_is_a_fake_flag}
category: pwn
author: mbund
points: dynamic
score_type: static
points: 100
files:
- src: hello.py
dst: main.py
- url: https://demo.ctfd.io/files/5bb49e6f5bb44f2eaa4c2e4ee76d685c/statue.jpg
- url: https://upload.wikimedia.org/wikipedia/commons/f/f6/Great_Sphinx_of_Giza_-_20080716a.jpg
dst: statue.jpg
ticket_template: |
# Web template
6 changes: 4 additions & 2 deletions examples/standalone/challenges/my-first-web/challenge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ description: |
flag: flag{this_is_a_fake_flag}
category: web
author: mbund
points: dynamic
score_type: dynamic
dynamic:
decay: 20
files:
- src: hello.py
dst: main.py
- url: https://upload.wikimedia.org/wikipedia/commons/1/14/Symmetries_of_square.svg
dst: statue.svg
dst: quadrilaterals.svg
ticket_template: |
# Web template
healthscript: https://example.com
6 changes: 3 additions & 3 deletions examples/standalone/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ divisions:
- name: Undergraduate
description: Undergraduate university students globally
email_regex: ^.*.edu$
requirement: Must verify a valid university .edu email address. Max of up to 4 players
requirement: Must verify a valid university .edu email address
- name: OSU
description: OSU undergraduate students
email_regex: ^.*\.\d+@(buckeyemail\.)?osu\.edu$
requirement: Must verify a valid OSU email address
requirement: Must verify a valid OSU email address. Max of up to 4 players
max_players: 4
discord:
guild_id: 1160610137703186636
client_id: 1160076447977848945
# author_role_id: 1198496803130183770
# first_blood_channel_id: 1240422645938389102
first_blood_channel_id: 1240422645938389102
support_channel_id: 1173026578385617010
# verified_role_id: 1170429782270431283
2 changes: 1 addition & 1 deletion rhombus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mail-parser = "0.9.4"
markdown = "1.0.0-alpha.20"
mime_guess = "2.0.5"
minify-html-onepass = "0.15.0"
minijinja = { version = "2.2.0", features = ["json", "loader"] }
minijinja = { version = "2.3.1", features = ["json", "loader", "speedups"] }
petname = "2.0.2"
pin-project-lite = "0.2.14"
poise = "0.6.1"
Expand Down
11 changes: 5 additions & 6 deletions rhombus/build.mjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { build } from "esbuild";
import { context } from "esbuild";
import { build, context } from "esbuild";
import { solidPlugin } from "esbuild-plugin-solid";

const options = {
entryPoints: ['frontend/app.tsx'],
entryPoints: ["frontend/app.tsx"],
bundle: true,
minify: true,
outfile: 'static/app.js',
outfile: "static/app.js",
globalName: "rhombus",
plugins: [solidPlugin()],
};

if (process.env.WATCH === 'true') {
if (process.env.WATCH === "true") {
const ctx = await context(options);

await ctx.watch();
console.log('Watching client js...');
console.log("Watching client js...");
} else {
build(options).catch(() => process.exit(1));
}
Loading

0 comments on commit 380735e

Please sign in to comment.