From 5c025dbf1e6a47b3d34c2f72b13f961bc4c2a779 Mon Sep 17 00:00:00 2001
From: Spencer Pogorzelski <34356756+Scoder12@users.noreply.github.com>
Date: Mon, 15 Jan 2024 21:17:11 -0800
Subject: [PATCH 001/123] git clone --depth 1 (#285)
---
challenge/Dockerfile | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/challenge/Dockerfile b/challenge/Dockerfile
index 8e0369a8f..02ce6585c 100644
--- a/challenge/Dockerfile
+++ b/challenge/Dockerfile
@@ -162,13 +162,13 @@ RUN <
Date: Fri, 12 Jan 2024 17:46:24 -0700
Subject: [PATCH 002/123] rewrite get_user_belts to expose the belt
requirements to importation
---
dojo_plugin/utils/dojo/__init__.py | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/dojo_plugin/utils/dojo/__init__.py b/dojo_plugin/utils/dojo/__init__.py
index a1c417a52..e6ab3fa46 100644
--- a/dojo_plugin/utils/dojo/__init__.py
+++ b/dojo_plugin/utils/dojo/__init__.py
@@ -398,17 +398,18 @@ def get_current_dojo_challenge(user=None):
.first()
)
+BELT_REQUIREMENTS = {
+ "orange": ["intro-to-cybersecurity"],
+ "yellow": ["program-security"],
+ "green": ["system-security"],
+ "blue": ["software-exploitation"],
+}
def get_user_belts(user):
- belts = {
- "Orange Belt": ["intro-to-cybersecurity"],
- "Yellow Belt": ["intro-to-cybersecurity", "program-security"],
- "Green Belt": ["intro-to-cybersecurity", "program-security", "system-security"],
- "Blue Belt": ["intro-to-cybersecurity", "program-security", "system-security", "software-exploitation"],
- }
- result = []
- for belt, dojo_ids in belts.items():
- dojos = Dojos.query.filter(Dojos.official, Dojos.id.in_(dojo_ids))
- if all(dojo.completed(user) for dojo in dojos):
- result.append(belt)
+ result = [ ]
+ for belt, dojo_id in BELT_REQUIREMENTS.items():
+ dojo = Dojos.query.filter(Dojos.official, Dojos.id == dojo_id).one()
+ if not dojo.completed(user):
+ break
+ result.append(belt.title() + " Belt")
return result
From 776b4ebee0bd13fb3019779304efa22c042b9386 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 12 Jan 2024 17:46:41 -0700
Subject: [PATCH 003/123] add logic for getting a list of dojo completers
---
dojo_plugin/models/__init__.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/dojo_plugin/models/__init__.py b/dojo_plugin/models/__init__.py
index f0e8a7864..be1d79396 100644
--- a/dojo_plugin/models/__init__.py
+++ b/dojo_plugin/models/__init__.py
@@ -194,6 +194,12 @@ def viewable(cls, id=None, user=None):
def solves(self, **kwargs):
return DojoChallenges.solves(dojo=self, **kwargs)
+ def completers(self):
+ sq = Solves.query.join(DojoChallenges, Solves.challenge_id == DojoChallenges.challenge_id).add_columns(
+ Solves.user_id.label("solve_user_id"), db.func.count().label("solve_count"), db.func.max(Solves.date).label("last_solve")
+ ).filter(DojoChallenges.dojo == self).group_by(Solves.user_id).subquery()
+ return Users.query.join(sq).filter_by(solve_count=len(self.challenges)).order_by(sq.columns.last_solve).all()
+
def completed(self, user):
return self.solves(user=user, ignore_visibility=True, ignore_admins=False).count() == len(self.challenges)
From e14e13ec96ea249f425d309af3ae8750d46b9609 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 12 Jan 2024 18:44:12 -0700
Subject: [PATCH 004/123] fix get_belts() and remove obsolete code
---
dojo_plugin/api/v1/belts.py | 36 ++++++----------
dojo_plugin/api/v1/scoreboard.py | 2 +-
dojo_plugin/models/__init__.py | 9 +++-
dojo_plugin/utils/__init__.py | 69 ------------------------------
dojo_plugin/utils/dojo/__init__.py | 8 ++--
5 files changed, 24 insertions(+), 100 deletions(-)
diff --git a/dojo_plugin/api/v1/belts.py b/dojo_plugin/api/v1/belts.py
index 75acc7a20..44f6f10e8 100644
--- a/dojo_plugin/api/v1/belts.py
+++ b/dojo_plugin/api/v1/belts.py
@@ -4,7 +4,8 @@
from CTFd.cache import cache
from CTFd.models import db, Users, Solves
-from ...utils import belt_challenges
+from ...models import Dojos
+from ...utils.dojo import BELT_REQUIREMENTS
belts_namespace = Namespace("belts", description="Endpoint to manage belts")
@@ -17,33 +18,20 @@ def get_belts():
"users": {},
}
- for color, challenges in belt_challenges().items():
+ for n,(color,dojo_id) in enumerate(BELT_REQUIREMENTS.items()):
result["dates"][color] = {}
-
- belted_users = (
- db.session.query(Users.id, Users.name, db.func.max(Solves.date))
- .join(Solves, Users.id == Solves.user_id)
- .filter(Solves.challenge_id.in_(challenges.subquery()))
- .group_by(Users.id)
- .having(db.func.count() == challenges.count())
- .order_by(db.func.max(Solves.date))
- )
-
- for user_id, handle, date in belted_users:
- result["dates"][color][user_id] = str(date)
- result["users"][user_id] = {
- "handle": handle,
+ dojo = Dojos.query.filter_by(id=dojo_id).first()
+
+ for user,date in dojo.completions():
+ if result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
+ continue
+ result["dates"][color][user.id] = str(date)
+ result["users"][user.id] = {
+ "handle": user.name,
"color": color,
+ "rank_id": n,
}
- # TODO: support belt deadlines
- for user_id, belt in list(result["users"].items()):
- if belt["color"] == "yellow":
- received = datetime.datetime.fromisoformat(result["dates"]["yellow"][user_id])
- if received > datetime.datetime.fromisoformat("2022-10-01"):
- del result["users"][user_id]
- del result["dates"]["yellow"][user_id]
-
return result
diff --git a/dojo_plugin/api/v1/scoreboard.py b/dojo_plugin/api/v1/scoreboard.py
index d86188209..af3b9236c 100644
--- a/dojo_plugin/api/v1/scoreboard.py
+++ b/dojo_plugin/api/v1/scoreboard.py
@@ -18,7 +18,7 @@
from sqlalchemy.orm.session import Session
from ...models import Dojos, DojoChallenges, DojoUsers, DojoMembers, DojoAdmins, DojoStudents, DojoModules, DojoChallengeVisibilities
-from ...utils import dojo_standings, dojo_completions, user_dojos, first_bloods, daily_solve_counts
+from ...utils import dojo_standings, user_dojos, first_bloods, daily_solve_counts
from ...utils.dojo import dojo_route, dojo_accessible
from .belts import get_belts
diff --git a/dojo_plugin/models/__init__.py b/dojo_plugin/models/__init__.py
index be1d79396..03d216ad4 100644
--- a/dojo_plugin/models/__init__.py
+++ b/dojo_plugin/models/__init__.py
@@ -194,11 +194,16 @@ def viewable(cls, id=None, user=None):
def solves(self, **kwargs):
return DojoChallenges.solves(dojo=self, **kwargs)
- def completers(self):
+ def completions(self):
+ """
+ Returns a list of (User, completion_timestamp) tuples for users, sorted by time in ascending order.
+ """
sq = Solves.query.join(DojoChallenges, Solves.challenge_id == DojoChallenges.challenge_id).add_columns(
Solves.user_id.label("solve_user_id"), db.func.count().label("solve_count"), db.func.max(Solves.date).label("last_solve")
).filter(DojoChallenges.dojo == self).group_by(Solves.user_id).subquery()
- return Users.query.join(sq).filter_by(solve_count=len(self.challenges)).order_by(sq.columns.last_solve).all()
+ return Users.query.join(sq).filter_by(
+ solve_count=len(self.challenges)
+ ).add_column(sq.columns.last_solve).order_by(sq.columns.last_solve).all()
def completed(self, user):
return self.solves(user=user, ignore_visibility=True, ignore_admins=False).count() == len(self.challenges)
diff --git a/dojo_plugin/utils/__init__.py b/dojo_plugin/utils/__init__.py
index ad1fe575c..f3fbb33a9 100644
--- a/dojo_plugin/utils/__init__.py
+++ b/dojo_plugin/utils/__init__.py
@@ -238,35 +238,6 @@ def load_dojo(dojo_id, dojo_spec, user=None, dojo_dir=None, commit=True, log=log
db.session.rollback()
-def dojo_completions():
- all_solves = (
- db.session.query(DojoChallenges.dojo_id.label("dojo_id"))
- .join(Solves, DojoChallenges.challenge_id == Solves.challenge_id)
- .add_columns(
- db.func.count(Solves.id).label("solves"),
- Solves.user_id,
- db.func.max(Solves.date).label("last_solve"),
- db.func.min(Solves.date).label("first_solve"),
- )
- .group_by(Solves.user_id, DojoChallenges.dojo_id)
- .order_by("last_solve")
- ).all()
- all_challenges = (
- db.session.query(Dojos.id.label("dojo_id"))
- .join(DojoChallenges, DojoChallenges.dojo_id == Dojos.id)
- .add_columns(db.func.count(DojoChallenges.challenge_id).label("challenges"))
- .group_by(Dojos.id)
- ).all()
-
- chal_counts = { d.dojo_id: d.challenges for d in all_challenges }
- completions = { }
- for s in all_solves:
- if s.solves == chal_counts[s.dojo_id]:
- completions.setdefault(s.user_id, []).append({
- "dojo": s.dojo_id, "last_solve": s.last_solve, "first_solve": s.first_solve
- })
- return completions
-
def first_bloods():
first_blood_string = db.func.min(Solves.date.cast(String)+"|"+Solves.user_id.cast(String))
first_blood_query = (
@@ -295,46 +266,6 @@ def daily_solve_counts():
).all()
return counts
-
-def belt_challenges():
- # TODO: move this concept into dojo yml
-
- yellow_categories = [
- "embryoio",
- "babysuid",
- "embryoasm",
- "babyshell",
- "babyjail",
- "embryogdb",
- "babyrev",
- "babymem",
- "toddlerone",
- ]
-
- blue_categories = [
- *yellow_categories,
- "babyrop",
- "babyheap",
- "babyrace",
- "babykernel",
- "toddlertwo",
- ]
-
- color_categories = {
- "yellow": yellow_categories,
- "blue": blue_categories,
- }
-
- return {
- color: db.session.query(Challenges.id).filter(
- Challenges.state == "visible",
- Challenges.value > 0,
- Challenges.id < 1000,
- Challenges.category.in_(categories),
- )
- for color, categories in color_categories.items()
- }
-
# based on https://stackoverflow.com/questions/36408496/python-logging-handler-to-append-to-list
class ListHandler(logging.Handler): # Inherit from logging.Handler
def __init__(self, log_list):
diff --git a/dojo_plugin/utils/dojo/__init__.py b/dojo_plugin/utils/dojo/__init__.py
index e6ab3fa46..602bb0dc1 100644
--- a/dojo_plugin/utils/dojo/__init__.py
+++ b/dojo_plugin/utils/dojo/__init__.py
@@ -399,10 +399,10 @@ def get_current_dojo_challenge(user=None):
)
BELT_REQUIREMENTS = {
- "orange": ["intro-to-cybersecurity"],
- "yellow": ["program-security"],
- "green": ["system-security"],
- "blue": ["software-exploitation"],
+ "orange": "intro-to-cybersecurity",
+ "yellow": "program-security",
+ "green": "system-security",
+ "blue": "software-exploitation",
}
def get_user_belts(user):
From 15257c6cdd3b56a2170051e0047210eb2a60bc9b Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 12 Jan 2024 18:47:24 -0700
Subject: [PATCH 005/123] i'm tired of missing the motherfucking flags on the
motherfucking scoreboard
---
dojo_plugin/api/v1/scoreboard.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/dojo_plugin/api/v1/scoreboard.py b/dojo_plugin/api/v1/scoreboard.py
index af3b9236c..59eaa72b0 100644
--- a/dojo_plugin/api/v1/scoreboard.py
+++ b/dojo_plugin/api/v1/scoreboard.py
@@ -95,6 +95,7 @@ def hook_object_update(mapper, connection, target):
invalidate_scoreboard_cache()
def get_scoreboard_page(model, duration=None, page=1, per_page=20):
+ belt_data = get_belts()
results = get_scoreboard_for(model, duration)
start_idx = (page - 1) * per_page
@@ -107,7 +108,7 @@ def standing(item):
result = {key: item[key] for key in item.keys()}
result["url"] = url_for("pwncollege_users.view_other", user_id=result["user_id"])
result["symbol"] = email_symbol_asset(result.pop("email"))
- result["belt"] = belt_asset(None) # TODO
+ result["belt"] = belt_asset(belt_data["users"].get(result["user_id"], {"color":None})["color"])
result["badges"] = [] # TODO
return result
From 5f5488d0f5d9e8f0c6bb0950e51d42dc714c5afe Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 12 Jan 2024 18:59:09 -0700
Subject: [PATCH 006/123] well i'll say, oh deary me
---
dojo_plugin/api/v1/belts.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/dojo_plugin/api/v1/belts.py b/dojo_plugin/api/v1/belts.py
index 44f6f10e8..06177792d 100644
--- a/dojo_plugin/api/v1/belts.py
+++ b/dojo_plugin/api/v1/belts.py
@@ -1,3 +1,4 @@
+import sqlalchemy
import datetime
from flask_restx import Namespace, Resource
@@ -20,7 +21,11 @@ def get_belts():
for n,(color,dojo_id) in enumerate(BELT_REQUIREMENTS.items()):
result["dates"][color] = {}
- dojo = Dojos.query.filter_by(id=dojo_id).first()
+ try:
+ dojo = Dojos.query.filter_by(id=dojo_id).first()
+ except sqlalchemy.exc.NoResultsFound:
+ # We are likely missing the correct dojos in the DB (e.g., custom deployment)
+ break
for user,date in dojo.completions():
if result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
From 024ea6a70e5ec7f403d5121ff4ca8af79ef42e69 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 12 Jan 2024 19:09:58 -0700
Subject: [PATCH 007/123] AWAY, EVIL EXCEPTION
---
dojo_plugin/api/v1/belts.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/dojo_plugin/api/v1/belts.py b/dojo_plugin/api/v1/belts.py
index 06177792d..a714ee8be 100644
--- a/dojo_plugin/api/v1/belts.py
+++ b/dojo_plugin/api/v1/belts.py
@@ -20,13 +20,13 @@ def get_belts():
}
for n,(color,dojo_id) in enumerate(BELT_REQUIREMENTS.items()):
- result["dates"][color] = {}
- try:
- dojo = Dojos.query.filter_by(id=dojo_id).first()
- except sqlalchemy.exc.NoResultsFound:
+ dojo = Dojos.query.filter_by(id=dojo_id).first()
+ if not dojo:
# We are likely missing the correct dojos in the DB (e.g., custom deployment)
break
+ result["dates"][color] = {}
+
for user,date in dojo.completions():
if result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
continue
From d2eaf654571e22ea03b285ff81e74d8b0d62539f Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 12 Jan 2024 19:24:42 -0700
Subject: [PATCH 008/123] move util belt logic into a utils.belts module
---
dojo_plugin/__init__.py | 2 +-
dojo_plugin/api/v1/belts.py | 37 +----------------------
dojo_plugin/api/v1/scoreboard.py | 2 +-
dojo_plugin/utils/belts.py | 48 ++++++++++++++++++++++++++++++
dojo_plugin/utils/dojo/__init__.py | 16 ----------
5 files changed, 51 insertions(+), 54 deletions(-)
create mode 100644 dojo_plugin/utils/belts.py
diff --git a/dojo_plugin/__init__.py b/dojo_plugin/__init__.py
index 71683473e..9f0d3fe63 100644
--- a/dojo_plugin/__init__.py
+++ b/dojo_plugin/__init__.py
@@ -19,7 +19,7 @@
from .config import DOJO_HOST, bootstrap
from .utils import unserialize_user_flag, render_markdown
from .utils.discord import get_discord_user, get_discord_roles, add_role, send_message
-from .utils.dojo import get_user_belts
+from .utils.belts import get_user_belts
from .pages.dojos import dojos, dojos_override
from .pages.dojo import dojo
from .pages.workspace import workspace
diff --git a/dojo_plugin/api/v1/belts.py b/dojo_plugin/api/v1/belts.py
index a714ee8be..7f6e5dcf1 100644
--- a/dojo_plugin/api/v1/belts.py
+++ b/dojo_plugin/api/v1/belts.py
@@ -1,45 +1,10 @@
-import sqlalchemy
-import datetime
-
from flask_restx import Namespace, Resource
-from CTFd.cache import cache
-from CTFd.models import db, Users, Solves
-
-from ...models import Dojos
-from ...utils.dojo import BELT_REQUIREMENTS
+from ...utils.belts import get_belts
belts_namespace = Namespace("belts", description="Endpoint to manage belts")
-@cache.memoize(timeout=60)
-def get_belts():
- result = {
- "dates": {},
- "users": {},
- }
-
- for n,(color,dojo_id) in enumerate(BELT_REQUIREMENTS.items()):
- dojo = Dojos.query.filter_by(id=dojo_id).first()
- if not dojo:
- # We are likely missing the correct dojos in the DB (e.g., custom deployment)
- break
-
- result["dates"][color] = {}
-
- for user,date in dojo.completions():
- if result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
- continue
- result["dates"][color][user.id] = str(date)
- result["users"][user.id] = {
- "handle": user.name,
- "color": color,
- "rank_id": n,
- }
-
- return result
-
-
@belts_namespace.route("")
class Belts(Resource):
def get(self):
diff --git a/dojo_plugin/api/v1/scoreboard.py b/dojo_plugin/api/v1/scoreboard.py
index 59eaa72b0..466e6ed0d 100644
--- a/dojo_plugin/api/v1/scoreboard.py
+++ b/dojo_plugin/api/v1/scoreboard.py
@@ -20,7 +20,7 @@
from ...models import Dojos, DojoChallenges, DojoUsers, DojoMembers, DojoAdmins, DojoStudents, DojoModules, DojoChallengeVisibilities
from ...utils import dojo_standings, user_dojos, first_bloods, daily_solve_counts
from ...utils.dojo import dojo_route, dojo_accessible
-from .belts import get_belts
+from ...utils.belts import get_belts
SCOREBOARD_CACHE_TIMEOUT_SECONDS = 60 * 60 * 2 # two hours make to cache all scoreboards
scoreboard_namespace = Namespace("scoreboard")
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
new file mode 100644
index 000000000..c75576e2b
--- /dev/null
+++ b/dojo_plugin/utils/belts.py
@@ -0,0 +1,48 @@
+from CTFd.cache import cache
+from ..models import Dojos
+
+
+BELT_REQUIREMENTS = {
+ "orange": "intro-to-cybersecurity",
+ "yellow": "program-security",
+ "green": "system-security",
+ "blue": "software-exploitation",
+}
+
+def get_user_belts(user):
+ result = [ ]
+ for belt, dojo_id in BELT_REQUIREMENTS.items():
+ dojo = Dojos.query.filter(Dojos.official, Dojos.id == dojo_id).one()
+ if not dojo.completed(user):
+ break
+ result.append(belt.title() + " Belt")
+ return result
+
+@cache.memoize(timeout=60)
+def get_belts():
+ result = {
+ "dates": {},
+ "users": {},
+ }
+
+ for n,(color,dojo_id) in enumerate(BELT_REQUIREMENTS.items()):
+ dojo = Dojos.query.filter_by(id=dojo_id).first()
+ if not dojo:
+ # We are likely missing the correct dojos in the DB (e.g., custom deployment)
+ break
+
+ result["dates"][color] = {}
+
+ for user,date in dojo.completions():
+ if result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
+ continue
+ result["dates"][color][user.id] = str(date)
+ result["users"][user.id] = {
+ "handle": user.name,
+ "color": color,
+ "rank_id": n,
+ }
+
+ return result
+
+
diff --git a/dojo_plugin/utils/dojo/__init__.py b/dojo_plugin/utils/dojo/__init__.py
index 602bb0dc1..3ce792a8a 100644
--- a/dojo_plugin/utils/dojo/__init__.py
+++ b/dojo_plugin/utils/dojo/__init__.py
@@ -397,19 +397,3 @@ def get_current_dojo_challenge(user=None):
DojoChallenges.dojo == Dojos.from_id(container.labels.get("dojo.dojo_id")).first())
.first()
)
-
-BELT_REQUIREMENTS = {
- "orange": "intro-to-cybersecurity",
- "yellow": "program-security",
- "green": "system-security",
- "blue": "software-exploitation",
-}
-
-def get_user_belts(user):
- result = [ ]
- for belt, dojo_id in BELT_REQUIREMENTS.items():
- dojo = Dojos.query.filter(Dojos.official, Dojos.id == dojo_id).one()
- if not dojo.completed(user):
- break
- result.append(belt.title() + " Belt")
- return result
From 1de112e9d3b903765f3658c44fdeab88fdcb506f Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 14:36:15 -0700
Subject: [PATCH 009/123] add belts page
---
dojo_plugin/__init__.py | 2 +
dojo_plugin/pages/belts.py | 8 ++++
dojo_plugin/utils/belts.py | 9 +++-
dojo_theme/templates/belts.html | 79 +++++++++++++++++++++++++++++++++
4 files changed, 96 insertions(+), 2 deletions(-)
create mode 100644 dojo_plugin/pages/belts.py
create mode 100644 dojo_theme/templates/belts.html
diff --git a/dojo_plugin/__init__.py b/dojo_plugin/__init__.py
index 9f0d3fe63..802d09cfd 100644
--- a/dojo_plugin/__init__.py
+++ b/dojo_plugin/__init__.py
@@ -30,6 +30,7 @@
from .pages.discord import discord
from .pages.course import course
from .pages.writeups import writeups
+from .pages.belts import belts
from .api import api
@@ -146,6 +147,7 @@ def load(app):
app.register_blueprint(users)
app.register_blueprint(course)
app.register_blueprint(writeups)
+ app.register_blueprint(belts)
app.register_blueprint(api, url_prefix="/pwncollege_api/v1")
app.jinja_env.filters["markdown"] = render_markdown
diff --git a/dojo_plugin/pages/belts.py b/dojo_plugin/pages/belts.py
new file mode 100644
index 000000000..f40a18de5
--- /dev/null
+++ b/dojo_plugin/pages/belts.py
@@ -0,0 +1,8 @@
+from flask import Blueprint, render_template
+from ..utils.belts import get_belts
+
+belts = Blueprint("pwncollege_belts", __name__)
+
+@belts.route("/belts")
+def view_belts():
+ return render_template("belts.html", belt_data=get_belts())
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index c75576e2b..004a662c1 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -23,6 +23,7 @@ def get_belts():
result = {
"dates": {},
"users": {},
+ "ranks": {},
}
for n,(color,dojo_id) in enumerate(BELT_REQUIREMENTS.items()):
@@ -32,6 +33,7 @@ def get_belts():
break
result["dates"][color] = {}
+ result["ranks"][color] = []
for user,date in dojo.completions():
if result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
@@ -39,10 +41,13 @@ def get_belts():
result["dates"][color][user.id] = str(date)
result["users"][user.id] = {
"handle": user.name,
+ "site": user.website,
"color": color,
+ "date": str(date),
"rank_id": n,
}
- return result
-
+ for user_id in result["users"]:
+ result["ranks"][result["users"][user_id]["color"]].append(user_id)
+ return result
diff --git a/dojo_theme/templates/belts.html b/dojo_theme/templates/belts.html
new file mode 100644
index 000000000..1cb7e639e
--- /dev/null
+++ b/dojo_theme/templates/belts.html
@@ -0,0 +1,79 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+
+
The Belted
+
+
+
+
+ The hacker ethos goes beyond the acquisition of a satisfactory grade in a college course.
+ A true hacker is never satisfied with the state of their knowledge.
+ They strive, or are irresistably driven towards, the achievement of absolute mastery of technical topics.
+
+
+
+ Below is a list of true hackers: those who stared at the yellow box, and rather than flinching, dove in.
+
+
+
+ NOTE: this list is a work in progress, and is missing some hallowed names from ancient times.
+ But don't worry! These masters' accomplishments can be viewed in the old belts page
+
+
+ {% for belt_color in belt_data.ranks|reverse %}
+
{{belt_color.title()}} Belts
+
+ {% if belt_color == "blue" %}
+ It is the rare individual who perseveres against the hardest of challenges, pushing on when the night is at its darkest.
+ The hackers below are truly the most stout of spirit, fighting through countless seemingly-impossible challenges and proving themselves worthy to wear their blue belt among the clouds.
+ {% elif belt_color == "green" %}
+ A sapling grows toward the sun: a hacker emerges into the light.
+ These individuals have shown a unique ability to see through the haze of uncertainty to the clarity beyond.
+ They are ready to journey into the sky.
+ {% elif belt_color == "yellow" %}
+ The sky brightens as the sun rises.
+ The minds of these students are opening, admitting phenomenal cybersecurity concepts that few even suspect to exist.
+ With sunlight comes the possibility of growth and the promise of learning to come.
+ {% elif belt_color == "orange" %}
+ Dawn is on the horizon.
+ No longer do the names written below belong to those unknowing cybersecurity: instead, their owners now realize the vast tracts of concepts still unexplored, waiting ready to expand their minds.
+ {% endif %}
+
+
+
+ {% for user_id in belt_data.ranks[belt_color] %}
+ {% set user = belt_data.users[user_id] %}
+
+ You, too, can be listed among the legends above.
+ You earn your belts by fully completing the appropriate pwn.college dojos.
+ This takes skill, cunning, and perseverance, but you can do it.
+ Good luck.
+
+{% endblock %}
From c9eb5c44b45e9c3234006b73d310cac289f0011b Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 15:14:33 -0700
Subject: [PATCH 010/123] move belt assets helper into utils
---
dojo_plugin/api/v1/scoreboard.py | 14 +-------------
dojo_plugin/utils/belts.py | 5 +++++
2 files changed, 6 insertions(+), 13 deletions(-)
diff --git a/dojo_plugin/api/v1/scoreboard.py b/dojo_plugin/api/v1/scoreboard.py
index 466e6ed0d..06af198e6 100644
--- a/dojo_plugin/api/v1/scoreboard.py
+++ b/dojo_plugin/api/v1/scoreboard.py
@@ -20,7 +20,7 @@
from ...models import Dojos, DojoChallenges, DojoUsers, DojoMembers, DojoAdmins, DojoStudents, DojoModules, DojoChallengeVisibilities
from ...utils import dojo_standings, user_dojos, first_bloods, daily_solve_counts
from ...utils.dojo import dojo_route, dojo_accessible
-from ...utils.belts import get_belts
+from ...utils.belts import get_belts, belt_asset
SCOREBOARD_CACHE_TIMEOUT_SECONDS = 60 * 60 * 2 # two hours make to cache all scoreboards
scoreboard_namespace = Namespace("scoreboard")
@@ -34,18 +34,6 @@ def email_symbol_asset(email):
group = "hacker.png"
return url_for("views.themes", path=f"img/dojo/{group}")
-
-def belt_asset(color):
- if color == "black":
- belt = "black.svg"
- elif color == "blue":
- belt = "blue.svg"
- elif color == "yellow":
- belt = "yellow.svg"
- else:
- belt = "white.svg"
- return url_for("views.themes", path=f"img/dojo/{belt}")
-
@cache.memoize(timeout=SCOREBOARD_CACHE_TIMEOUT_SECONDS)
def get_scoreboard_for(model, duration):
duration_filter = (
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index 004a662c1..62fbdadc8 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -1,4 +1,5 @@
from CTFd.cache import cache
+from flask import url_for
from ..models import Dojos
@@ -9,6 +10,10 @@
"blue": "software-exploitation",
}
+def belt_asset(color):
+ belt = color + ".svg" if color in BELT_REQUIREMENTS else "white.svg"
+ return url_for("views.themes", path=f"img/dojo/{belt}")
+
def get_user_belts(user):
result = [ ]
for belt, dojo_id in BELT_REQUIREMENTS.items():
From 73d5081867fa3655a95bc1ed6d29bbf4c423af30 Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 15:15:00 -0700
Subject: [PATCH 011/123] set a cutoff date after which belts become cumulative
(currently 2023.10.1)
---
dojo_plugin/utils/belts.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index 62fbdadc8..dd9b46499 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -1,8 +1,11 @@
+import datetime
+
from CTFd.cache import cache
from flask import url_for
from ..models import Dojos
+CUMULATIVE_CUTOFF = datetime.datetime(2023, 10, 1)
BELT_REQUIREMENTS = {
"orange": "intro-to-cybersecurity",
"yellow": "program-security",
@@ -41,7 +44,7 @@ def get_belts():
result["ranks"][color] = []
for user,date in dojo.completions():
- if result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
+ if date > CUMULATIVE_CUTOFF and result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
continue
result["dates"][color][user.id] = str(date)
result["users"][user.id] = {
From 6df5d406348125330df610fd34c0dfd6b9602a14 Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 16:08:27 -0700
Subject: [PATCH 012/123] fetch belts from the awards table, named BELT_BLUE,
BELT_GREEN, etc
---
dojo_plugin/utils/belts.py | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index dd9b46499..5da33b910 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -1,6 +1,7 @@
import datetime
from CTFd.cache import cache
+from CTFd.models import Awards
from flask import url_for
from ..models import Dojos
@@ -43,9 +44,18 @@ def get_belts():
result["dates"][color] = {}
result["ranks"][color] = []
- for user,date in dojo.completions():
- if date > CUMULATIVE_CUTOFF and result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1:
+ belt_awards = [ (award.user, award.date) for award in Awards.query.filter_by(name="BELT_"+color.upper()) ]
+ awarded_ids = set(u.id for u,_ in belt_awards)
+ sorted_belts = sorted(belt_awards + dojo.completions(), key=lambda d: d[1])
+
+ for user,date in sorted_belts:
+ if (
+ date > CUMULATIVE_CUTOFF and
+ user.id not in awarded_ids and
+ result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1
+ ):
continue
+
result["dates"][color][user.id] = str(date)
result["users"][user.id] = {
"handle": user.name,
From d93c8b3c8eee304878712c5416242d9f5cdac105 Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 18:54:45 -0700
Subject: [PATCH 013/123] some crazy polymorphic shenanigans
---
dojo_plugin/models/__init__.py | 5 ++++-
dojo_plugin/utils/belts.py | 5 ++---
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/dojo_plugin/models/__init__.py b/dojo_plugin/models/__init__.py
index 03d216ad4..dea746680 100644
--- a/dojo_plugin/models/__init__.py
+++ b/dojo_plugin/models/__init__.py
@@ -20,7 +20,7 @@
from sqlalchemy.sql import or_, and_
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.ext.associationproxy import association_proxy
-from CTFd.models import db, get_class_by_tablename, Challenges, Solves, Flags, Users, Admins
+from CTFd.models import db, get_class_by_tablename, Challenges, Solves, Flags, Users, Admins, Awards
from CTFd.utils.user import get_current_user, is_admin
from ..config import DOJOS_DIR
@@ -652,3 +652,6 @@ class DiscordUsers(db.Model):
user = db.relationship("Users")
__repr__ = columns_repr(["user", "discord_id"])
+
+class Belts(Awards):
+ __mapper_args__ = {"polymorphic_identity": "belt"}
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index 5da33b910..e7a2bfb9d 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -1,9 +1,8 @@
import datetime
from CTFd.cache import cache
-from CTFd.models import Awards
from flask import url_for
-from ..models import Dojos
+from ..models import Dojos, Belts
CUMULATIVE_CUTOFF = datetime.datetime(2023, 10, 1)
@@ -44,7 +43,7 @@ def get_belts():
result["dates"][color] = {}
result["ranks"][color] = []
- belt_awards = [ (award.user, award.date) for award in Awards.query.filter_by(name="BELT_"+color.upper()) ]
+ belt_awards = [ (award.user, award.date) for award in Belts.query.filter_by(name=color) ]
awarded_ids = set(u.id for u,_ in belt_awards)
sorted_belts = sorted(belt_awards + dojo.completions(), key=lambda d: d[1])
From 85d70902d3b7eceb725c5d87e077d15ac59a8f89 Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 19:02:03 -0700
Subject: [PATCH 014/123] automatically grant belt awards going forward
---
dojo_plugin/__init__.py | 19 ++++++++++++++-----
dojo_plugin/utils/belts.py | 2 +-
2 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/dojo_plugin/__init__.py b/dojo_plugin/__init__.py
index 802d09cfd..fbb0fb561 100644
--- a/dojo_plugin/__init__.py
+++ b/dojo_plugin/__init__.py
@@ -15,7 +15,7 @@
from CTFd.plugins.challenges import CHALLENGE_CLASSES, BaseChallenge
from CTFd.plugins.flags import FLAG_CLASSES, BaseFlag, FlagException
-from .models import Dojos, DojoChallenges
+from .models import Dojos, DojoChallenges, Belts
from .config import DOJO_HOST, bootstrap
from .utils import unserialize_user_flag, render_markdown
from .utils.discord import get_discord_user, get_discord_roles, add_role, send_message
@@ -43,18 +43,27 @@ class DojoChallenge(BaseChallenge):
def solve(cls, user, team, challenge, request):
super().solve(user, team, challenge, request)
+ current_belts = get_user_belts(user)
+ for belt in current_belts:
+ belt_award = Belts.query.filter_by(user=user, name=belt).one()
+ if belt_award:
+ continue
+ db.session.add(Belts(user=user, name=belt))
+ db.session.commit()
+
discord_user = get_discord_user(user.id)
if not discord_user:
return
discord_roles = get_discord_roles()
- for belt in get_user_belts(user):
- if discord_roles.get(belt) in discord_user["roles"]:
+ for belt in current_belts:
+ belt_role = belt.title() + " Belt"
+ if discord_roles.get(belt_role) in discord_user["roles"]:
continue
user_mention = f"<@{discord_user['user']['id']}>"
- message = f"{user_mention} earned their {belt}! :tada:"
+ message = f"{user_mention} earned their {belt_role}! :tada:"
print(message, flush=True)
- add_role(discord_user["user"]["id"], belt)
+ add_role(discord_user["user"]["id"], belt_role)
send_message(message, "belting-ceremony")
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index e7a2bfb9d..abf5c28ba 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -23,7 +23,7 @@ def get_user_belts(user):
dojo = Dojos.query.filter(Dojos.official, Dojos.id == dojo_id).one()
if not dojo.completed(user):
break
- result.append(belt.title() + " Belt")
+ result.append(belt)
return result
@cache.memoize(timeout=60)
From c2e4fbcd1ea59a00659a1e871a8a6819b9ccb962 Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 19:41:37 -0700
Subject: [PATCH 015/123] gracefully handle missing dojos
---
dojo_plugin/utils/belts.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index abf5c28ba..98f8922c0 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -20,7 +20,10 @@ def belt_asset(color):
def get_user_belts(user):
result = [ ]
for belt, dojo_id in BELT_REQUIREMENTS.items():
- dojo = Dojos.query.filter(Dojos.official, Dojos.id == dojo_id).one()
+ dojo = Dojos.query.filter(Dojos.official, Dojos.id == dojo_id).first()
+ if not dojo:
+ # We are likely missing the correct dojos in the DB (e.g., custom deployment)
+ break
if not dojo.completed(user):
break
result.append(belt)
From 3ded88691fa60bf7eed80f670d4263627ba53403 Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 13 Jan 2024 20:58:41 -0700
Subject: [PATCH 016/123] fix belt granting logic
---
dojo_plugin/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dojo_plugin/__init__.py b/dojo_plugin/__init__.py
index fbb0fb561..8eeada442 100644
--- a/dojo_plugin/__init__.py
+++ b/dojo_plugin/__init__.py
@@ -45,7 +45,7 @@ def solve(cls, user, team, challenge, request):
current_belts = get_user_belts(user)
for belt in current_belts:
- belt_award = Belts.query.filter_by(user=user, name=belt).one()
+ belt_award = Belts.query.filter_by(user=user, name=belt).first()
if belt_award:
continue
db.session.add(Belts(user=user, name=belt))
From 790140f90adbcd35d0a005e885805f1b486151b5 Mon Sep 17 00:00:00 2001
From: Robert Wasinger
Date: Tue, 16 Jan 2024 00:51:28 -0700
Subject: [PATCH 017/123] update homepage to link to dynamic belts list
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index d797af2c3..a4e516bda 100644
--- a/index.html
+++ b/index.html
@@ -84,7 +84,7 @@
Learn to hack!
### How to get the actual belt?
To get your belt, [send us an email](malto:pwn@pwn.college) from the email address associated with your pwn.college account once you’ve completed the necessary challenges. We’ll then get your belt over to you (eventually)!
-**Who has earned belts?** We maintain a [list of hackers with pwn.college belts](https://static.pwn.college/belts). Once you achieve your belt, if you provide your name, email address, and emoji, we will add you to the [list](https://static.pwn.college/belts)!
+**Who has earned belts?** We maintain a [list of hackers with pwn.college belts](https://pwn.college/belts). Once you achieve your belt, we will add you to the [list](https://pwn.college/belts)!
From dd283de35669a61d195b6f8a0d68ba856fcc5b71 Mon Sep 17 00:00:00 2001
From: Yan
Date: Tue, 16 Jan 2024 01:14:33 -0700
Subject: [PATCH 018/123] fetch belts just from the belt table, now that we
have it all set up
---
dojo_plugin/utils/belts.py | 24 ++++++------------------
1 file changed, 6 insertions(+), 18 deletions(-)
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index 98f8922c0..63e44903c 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -5,7 +5,6 @@
from ..models import Dojos, Belts
-CUMULATIVE_CUTOFF = datetime.datetime(2023, 10, 1)
BELT_REQUIREMENTS = {
"orange": "intro-to-cybersecurity",
"yellow": "program-security",
@@ -46,24 +45,13 @@ def get_belts():
result["dates"][color] = {}
result["ranks"][color] = []
- belt_awards = [ (award.user, award.date) for award in Belts.query.filter_by(name=color) ]
- awarded_ids = set(u.id for u,_ in belt_awards)
- sorted_belts = sorted(belt_awards + dojo.completions(), key=lambda d: d[1])
-
- for user,date in sorted_belts:
- if (
- date > CUMULATIVE_CUTOFF and
- user.id not in awarded_ids and
- result["users"].get(user.id, {"rank_id":-1})["rank_id"] != n-1
- ):
- continue
-
- result["dates"][color][user.id] = str(date)
- result["users"][user.id] = {
- "handle": user.name,
- "site": user.website,
+ for belt in Belts.query.filter_by(name=color).order_by(Belts.date):
+ result["dates"][color][belt.user.id] = str(belt.date)
+ result["users"][belt.user.id] = {
+ "handle": belt.user.name,
+ "site": belt.user.website,
"color": color,
- "date": str(date),
+ "date": str(belt.date),
"rank_id": n,
}
From 8c7943caa8da03fd476e2b0e58322235d664c469 Mon Sep 17 00:00:00 2001
From: Yan
Date: Tue, 16 Jan 2024 01:37:13 -0700
Subject: [PATCH 019/123] sort the belts list
---
dojo_theme/templates/belts.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dojo_theme/templates/belts.html b/dojo_theme/templates/belts.html
index 1cb7e639e..2b72e2b41 100644
--- a/dojo_theme/templates/belts.html
+++ b/dojo_theme/templates/belts.html
@@ -43,7 +43,7 @@
{{belt_color.title()}} Belts
- {% for user_id in belt_data.ranks[belt_color] %}
+ {% for user_id in belt_data.ranks[belt_color]|sort(attribute='date') %}
{% set user = belt_data.users[user_id] %}
{{ user.handle }}
From e400a84ac5b82e44f063da732a1951bc157bae8e Mon Sep 17 00:00:00 2001
From: Yan
Date: Tue, 16 Jan 2024 01:51:58 -0700
Subject: [PATCH 020/123] sort properly
---
dojo_plugin/utils/belts.py | 3 +++
dojo_theme/templates/belts.html | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/belts.py
index 63e44903c..0f27ea248 100644
--- a/dojo_plugin/utils/belts.py
+++ b/dojo_plugin/utils/belts.py
@@ -58,4 +58,7 @@ def get_belts():
for user_id in result["users"]:
result["ranks"][result["users"][user_id]["color"]].append(user_id)
+ for rank in result["ranks"].values():
+ rank.sort(key=lambda uid: result["users"][uid]["date"])
+
return result
diff --git a/dojo_theme/templates/belts.html b/dojo_theme/templates/belts.html
index 2b72e2b41..1cb7e639e 100644
--- a/dojo_theme/templates/belts.html
+++ b/dojo_theme/templates/belts.html
@@ -43,7 +43,7 @@
{{belt_color.title()}} Belts
- {% for user_id in belt_data.ranks[belt_color]|sort(attribute='date') %}
+ {% for user_id in belt_data.ranks[belt_color] %}
{% set user = belt_data.users[user_id] %}
{{ user.handle }}
From f0a17ad8dee3eba29bbfcb76dc7d079739a33b55 Mon Sep 17 00:00:00 2001
From: Yan
Date: Wed, 17 Jan 2024 15:00:09 -0700
Subject: [PATCH 021/123] no longer wip
---
dojo_theme/templates/belts.html | 5 -----
1 file changed, 5 deletions(-)
diff --git a/dojo_theme/templates/belts.html b/dojo_theme/templates/belts.html
index 1cb7e639e..b1e82a199 100644
--- a/dojo_theme/templates/belts.html
+++ b/dojo_theme/templates/belts.html
@@ -17,11 +17,6 @@
The Belted
Below is a list of true hackers: those who stared at the yellow box, and rather than flinching, dove in.
-
- NOTE: this list is a work in progress, and is missing some hallowed names from ancient times.
- But don't worry! These masters' accomplishments can be viewed in the old belts page
-
-
{% for belt_color in belt_data.ranks|reverse %}
{{belt_color.title()}} Belts
From a2e28b7c6c4ebc58db8bb05efaa0690a2b5f862a Mon Sep 17 00:00:00 2001
From: Adam Doupe
Date: Wed, 17 Jan 2024 15:18:30 -0700
Subject: [PATCH 022/123] Show the number of days and hours left until a
deadline for future deadlines for students in the class. Fixes #294
---
dojo_plugin/models/__init__.py | 7 +++++++
dojo_plugin/pages/dojo.py | 16 +++++++++++++++-
dojo_theme/static/css/custom.css | 9 +++++++++
dojo_theme/templates/module.html | 6 ++++++
4 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/dojo_plugin/models/__init__.py b/dojo_plugin/models/__init__.py
index dea746680..8cf200c1e 100644
--- a/dojo_plugin/models/__init__.py
+++ b/dojo_plugin/models/__init__.py
@@ -350,6 +350,13 @@ def resources(self, value):
def path(self):
return self.dojo.path / self.id
+ @property
+ def assessments(self):
+ course_assessments = (self.dojo.course or {}).get("assessments", [])
+ to_return = [assessment for assessment in course_assessments if assessment.get('id', None) == self.id]
+ return to_return
+
+
def visible_challenges(self, user=None):
return [challenge for challenge in self.challenges if challenge.visible() or self.dojo.is_admin(user=user)]
diff --git a/dojo_plugin/pages/dojo.py b/dojo_plugin/pages/dojo.py
index aeb97249f..5129150d7 100644
--- a/dojo_plugin/pages/dojo.py
+++ b/dojo_plugin/pages/dojo.py
@@ -11,7 +11,7 @@
from ..utils import render_markdown, module_visible, module_challenges_visible, is_dojo_admin
from ..utils.dojo import dojo_route, get_current_dojo_challenge
-from ..models import Dojos, DojoUsers
+from ..models import Dojos, DojoUsers, DojoStudents
dojo = Blueprint("pwncollege_dojo", __name__)
@@ -72,7 +72,20 @@ def view_module(dojo, module):
total_solves = dict(module.solves()
.group_by(Solves.challenge_id)
.with_entities(Solves.challenge_id, db.func.count()))
+
current_dojo_challenge = get_current_dojo_challenge()
+ requested_types = ['checkpoint', 'due']
+ due_info = {}
+ now = datetime.datetime.now().astimezone()
+ student = DojoStudents.query.filter_by(dojo=dojo, user=user).first()
+ if (student and student.official) or user.type == 'admin':
+ assessments = {assessment['type']: assessment for assessment in module.assessments}
+ for requested in requested_types:
+ if requested in assessments:
+ due_date = datetime.datetime.fromisoformat(assessments[requested]['date'])
+ delta = due_date - now
+ show = due_date > now
+ due_info[requested] = dict(due_date=due_date, delta=delta, show=show)
return render_template(
"module.html",
dojo=dojo,
@@ -81,5 +94,6 @@ def view_module(dojo, module):
user_solves=user_solves,
total_solves=total_solves,
user=user,
+ due_info=due_info,
current_dojo_challenge=current_dojo_challenge,
)
diff --git a/dojo_theme/static/css/custom.css b/dojo_theme/static/css/custom.css
index 9e9edbe61..a531ee4fa 100644
--- a/dojo_theme/static/css/custom.css
+++ b/dojo_theme/static/css/custom.css
@@ -301,3 +301,12 @@ hr {
.module-dojo a {
color: #b5e853 !important;
}
+
+.module-checkpoint-due-date, .module-due-date {
+ font-size: 1.25em;
+}
+
+.module-checkpoint-due-date a, .module-due-date a {
+ color: rgb(234, 234, 234) !important;
+}
+
diff --git a/dojo_theme/templates/module.html b/dojo_theme/templates/module.html
index f2acc3062..786b0de10 100644
--- a/dojo_theme/templates/module.html
+++ b/dojo_theme/templates/module.html
@@ -7,6 +7,12 @@
From 4244055c97dd39d80d016337ded6fde3ba649558 Mon Sep 17 00:00:00 2001
From: Yan Shoshitaishvili
Date: Mon, 22 Jan 2024 12:57:34 -0700
Subject: [PATCH 027/123] UI for promoting dojo members to dojo admins (#300)
* support promoting dojo members to admins
* add confirmation functionality to form_fetch_and_show
* yolo test?
* require the user to be a dojo member before promoting to a dojo admin
* fix text issues
* print
* allow people to join official dojos
* adapt to non-tty DB output
* assert failure when appropriate
* checking
* remove debug print
* add debug print
* is the csrf token going stale somehow?
* Revert "is the csrf token going stale somehow?"
This reverts commit e42bc522b03cc2ba02a2d8c83be7dc588bc5e24e.
* Revert "add debug print"
This reverts commit 7f98c5685d207194bad4c8b0fdf5f21a2d4c8f32.
* Revert "checking"
This reverts commit 72e576b334dc2d1061aa059d723a544a49eda5b6.
* json?
* split out the promotion test from the join test
* now what
* join before promotion
* user module-scoped user for join and promote, instead of creating two users
* fix get_user_id
---
dojo_plugin/api/v1/dojo.py | 21 +++++++++++++++++++-
dojo_plugin/pages/dojos.py | 3 ---
dojo_theme/static/js/dojo/settings.js | 8 +++++++-
dojo_theme/templates/dojo_admin.html | 20 ++++++++++++++++---
test/test_running.py | 28 +++++++++++++++++++++++++++
5 files changed, 72 insertions(+), 8 deletions(-)
diff --git a/dojo_plugin/api/v1/dojo.py b/dojo_plugin/api/v1/dojo.py
index aab050f79..74b9f9a35 100644
--- a/dojo_plugin/api/v1/dojo.py
+++ b/dojo_plugin/api/v1/dojo.py
@@ -21,7 +21,7 @@
from CTFd.utils.modes import get_model
from CTFd.utils.security.sanitize import sanitize_html
-from ...models import Dojos, DojoMembers, DojoAdmins
+from ...models import Dojos, DojoMembers, DojoAdmins, DojoUsers
from ...utils.dojo import dojo_accessible, dojo_clone, load_dojo_dir, dojo_route
@@ -75,6 +75,25 @@ def create_dojo(user, repository, public_key, private_key):
return {"success": True, "dojo": dojo.reference_id}
+@dojo_namespace.route("//promote-admin")
+class PromoteAdmin(Resource):
+ @authed_only
+ @dojo_route
+ def post(self, dojo):
+ data = request.get_json()
+ if 'user_id' not in data:
+ return {"success": False, "error": "User not specified."}, 400
+ new_admin_id = data['user_id']
+ user = get_current_user()
+ if not dojo.is_admin(user):
+ return {"success": False, "error": "Requestor is not a dojo admin."}, 403
+ u = DojoUsers.query.filter_by(dojo=dojo, user_id=new_admin_id).first()
+ if u:
+ u.type = 'admin'
+ else:
+ return {"success": False, "error": "User is not currently a dojo member."}, 400
+ db.session.commit()
+ return {"success": True}
@dojo_namespace.route("/create")
class CreateDojo(Resource):
diff --git a/dojo_plugin/pages/dojos.py b/dojo_plugin/pages/dojos.py
index 0091193e6..653a50629 100644
--- a/dojo_plugin/pages/dojos.py
+++ b/dojo_plugin/pages/dojos.py
@@ -73,9 +73,6 @@ def join_dojo(dojo, password=None):
if not dojo:
abort(404)
- if dojo.official:
- return redirect(url_for("pwncollege_dojo.listing", dojo=dojo.reference_id))
-
if dojo.password and dojo.password != password:
abort(403)
diff --git a/dojo_theme/static/js/dojo/settings.js b/dojo_theme/static/js/dojo/settings.js
index c1bd0367d..380748e98 100644
--- a/dojo_theme/static/js/dojo/settings.js
+++ b/dojo_theme/static/js/dojo/settings.js
@@ -12,7 +12,7 @@ var success_template =
' \n' +
'
';
-function form_fetch_and_show(name, endpoint, method, success_message) {
+function form_fetch_and_show(name, endpoint, method, success_message, confirm_msg=null) {
const form = $(`#${name}-form`);
const results = $(`#${name}-results`);
form.submit(e => {
@@ -20,6 +20,8 @@ function form_fetch_and_show(name, endpoint, method, success_message) {
results.empty();
const params = form.serializeJSON();
+ if (confirm_msg && !confirm(confirm_msg(form, params))) return;
+
CTFd.fetch(endpoint, {
method: method,
credentials: "same-origin",
@@ -45,6 +47,10 @@ function form_fetch_and_show(name, endpoint, method, success_message) {
$(() => {
form_fetch_and_show("ssh-key", "/pwncollege_api/v1/ssh_key", "PATCH", "Your public key has been updated");
form_fetch_and_show("dojo-create", "/pwncollege_api/v1/dojo/create", "POST", "Your dojo has been created");
+ form_fetch_and_show("dojo-promote-admin", `/pwncollege_api/v1/dojo/${init.dojo}/promote-admin`, "POST", "User has been promoted to admin.", confirm_msg = (form, params) => {
+ var user_name = form.find(`#name-for-${params["user_id"]}`)
+ return `Promote ${user_name.text()} (UID ${params["user_id"]}) to admin?`;
+ });
$(".copy-button").click((event) => {
let input = $(event.target).parents(".input-group").children("input")[0];
diff --git a/dojo_theme/templates/dojo_admin.html b/dojo_theme/templates/dojo_admin.html
index e580774b1..28af11937 100644
--- a/dojo_theme/templates/dojo_admin.html
+++ b/dojo_theme/templates/dojo_admin.html
@@ -62,11 +62,25 @@
{{ dojo.name }}
{% endfor %}
Members
-
+
{% endblock %}
+{% block entrypoint %}
+
+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
diff --git a/test/test_running.py b/test/test_running.py
index ec7784c3d..e7a7f767c 100644
--- a/test/test_running.py
+++ b/test/test_running.py
@@ -51,6 +51,10 @@ def start_challenge(dojo, module, challenge, practice=False, *, session):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
assert response.json()["success"], f"Failed to start challenge: {response.json()['error']}"
+def get_user_id(user_name):
+ sql = f"SELECT id FROM users WHERE name = '{user_name}'"
+ db_result = dojo_run("db", input=sql)
+ return int(db_result.stdout.split()[1])
@pytest.fixture
def admin_session():
@@ -65,6 +69,13 @@ def random_user():
yield random_id, session
+@pytest.fixture(scope="module")
+def singleton_user():
+ random_id = "".join(random.choices(string.ascii_lowercase, k=16))
+ session = login(random_id, random_id, register=True)
+ yield random_id, session
+
+
@pytest.mark.parametrize("endpoint", ["/", "/dojos", "/login", "/register"])
def test_unauthenticated_return_200(endpoint):
response = requests.get(f"{PROTO}://{HOST}{endpoint}")
@@ -116,6 +127,23 @@ def test_create_import_dojo(admin_session):
def test_start_challenge(admin_session):
start_challenge("example", "hello", "apple", session=admin_session)
+@pytest.mark.dependency(depends=["test_create_dojo"])
+def test_join_dojo(admin_session, singleton_user):
+ random_user_name, random_session = singleton_user
+ response = random_session.get(f"{PROTO}://{HOST}/dojo/example/join/")
+ assert response.status_code == 200
+ response = admin_session.get(f"{PROTO}://{HOST}/dojo/example/admin/")
+ assert response.status_code == 200
+ assert random_user_name in response.text and response.text.index("Members") < response.text.index(random_user_name)
+
+@pytest.mark.dependency(depends=["test_join_dojo"])
+def test_promote_dojo_member(admin_session, singleton_user):
+ random_user_name, _ = singleton_user
+ random_user_id = get_user_id(random_user_name)
+ response = admin_session.post(f"{PROTO}://{HOST}/pwncollege_api/v1/dojo/example/promote-admin", json={"user_id": random_user_id})
+ assert response.status_code == 200
+ response = admin_session.get(f"{PROTO}://{HOST}/dojo/example/admin/")
+ assert random_user_name in response.text and response.text.index("Members") > response.text.index(random_user_name)
@pytest.mark.dependency(depends=["test_start_challenge"])
@pytest.mark.parametrize("path", ["/flag", "/challenge/apple"])
From 9444d8f5ef45ced306e845f2ba4bd06eff5480e8 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 19 Jan 2024 22:00:02 -0700
Subject: [PATCH 028/123] rename utils.belts to utils.awards
---
dojo_plugin/__init__.py | 2 +-
dojo_plugin/api/v1/belts.py | 2 +-
dojo_plugin/api/v1/scoreboard.py | 2 +-
dojo_plugin/pages/belts.py | 2 +-
dojo_plugin/utils/{belts.py => awards.py} | 0
5 files changed, 4 insertions(+), 4 deletions(-)
rename dojo_plugin/utils/{belts.py => awards.py} (100%)
diff --git a/dojo_plugin/__init__.py b/dojo_plugin/__init__.py
index 8eeada442..395f2e67b 100644
--- a/dojo_plugin/__init__.py
+++ b/dojo_plugin/__init__.py
@@ -19,7 +19,7 @@
from .config import DOJO_HOST, bootstrap
from .utils import unserialize_user_flag, render_markdown
from .utils.discord import get_discord_user, get_discord_roles, add_role, send_message
-from .utils.belts import get_user_belts
+from .utils.awards import get_user_belts
from .pages.dojos import dojos, dojos_override
from .pages.dojo import dojo
from .pages.workspace import workspace
diff --git a/dojo_plugin/api/v1/belts.py b/dojo_plugin/api/v1/belts.py
index 7f6e5dcf1..d862a3969 100644
--- a/dojo_plugin/api/v1/belts.py
+++ b/dojo_plugin/api/v1/belts.py
@@ -1,6 +1,6 @@
from flask_restx import Namespace, Resource
-from ...utils.belts import get_belts
+from ...utils.awards import get_belts
belts_namespace = Namespace("belts", description="Endpoint to manage belts")
diff --git a/dojo_plugin/api/v1/scoreboard.py b/dojo_plugin/api/v1/scoreboard.py
index 06af198e6..5d6393749 100644
--- a/dojo_plugin/api/v1/scoreboard.py
+++ b/dojo_plugin/api/v1/scoreboard.py
@@ -20,7 +20,7 @@
from ...models import Dojos, DojoChallenges, DojoUsers, DojoMembers, DojoAdmins, DojoStudents, DojoModules, DojoChallengeVisibilities
from ...utils import dojo_standings, user_dojos, first_bloods, daily_solve_counts
from ...utils.dojo import dojo_route, dojo_accessible
-from ...utils.belts import get_belts, belt_asset
+from ...utils.awards import get_belts, belt_asset
SCOREBOARD_CACHE_TIMEOUT_SECONDS = 60 * 60 * 2 # two hours make to cache all scoreboards
scoreboard_namespace = Namespace("scoreboard")
diff --git a/dojo_plugin/pages/belts.py b/dojo_plugin/pages/belts.py
index f40a18de5..2297b8029 100644
--- a/dojo_plugin/pages/belts.py
+++ b/dojo_plugin/pages/belts.py
@@ -1,5 +1,5 @@
from flask import Blueprint, render_template
-from ..utils.belts import get_belts
+from ..utils.awards import get_belts
belts = Blueprint("pwncollege_belts", __name__)
diff --git a/dojo_plugin/utils/belts.py b/dojo_plugin/utils/awards.py
similarity index 100%
rename from dojo_plugin/utils/belts.py
rename to dojo_plugin/utils/awards.py
From d5cbb7683a45d3e116a5c491c1f9392e36b47c48 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 19 Jan 2024 22:16:12 -0700
Subject: [PATCH 029/123] make emoji awards on solve
---
dojo_plugin/__init__.py | 12 ++++++++++--
dojo_plugin/models/__init__.py | 3 +++
dojo_plugin/utils/awards.py | 10 ++++++++++
3 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/dojo_plugin/__init__.py b/dojo_plugin/__init__.py
index 395f2e67b..3d954bcd0 100644
--- a/dojo_plugin/__init__.py
+++ b/dojo_plugin/__init__.py
@@ -15,11 +15,11 @@
from CTFd.plugins.challenges import CHALLENGE_CLASSES, BaseChallenge
from CTFd.plugins.flags import FLAG_CLASSES, BaseFlag, FlagException
-from .models import Dojos, DojoChallenges, Belts
+from .models import Dojos, DojoChallenges, Belts, Emojis
from .config import DOJO_HOST, bootstrap
from .utils import unserialize_user_flag, render_markdown
from .utils.discord import get_discord_user, get_discord_roles, add_role, send_message
-from .utils.awards import get_user_belts
+from .utils.awards import get_user_belts, get_user_emoji
from .pages.dojos import dojos, dojos_override
from .pages.dojo import dojo
from .pages.workspace import workspace
@@ -51,6 +51,14 @@ def solve(cls, user, team, challenge, request):
db.session.add(Belts(user=user, name=belt))
db.session.commit()
+ current_emojis = get_user_emojis(user)
+ for emoji in current_emojis:
+ emoji_award = Emojis.query.filter_by(user=user, name=emoji).first()
+ if emoji_award:
+ continue
+ db.session.add(Emojis(user=user, name=emoji))
+ db.session.commit()
+
discord_user = get_discord_user(user.id)
if not discord_user:
return
diff --git a/dojo_plugin/models/__init__.py b/dojo_plugin/models/__init__.py
index 91de4ab5a..6dcd81d70 100644
--- a/dojo_plugin/models/__init__.py
+++ b/dojo_plugin/models/__init__.py
@@ -659,3 +659,6 @@ class DiscordUsers(db.Model):
class Belts(Awards):
__mapper_args__ = {"polymorphic_identity": "belt"}
+
+class Emojis(Awards):
+ __mapper_args__ = {"polymorphic_identity": "emoji"}
diff --git a/dojo_plugin/utils/awards.py b/dojo_plugin/utils/awards.py
index 0f27ea248..486f76745 100644
--- a/dojo_plugin/utils/awards.py
+++ b/dojo_plugin/utils/awards.py
@@ -28,6 +28,16 @@ def get_user_belts(user):
result.append(belt)
return result
+def get_user_emojis(user):
+ emojis = [ ]
+ for dojo in Dojos.query.all():
+ emoji = dojo.award and dojo.award.get('emoji', None)
+ if not emoji:
+ continue
+ if dojo.completed(user):
+ emojis.append(emoji)
+ return emojis
+
@cache.memoize(timeout=60)
def get_belts():
result = {
From fe03132cb6225907d870f5bfeb6b42cd70194383 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 19 Jan 2024 22:16:58 -0700
Subject: [PATCH 030/123] no need to cache get_belts since we no longer
dynamically compute the belts
---
dojo_plugin/utils/awards.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/dojo_plugin/utils/awards.py b/dojo_plugin/utils/awards.py
index 486f76745..e3fceeba1 100644
--- a/dojo_plugin/utils/awards.py
+++ b/dojo_plugin/utils/awards.py
@@ -38,7 +38,6 @@ def get_user_emojis(user):
emojis.append(emoji)
return emojis
-@cache.memoize(timeout=60)
def get_belts():
result = {
"dates": {},
From 3ebd658f9a4b0171719885b9b162d85c7f6bb7e3 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 19 Jan 2024 22:22:24 -0700
Subject: [PATCH 031/123] waive cumulative dojo solve requirement if the
previous belt award is present
---
dojo_plugin/utils/awards.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/dojo_plugin/utils/awards.py b/dojo_plugin/utils/awards.py
index e3fceeba1..9d034420b 100644
--- a/dojo_plugin/utils/awards.py
+++ b/dojo_plugin/utils/awards.py
@@ -19,6 +19,10 @@ def belt_asset(color):
def get_user_belts(user):
result = [ ]
for belt, dojo_id in BELT_REQUIREMENTS.items():
+ belt_award = Belts.query.filter_by(user=user, name=belt).first()
+ if belt_award:
+ continue
+
dojo = Dojos.query.filter(Dojos.official, Dojos.id == dojo_id).first()
if not dojo:
# We are likely missing the correct dojos in the DB (e.g., custom deployment)
From f5f0b3c3ec3014ed1998565899acfd1d757239d3 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 19 Jan 2024 22:22:50 -0700
Subject: [PATCH 032/123] move belt awarding logic to utils.awards
---
dojo_plugin/__init__.py | 20 +++-----------------
dojo_plugin/utils/awards.py | 20 +++++++++++++++++++-
2 files changed, 22 insertions(+), 18 deletions(-)
diff --git a/dojo_plugin/__init__.py b/dojo_plugin/__init__.py
index 3d954bcd0..5ae517b04 100644
--- a/dojo_plugin/__init__.py
+++ b/dojo_plugin/__init__.py
@@ -19,7 +19,7 @@
from .config import DOJO_HOST, bootstrap
from .utils import unserialize_user_flag, render_markdown
from .utils.discord import get_discord_user, get_discord_roles, add_role, send_message
-from .utils.awards import get_user_belts, get_user_emoji
+from .utils.awards import update_awards, get_user_belts
from .pages.dojos import dojos, dojos_override
from .pages.dojo import dojo
from .pages.workspace import workspace
@@ -42,27 +42,13 @@ class DojoChallenge(BaseChallenge):
@classmethod
def solve(cls, user, team, challenge, request):
super().solve(user, team, challenge, request)
-
- current_belts = get_user_belts(user)
- for belt in current_belts:
- belt_award = Belts.query.filter_by(user=user, name=belt).first()
- if belt_award:
- continue
- db.session.add(Belts(user=user, name=belt))
- db.session.commit()
-
- current_emojis = get_user_emojis(user)
- for emoji in current_emojis:
- emoji_award = Emojis.query.filter_by(user=user, name=emoji).first()
- if emoji_award:
- continue
- db.session.add(Emojis(user=user, name=emoji))
- db.session.commit()
+ update_awards(user)
discord_user = get_discord_user(user.id)
if not discord_user:
return
+ current_belts = get_user_belts(user)
discord_roles = get_discord_roles()
for belt in current_belts:
belt_role = belt.title() + " Belt"
diff --git a/dojo_plugin/utils/awards.py b/dojo_plugin/utils/awards.py
index 9d034420b..cdbf12623 100644
--- a/dojo_plugin/utils/awards.py
+++ b/dojo_plugin/utils/awards.py
@@ -1,8 +1,9 @@
import datetime
from CTFd.cache import cache
+from CTFd.models import db
from flask import url_for
-from ..models import Dojos, Belts
+from ..models import Dojos, Belts, Emojis
BELT_REQUIREMENTS = {
@@ -75,3 +76,20 @@ def get_belts():
rank.sort(key=lambda uid: result["users"][uid]["date"])
return result
+
+def update_awards(user):
+ current_belts = get_user_belts(user)
+ for belt in current_belts:
+ belt_award = Belts.query.filter_by(user=user, name=belt).first()
+ if belt_award:
+ continue
+ db.session.add(Belts(user=user, name=belt))
+ db.session.commit()
+
+ current_emojis = get_user_emojis(user)
+ for emoji in current_emojis:
+ emoji_award = Emojis.query.filter_by(user=user, name=emoji).first()
+ if emoji_award:
+ continue
+ db.session.add(Emojis(user=user, name=emoji))
+ db.session.commit()
From bd4086bc7313697b50c87ddbe15d71cbc4a47e58 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 19 Jan 2024 23:21:05 -0700
Subject: [PATCH 033/123] display earned badges (for visible dojos) on
scoreboards
---
dojo_plugin/api/v1/scoreboard.py | 19 ++++++++++++++++---
dojo_plugin/utils/awards.py | 9 +++++----
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/dojo_plugin/api/v1/scoreboard.py b/dojo_plugin/api/v1/scoreboard.py
index 5d6393749..3de1338b4 100644
--- a/dojo_plugin/api/v1/scoreboard.py
+++ b/dojo_plugin/api/v1/scoreboard.py
@@ -17,7 +17,7 @@
from sqlalchemy import event
from sqlalchemy.orm.session import Session
-from ...models import Dojos, DojoChallenges, DojoUsers, DojoMembers, DojoAdmins, DojoStudents, DojoModules, DojoChallengeVisibilities
+from ...models import Dojos, DojoChallenges, DojoUsers, DojoMembers, DojoAdmins, DojoStudents, DojoModules, DojoChallengeVisibilities, Emojis
from ...utils import dojo_standings, user_dojos, first_bloods, daily_solve_counts
from ...utils.dojo import dojo_route, dojo_accessible
from ...utils.awards import get_belts, belt_asset
@@ -89,6 +89,20 @@ def get_scoreboard_page(model, duration=None, page=1, per_page=20):
start_idx = (page - 1) * per_page
end_idx = start_idx + per_page
pagination = Pagination(None, page, per_page, len(results), results[start_idx:end_idx])
+ user = get_current_user()
+
+ viewable_dojos = { dojo.reference_id for dojo in Dojos.viewable(user=user) }
+ emojis = { }
+ for emoji in Emojis.query.order_by(Emojis.date).all():
+ if emoji.category not in viewable_dojos:
+ continue
+
+ emojis.setdefault(emoji.user.id, []).append({
+ "text": emoji.description,
+ "emoji": emoji.name,
+ "count": 1,
+ "url": url_for("pwncollege_dojo.listing", dojo=emoji.category)
+ })
def standing(item):
if not item:
@@ -97,7 +111,7 @@ def standing(item):
result["url"] = url_for("pwncollege_users.view_other", user_id=result["user_id"])
result["symbol"] = email_symbol_asset(result.pop("email"))
result["belt"] = belt_asset(belt_data["users"].get(result["user_id"], {"color":None})["color"])
- result["badges"] = [] # TODO
+ result["badges"] = emojis.get(result["user_id"], [])
return result
result = {
@@ -106,7 +120,6 @@ def standing(item):
pages = set(page for page in pagination.iter_pages() if page)
- user = get_current_user()
if user and not user.hidden:
me = None
for r in results:
diff --git a/dojo_plugin/utils/awards.py b/dojo_plugin/utils/awards.py
index cdbf12623..12e382501 100644
--- a/dojo_plugin/utils/awards.py
+++ b/dojo_plugin/utils/awards.py
@@ -40,7 +40,7 @@ def get_user_emojis(user):
if not emoji:
continue
if dojo.completed(user):
- emojis.append(emoji)
+ emojis.append((emoji, dojo.name, dojo.reference_id))
return emojis
def get_belts():
@@ -87,9 +87,10 @@ def update_awards(user):
db.session.commit()
current_emojis = get_user_emojis(user)
- for emoji in current_emojis:
- emoji_award = Emojis.query.filter_by(user=user, name=emoji).first()
+ for emoji,dojo_name,dojo_id in current_emojis:
+ # note: the category filter is critical, since SQL seems to be unable to query by emoji!
+ emoji_award = Emojis.query.filter_by(user=user, name=emoji, category=dojo_id).first()
if emoji_award:
continue
- db.session.add(Emojis(user=user, name=emoji))
+ db.session.add(Emojis(user=user, name=emoji, description=f"Awarded for completing the {dojo_name} dojo.", category=dojo_id))
db.session.commit()
From 0a6761915ab6c1b69f054aa38f1010a242fb5b2e Mon Sep 17 00:00:00 2001
From: Connor Nelson
Date: Mon, 22 Jan 2024 18:18:49 -0700
Subject: [PATCH 034/123] Infrastructure: Fix ctfd command
---
docker-compose.yml | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index f4e37af28..edfd6fbe6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -23,10 +23,15 @@ services:
restart: always
privileged: true
entrypoint: ""
- command: >
- /bin/sh -c '[ "$DOJO_ENV" != "development" ] &&
- ./docker-entrypoint.sh ||
- FLASK_ENV=development WERKZEUG_DEBUG_PIN=off flask run --host 0.0.0.0 --port 8000'
+ command:
+ - /bin/sh
+ - -c
+ - |
+ if [ "$DOJO_ENV" != "development" ]; then
+ ./docker-entrypoint.sh;
+ else
+ FLASK_ENV=development WERKZEUG_DEBUG_PIN=off flask run --host 0.0.0.0 --port 8000;
+ fi
ulimits:
nofile:
soft: 32768
From 7e64adc7e446d858592a9ca3cb96e54f1f7d8805 Mon Sep 17 00:00:00 2001
From: Yan
Date: Mon, 22 Jan 2024 13:28:53 -0700
Subject: [PATCH 035/123] make type:public dojos public
don't show dojos with passwords
---
dojo_plugin/models/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/dojo_plugin/models/__init__.py b/dojo_plugin/models/__init__.py
index 6dcd81d70..36a0943cd 100644
--- a/dojo_plugin/models/__init__.py
+++ b/dojo_plugin/models/__init__.py
@@ -185,6 +185,7 @@ def viewable(cls, id=None, user=None):
return (
(cls.from_id(id) if id is not None else cls.query)
.filter(or_(cls.official,
+ and_(cls.data["type"] == "public", cls.password == None),
cls.dojo_id.in_(db.session.query(DojoUsers.dojo_id)
.filter_by(user=user)
.subquery())))
From 319120f087e4b445290707e72e42f3e8319bb66a Mon Sep 17 00:00:00 2001
From: Yan
Date: Mon, 22 Jan 2024 14:05:00 -0700
Subject: [PATCH 036/123] description text in the dojos listing
---
dojo_theme/templates/dojos.html | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/dojo_theme/templates/dojos.html b/dojo_theme/templates/dojos.html
index 68523ef01..bab959e13 100644
--- a/dojo_theme/templates/dojos.html
+++ b/dojo_theme/templates/dojos.html
@@ -10,6 +10,13 @@
Dojos
{% for type, dojos in typed_dojos.items() %}
{{ type | title }}
+ {% if type == "Topics" %}
+
These dojos form the official the pwn.college curriculum. We recommend that you tackle them in order. Good luck!
+ {% elif type == "Courses" %}
+
We run a number of courses on this platform. For the most part, these courses import the above material, though some might introduce new concepts and challenges.
+ {% elif type == "More" %}
+
This section contains public dojos created by the pwn.college community. Completing these dojos will grant you emoji badges!
+ {% endif %}
{% for dojo in dojos %}
{{ card(url_for("pwncollege_dojos.view_dojo", dojo=dojo.reference_id),
From 5279d96c5cd95490fd2e2d01f9ee953fddf3efb1 Mon Sep 17 00:00:00 2001
From: Yan
Date: Mon, 22 Jan 2024 14:16:18 -0700
Subject: [PATCH 037/123] show emoji awards on dojos
---
dojo_theme/templates/dojos.html | 3 ++-
dojo_theme/templates/macros/widgets.html | 7 +++++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/dojo_theme/templates/dojos.html b/dojo_theme/templates/dojos.html
index bab959e13..98d01f4ed 100644
--- a/dojo_theme/templates/dojos.html
+++ b/dojo_theme/templates/dojos.html
@@ -22,7 +22,8 @@
{{ type | title }}
{{ card(url_for("pwncollege_dojos.view_dojo", dojo=dojo.reference_id),
title=dojo.name,
text="{} Modules : ".format(dojo.modules | length) + "{} / {}".format(dojo.solves(user=user, ignore_visibility=True, ignore_admins=False).count() if user else 0, dojo.challenges | length),
- icon="/themes/dojo_theme/static/img/dojo/{}.svg".format(dojo.award.belt) if (dojo.award.belt and dojo.official) else None) }}
+ icon="/themes/dojo_theme/static/img/dojo/{}.svg".format(dojo.award.belt) if (dojo.award.belt and dojo.official) else None,
+ emoji=dojo.award.emoji ) }}
{% endfor %}
{% if type == "More" %}
diff --git a/dojo_theme/templates/macros/widgets.html b/dojo_theme/templates/macros/widgets.html
index 36aa8725d..cc0436c3f 100644
--- a/dojo_theme/templates/macros/widgets.html
+++ b/dojo_theme/templates/macros/widgets.html
@@ -16,13 +16,16 @@
{% endmacro %}
-{% macro card(url, title=None, text=None, icon=None, custom=False) -%}
+{% macro card(url, title=None, text=None, icon=None, emoji=None, custom=False) -%}
We run a number of courses on this platform. For the most part, these courses import the above material, though some might introduce new concepts and challenges.
{% elif type == "More" %}
From 4923345ddf7a72cd72aecc79c10cbb4f722d09f4 Mon Sep 17 00:00:00 2001
From: Yan Shoshitaishvili
Date: Fri, 26 Jan 2024 11:28:00 -0700
Subject: [PATCH 072/123] use a better icon to represent entering the dojo
learning space
---
dojo_theme/templates/components/navbar.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dojo_theme/templates/components/navbar.html b/dojo_theme/templates/components/navbar.html
index abee7d571..8f7b44e6d 100644
--- a/dojo_theme/templates/components/navbar.html
+++ b/dojo_theme/templates/components/navbar.html
@@ -15,7 +15,7 @@
- {{ navitem("Dojos", url_for("pwncollege_dojos.listing"), "fa-bolt") }}
+ {{ navitem("Dojos", url_for("pwncollege_dojos.listing"), "fa-torii-gate") }}
{{ navitem("Workspace", url_for("pwncollege_workspace.view_workspace", service="vscode"), "fa-terminal") }}
{{ navitem("Desktop", url_for("pwncollege_workspace.view_workspace", service="desktop"), "fa-desktop") }}
{{ navitem("Help", url_for("pwncollege_sensai.view_sensai"), "fa-question") }}
From 7094a88ad9d48c2e7a6286b5f28329be2e839516 Mon Sep 17 00:00:00 2001
From: Connor Nelson
Date: Fri, 26 Jan 2024 14:14:23 -0700
Subject: [PATCH 073/123] Update brown and red belts to use same format as
other belts
---
dojo_theme/static/img/dojo/brown.svg | 23 +++++++++++------------
dojo_theme/static/img/dojo/red.svg | 2 +-
2 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/dojo_theme/static/img/dojo/brown.svg b/dojo_theme/static/img/dojo/brown.svg
index 11cf3b776..40b8b750d 100644
--- a/dojo_theme/static/img/dojo/brown.svg
+++ b/dojo_theme/static/img/dojo/brown.svg
@@ -1,13 +1,12 @@
-
-
diff --git a/dojo_theme/static/img/dojo/red.svg b/dojo_theme/static/img/dojo/red.svg
index 2e769f5a1..d26075819 100644
--- a/dojo_theme/static/img/dojo/red.svg
+++ b/dojo_theme/static/img/dojo/red.svg
@@ -9,4 +9,4 @@
-
\ No newline at end of file
+
From a286dfa0439537fe25e82ec7fdbc3519970f5848 Mon Sep 17 00:00:00 2001
From: Connor Nelson
Date: Fri, 26 Jan 2024 14:26:14 -0700
Subject: [PATCH 074/123] Update purple belt to use same format as other belts
---
dojo_theme/static/img/dojo/purple.svg | 32 ++++++++++-----------------
1 file changed, 12 insertions(+), 20 deletions(-)
diff --git a/dojo_theme/static/img/dojo/purple.svg b/dojo_theme/static/img/dojo/purple.svg
index d1fdf14cf..5d78ad0e5 100644
--- a/dojo_theme/static/img/dojo/purple.svg
+++ b/dojo_theme/static/img/dojo/purple.svg
@@ -1,20 +1,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
From 8d0c53ecc6b566d119cd61f9c6266e5554dd0da5 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 26 Jan 2024 16:18:21 -0700
Subject: [PATCH 075/123] streamline
---
index.html | 80 +++++++++++++++++++++++++++++-------------------------
1 file changed, 43 insertions(+), 37 deletions(-)
diff --git a/index.html b/index.html
index a4e516bda..5fab42aea 100644
--- a/index.html
+++ b/index.html
@@ -6,58 +6,53 @@
Learn to hack!
# Welcome to pwn.college!
-pwn.college is an education platform for students (and other interested parties) to learn about, and practice, core cybersecurity concepts in a hands-on fashion. In martial arts terms, it is designed to take a “[white belt](https://pwn.college/fundamentals/)” in cybersecurity to becoming a “[blue belt](https://pwn.college/cse494-s2023/)”, able to approach (simple) CTFs and wargames. The philosophy of pwn.college is “practice makes perfect”.
+pwn.college is an education platform for students (and other interested parties) to learn about, and practice, core cybersecurity concepts in a hands-on fashion.
+In martial arts terms, it is designed to take a “[white belt](/fundamentals/)” in cybersecurity to becoming a “[blue belt](/software-exploitation)”, able to approach (simple) CTFs and wargames.
+The philosophy of pwn.college is “practice makes perfect”.
-pwn.college was created by [Zardus (Yan Shoshitaishvili)](http://yancomm.net) and [kanak (Connor Nelson)](https://connornelson.com) at Arizona State University. It powers ASU’s Introduction to Cybersecurity (CSE 365) and Computer Systems Security (CSE 466) courses, and is open, for free, to participation for interested people around the world!
+pwn.college was created by [Zardus (Yan Shoshitaishvili)](http://yancomm.net) and [kanak (Connor Nelson)](https://connornelson.com) at Arizona State University.
+It powers much of ASU's cybersecurity curriculum, and is open, for free, to participation for interested people around the world!
If you have comments, suggestions, and feedback, please email us at [pwn@pwn.college](mailto:pwn@pwn.college)!
-
-
-
-
-
-
# Great! How do I jump in?
-pwn.college is organized into a series of modules, that launch throughout the school year and stay open until the next iteration of the courses. For each module, students should go through the following process:
-1. Watch the **PRERECORDED MODULE LECTURES** on [YouTube](https://www.youtube.com/pwncollege). These classes represent the bulk of the passive educational content in pwn.college! They give you a starting point from which to approach the practice problems (Step 3, below).
+pwn.college is organized into a series of dojos, each of which covers a high-level topic area.
+Please start your journey with the `Using the Dojo` dojo.
+You can access it (and many other dojos!) through the Dojos link at the top of the page or (if you promise not to forget about the Dojos link later) by [clicking here](/welcome).
-2. Attend the LIVE CLASSES on [Twitch](https://www.twitch.tv/pwncollege), during the ASU semester.
+When you run into problems with concepts and challenges, you can get help on our [discord server](https://discord.gg/pwncollege).
+The discord is also a great place to chat with other aspiring hackers!
-3. Catch up on any live classes you miss by checking out RECORDED STREAMS, which are [initially saved on Twitch](https://www.twitch.tv/pwncollege/videos) right after the live stream (but time out after a week or two) and are eventually [archived to YouTube](https://www.youtube.com/pwncollege). The live classes are used to fill in gaps between prerecorded lectures and answer student questions. Current live classes:
-
-4. Solve the **CHALLENGE PROBLEMS** hosted in the [dojos](https://pwn.college/dojos). The challenge problems are the active educational component of pwn.college. They are absolutely critical to learning: the lectures give you a starting point for the content, but the challenge problems force you to truly learn it.
+
-5. When you run into problems with the challenges, you can GET HELP on our [discord server](https://discord.com/invite/e4UU4ytK6q). The discord is also a great place to chat with other aspiring hackers!
+# The "School Year"
-pwn.college currently has four major stages of progression. Consider hacking as a martial art that students earn belts in as they progress. We currently have four belts in four dedicated dojos: [orange](https://pwn.college/intro-to-cybersecurity/), [yellow](https://pwn.college/program-security/), [green](https://pwn.college/system-security/), and [blue](https://pwn.college/software-exploitation/).
+pwn.college is available year-round, but during ASU's semester, we host live classes on [our Twitch channel](https://www.twitch.tv/pwncollege) to dig into the material currently being covered on pwn.college, then archive those to [our YouTube channel](https://www.youtube.com/pwncollege).
+The live classes are used to fill in gaps between prerecorded lectures and answer student questions.
-Over time, hackers become more sure in their skills, achieving *brown belt* status (and able to, for example, usefully contribute to the cybersecurity industry), before finally graduating to hacking masters: *black belts*. But this, unfortunately, must happen outside of the dojo (for now!).
+Current live classes:
-
-
-# Launching Challenges
-At the core of pwn.college is flags. How do you get those flags? Solve challenges. You can start a challenge by clicking on the `Challenges` tab at the top, selecting a module, clicking on a particular level, and hitting `Start`. In order to access that challenge, you have two options.
+
-The first option is using the `Workspace` tab. This will present you with a fully functional development environment in your browser via Visual Studio Code. You can, for instance, quickly open a new terminal by pressing `F1`, searching for `New Terminal`, and pressing enter.
-The second option is using `ssh`. In order to ssh into your challenge instances, you must add a public ssh key to `Settings` > `SSH Key`. You can quickly generate an ssh key by running `ssh-keygen -f key -N ''` in a terminal on your (unix-friendly) host machine. This will generate files `key` and `key.pub`, which are your private and public keys respectively. Once you have linked your ssh key to your account, you can run `ssh -i key hacker@dojo.pwn.college` to connect into your challenge instance.
+
-Once you are in a challenge instance, your goal is to get the contents of the `/flag` file. Unfortunately for you, you are executing as the `hacker` user, but `/flag` is only readable by the `root` user. Fortunately, however, there are challenge programs located inside of the `/challenge` directory, which when run, will run with the privileges of the `root` user. Solve the challenge to get the `/flag`, and then submit it in order to complete the challenge!
+# The Belts
-A few things to note. Your home directory `/home/hacker` is persistent. This means that when you start a new challenge, all of the files you have saved in there will still be there. The `Practice` button can be incredibly useful for debugging your solution. When you start a challenge in this way, you will have the ability to run programs as the `root` user with the `sudo` command; however, the instance will only have a practice flag. For some of the later (kernel-focused) challenges, you will need to solve the challenge in a virtual machine. You can interact with the virtual machine using the `vm` command.
-
+Consider hacking as a martial art that students earn belts in as they progress and build their skills.
+This course progresses hackers from white belts, to orange belts, yellow belts, green belts, and then finally refines them into blue belts.
+This is not just metaphor: **we have actual belts, in yellow, green, and blue, custom-embroidered for pwn.college.**
-# The Belts
-This course progresses hackers from white belts, to orange belts, yellow belts, green belts, and then finally refines them into blue belts. This is not just metaphor: **we have actual belts, in yellow, green, and blue, custom-embroidered for pwn.college.**
+Eventually, hackers continue their journey beyond pwn.college, becoming certain in their skills, achieving *brown belt* status (and able to, for example, usefully contribute to the cybersecurity industry and academia), before finally graduating to hacking masters: *black belts*.
+But this, unfortunately, must happen outside of the dojo (for now!).
### How to earn an orange belt?
For an orange belt, you must complete all active challenges launched in:
@@ -82,14 +77,18 @@
Learn to hack!
- [Software Exploitation](https://pwn.college/software-exploitation/)
### How to get the actual belt?
-To get your belt, [send us an email](malto:pwn@pwn.college) from the email address associated with your pwn.college account once you’ve completed the necessary challenges. We’ll then get your belt over to you (eventually)!
+To get your belt, [send us an email](malto:pwn@pwn.college) from the email address associated with your pwn.college account once you’ve completed the necessary challenges.
+We’ll then get your belt over to you (eventually)!
+Note that, due to logistical challenges, we're currently only _shipping_ belts to hackers after they earn their blue belt. Until then, we will belt you in person, at ASU or some security conference.
**Who has earned belts?** We maintain a [list of hackers with pwn.college belts](https://pwn.college/belts). Once you achieve your belt, we will add you to the [list](https://pwn.college/belts)!
# Collaboration, Livestream, and Writeup Policy
-The challenges created for pwn.college are educational material, and are used to grade students at ASU. Because of this, we would appreciate that writeups, walkthrough videos, and livestreams of challenge solutions are not posted to the internet. Obviously, we can’t enforce this, but we worked hard to make all of this public, and we would appreciate your help in keeping pwn.college a viable educational platform.
+The challenges created for pwn.college are educational material, and are used to grade students at ASU.
+Because of this, we would appreciate that writeups, walkthrough videos, and livestreams of challenge solutions are not posted to the internet.
+Obviously, we can’t enforce this, but we worked hard to make all of this public, and we would appreciate your help in keeping pwn.college a viable educational platform.
@@ -107,7 +106,10 @@
Learn to hack!
# Reusing pwn.college Material
Want to use pwn.college in your course? You can!
-The videos and slides of pwn.college lectures are licensed under CC-BY-NC. You can use them freely for non-commercial purposes, but please provide attribution! Additionally, if you use pwn.college in your own education program, we would appreciate it if you email us to let us know. Evidence of wide-spread use of pwn.college for education will be a huge help for Yan’s tenure case!
+The videos and slides of pwn.college lectures are licensed under CC-BY-NC.
+You can use them freely for non-commercial purposes, but please provide attribution!
+Additionally, if you use pwn.college in your own education program, we would appreciate it if you email us to let us know.
+Evidence of wide-spread use of pwn.college for education will be a huge help for Yan’s tenure case!
EDUCATORS: If you are an educational institution and are using pwn.college as part of a class, you can request a (free) private instance of the infrastructure! Please [email](mailto:pwn@pwn.college) us to ask!
@@ -116,18 +118,22 @@
Learn to hack!
# Contributing
-The [infrastructure powering pwn.college](https://github.com/pwncollege) is open source, and we welcome pull requests and issues. The modules are closed-source, because they include source code and solution scripts. If you are an educator, or otherwise someone we trust, and are interested in collaborating on the modules themselves, please email us at [pwn@pwn.college](pwn@pwn.college). Likewise, drop us a line if you are interested in collaborating on the slides!
+The [infrastructure powering pwn.college](https://github.com/pwncollege/dojo) is open source, and we welcome pull requests and issues.
+Some of the modules are closed-source, because they include source code and solution scripts, but others can be found on [github](https://github.com/pwncollege).
+If you are an educator, or otherwise someone we trust, and are interested in collaborating on the modules themselves, please email us at [pwn@pwn.college](pwn@pwn.college).
+Likewise, drop us a line if you are interested in collaborating on the slides!
# Greetz
Team work makes the dream work, and the team behind pwn.college is full of dreamers! They are:
- - [Zardus (Yan Shoshitaishvili)](https://yancomm.net) originated the idea, created the initial prototype and many of the challenges, and continues to cajole everyone else into a shared vision!
+- [Zardus (Yan Shoshitaishvili)](https://yancomm.net) originated the idea, created the initial prototype and many of the challenges, and continues to cajole everyone else into a shared vision!
- [kanak (Connor Nelson)](https://connornelson.com) made the mistake of signing up to TA an “easy A” course and quickly found himself reimplementing Yan’s insane code into the amazing dojo you see today, along with the creation of many challenges.
+- [robwaz (Robert Wasinger)](https://github.com/robwaz) for the creation of the Software Exploitation dojo.
+- [Adam Doupe](https://adamdoupe.com) for helping in the brainstorming of the initial concept and helping develop the pwn.college platform.
- [mahaloz (Zion Basque)](https://www.zionbasque.com) for contributing ROP challenges and embryoasm to pwn.college.
- [Erik Trickel](https://twitter.com/eriktrickel) for helping in the development of pedagogical concepts underlying pwn.college.
-- [Adam Doupe](https://adamdoupe.com) for helping in the brainstorming of the initial concept.
- redgate, for contributing to embryoasm.
- [Pascal-0x90](https://twitter.com/pascal_0x90), for contributing to embryoasm.
- [frqmod](https://twitter.com/frqmod), for contributing embryogdb.
From d3ddc9bf820257810c199e8e7e5b0dc3bc61376e Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 26 Jan 2024 16:24:58 -0700
Subject: [PATCH 076/123] dash
---
index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/index.html b/index.html
index 5fab42aea..434dbf188 100644
--- a/index.html
+++ b/index.html
@@ -37,8 +37,8 @@
# Welcome to pwn.college!
pwn.college is an education platform for students (and other interested parties) to learn about, and practice, core cybersecurity concepts in a hands-on fashion.
In martial arts terms, it is designed to take a “[white belt](/fundamentals/)” in cybersecurity to becoming a “[blue belt](/software-exploitation)”, able to approach (simple) CTFs and wargames.
-The philosophy of pwn.college is “practice makes perfect”.
+Our philosophy is “practice makes perfect”.
-pwn.college was created by [Zardus (Yan Shoshitaishvili)](http://yancomm.net) and [kanak (Connor Nelson)](https://connornelson.com) at Arizona State University.
+The platform is maintained by an [awesome team](#greetz) of hackers at Arizona State University.
It powers much of ASU's cybersecurity curriculum, and is open, for free, to participation for interested people around the world!
If you have comments, suggestions, and feedback, please email us at [pwn@pwn.college](mailto:pwn@pwn.college)!
@@ -125,7 +125,7 @@
Learn to hack!
-# Greetz
+# Greetz
Team work makes the dream work, and the team behind pwn.college is full of dreamers! They are:
- [Zardus (Yan Shoshitaishvili)](https://yancomm.net) originated the idea, created the initial prototype and many of the challenges, and continues to cajole everyone else into a shared vision!
From 18edc1ceb53ebb223e19739528f9244b4b57f6c4 Mon Sep 17 00:00:00 2001
From: Connor Nelson
Date: Fri, 26 Jan 2024 18:20:23 -0700
Subject: [PATCH 078/123] CSS: Add drop shadow to card icons
---
dojo_theme/static/css/custom.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/dojo_theme/static/css/custom.css b/dojo_theme/static/css/custom.css
index 69dc19fef..61b68976c 100644
--- a/dojo_theme/static/css/custom.css
+++ b/dojo_theme/static/css/custom.css
@@ -192,6 +192,7 @@ h4 {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
+ filter: drop-shadow(0px 0px 3px black);
}
.accordion-item {
From d25491b5f71aed42e6b87493c8b7193ef0d5e8cf Mon Sep 17 00:00:00 2001
From: Yan Shoshitaishvili
Date: Fri, 26 Jan 2024 23:30:50 -0700
Subject: [PATCH 079/123] source /challenge/.bashrc in every hacker shell
(#309)
* source env variables from /challenge/.env on startup
* Name the sourced file more explicitly --- it's a challenge-specific bashrc
---
challenge/bash.bashrc | 2 ++
1 file changed, 2 insertions(+)
diff --git a/challenge/bash.bashrc b/challenge/bash.bashrc
index c5d990a30..28edb3760 100755
--- a/challenge/bash.bashrc
+++ b/challenge/bash.bashrc
@@ -11,3 +11,5 @@ if [ -e "/challenge/README.md" ] && [ ! -e "/tmp/.dojo/readme-once" ]; then
touch /tmp/.dojo/readme-once
fi
+
+[ -f "/challenge/.bashrc" ] && source /challenge/.bashrc
From e2640e810f916c3a8f3b743cdb216a2836dcc7c6 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 26 Jan 2024 23:24:16 -0700
Subject: [PATCH 080/123] fix check for cache key insertion on dojo create
---
dojo_plugin/api/v1/dojo.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dojo_plugin/api/v1/dojo.py b/dojo_plugin/api/v1/dojo.py
index 4c40d8556..a5a4c93de 100644
--- a/dojo_plugin/api/v1/dojo.py
+++ b/dojo_plugin/api/v1/dojo.py
@@ -75,7 +75,7 @@ def create_dojo(user, repository, public_key, private_key):
traceback.print_exc(file=sys.stderr)
return {"success": False, "error": str(e)}, 400
- return {"success": True, "dojo": dojo.reference_id}
+ return {"success": True, "dojo": dojo.reference_id}, 200
@dojo_namespace.route("//prune-awards")
class PruneAwards(Resource):
@@ -128,7 +128,7 @@ def post(self):
return {"success": False, "error": "You can only create 1 dojo per day."}, 429
result = create_dojo(user, repository, public_key, private_key)
- if result["success"]:
+ if result[0]["success"]:
cache.set(key, 1, timeout=timeout)
return result
From 8986b32e8d6eba3bbdf78b2b721446730556ff70 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 26 Jan 2024 23:40:45 -0700
Subject: [PATCH 081/123] some might be private
---
dojo_theme/templates/dojos.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dojo_theme/templates/dojos.html b/dojo_theme/templates/dojos.html
index d8bcaa3c7..ef8993362 100644
--- a/dojo_theme/templates/dojos.html
+++ b/dojo_theme/templates/dojos.html
@@ -20,7 +20,7 @@
{{ type | title }}
{% elif type == "Courses" %}
We run a number of courses on this platform. For the most part, these courses import the above material, though some might introduce new concepts and challenges.
{% elif type == "More" %}
-
This section contains public dojos created by the pwn.college community. Completing these dojos will grant you emoji badges!
+
This section contains dojos created by the pwn.college community. Completing these dojos will grant you emoji badges!
{% endif %}
{% for dojo in dojos %}
From 60acdce01f4dfcf600a52b9e086c446ae3f384d4 Mon Sep 17 00:00:00 2001
From: Yan
Date: Fri, 26 Jan 2024 23:45:35 -0700
Subject: [PATCH 082/123] updating dojo name
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 14355a20c..69cad1da1 100644
--- a/index.html
+++ b/index.html
@@ -20,7 +20,7 @@
Learn to hack!
# Great! How do I jump in?
pwn.college is organized into a series of dojos, each of which covers a high-level topic area.
-Please start your journey with the `Using the Dojo` dojo.
+Please start your journey with the `Getting Started` dojo.
You can access it (and many other dojos!) through the Dojos link at the top of the page or (if you promise not to forget about the Dojos link later) by [clicking here](/welcome).
When you run into problems with concepts and challenges, you can get help on our [discord server](https://discord.gg/pwncollege).
From 79ad58e708305ac93282b142031bd8a8bbcfce8a Mon Sep 17 00:00:00 2001
From: Yan Shoshitaishvili
Date: Sat, 27 Jan 2024 23:25:49 -0700
Subject: [PATCH 083/123] source the pwn.college bashrc rather than calling it
(#310)
---
challenge/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/challenge/Dockerfile b/challenge/Dockerfile
index 02ce6585c..df478146b 100644
--- a/challenge/Dockerfile
+++ b/challenge/Dockerfile
@@ -566,7 +566,7 @@ RUN <> /etc/bash.bashrc
+ echo -e '\n. /opt/pwn.college/bash.bashrc' >> /etc/bash.bashrc
find / -xdev -type f -perm -4000 -exec chmod u-s {} \;
From e1664d30c19888fc6aee1a9f166a2eb5d848f803 Mon Sep 17 00:00:00 2001
From: Yan
Date: Sat, 27 Jan 2024 23:28:20 -0700
Subject: [PATCH 084/123] some safety: only source /opt/pwn.college/bash.bashrc
for the hacker user
---
challenge/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/challenge/Dockerfile b/challenge/Dockerfile
index df478146b..3931d77ca 100644
--- a/challenge/Dockerfile
+++ b/challenge/Dockerfile
@@ -566,7 +566,7 @@ RUN <> /etc/bash.bashrc
+ echo -e '\n[ "$UID" -eq 1000 ] && . /opt/pwn.college/bash.bashrc' >> /etc/bash.bashrc
find / -xdev -type f -perm -4000 -exec chmod u-s {} \;
From 76ea0d1d712526d899231b6db0451b3fa4467bf6 Mon Sep 17 00:00:00 2001
From: Connor Nelson
Date: Sun, 28 Jan 2024 10:29:39 -0700
Subject: [PATCH 085/123] Home Page: Lower pwn.college name
---
index.html | 2 ++
1 file changed, 2 insertions(+)
diff --git a/index.html b/index.html
index 69cad1da1..f3284b79f 100644
--- a/index.html
+++ b/index.html
@@ -1,3 +1,5 @@
+
+
pwn.college
Learn to hack!
From 3ade4f246b47cba25284a70b687c043755ac54d3 Mon Sep 17 00:00:00 2001
From: Yan Shoshitaishvili
Date: Sun, 28 Jan 2024 20:31:00 -0700
Subject: [PATCH 086/123] TAKE DOWN THE TYRRANY OF /
---
dojo_plugin/pages/dojo.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/dojo_plugin/pages/dojo.py b/dojo_plugin/pages/dojo.py
index ff637252d..cfdda1e89 100644
--- a/dojo_plugin/pages/dojo.py
+++ b/dojo_plugin/pages/dojo.py
@@ -43,6 +43,7 @@ def get_stats(dojo):
}
+@dojo.route("/")
@dojo.route("//")
@dojo_route
@check_challenge_visibility
From 8cdf9baabe0ddf0a75c6aa61dab0b64ec70b0c1b Mon Sep 17 00:00:00 2001
From: Yan
Date: Mon, 29 Jan 2024 00:18:33 -0700
Subject: [PATCH 087/123] add more info on admin_dojos page
---
dojo_plugin/models/__init__.py | 5 +++++
dojo_theme/templates/admin_dojos.html | 10 ++++++++++
2 files changed, 15 insertions(+)
diff --git a/dojo_plugin/models/__init__.py b/dojo_plugin/models/__init__.py
index 2311d0868..fd2f694d9 100644
--- a/dojo_plugin/models/__init__.py
+++ b/dojo_plugin/models/__init__.py
@@ -171,6 +171,11 @@ def hash(self):
from ..utils.dojo import dojo_git_command
return dojo_git_command(self, "rev-parse", "HEAD").stdout.decode().strip()
+ @property
+ def last_commit_time(self):
+ from ..utils.dojo import dojo_git_command
+ return datetime.datetime.fromisoformat(dojo_git_command(self, "show", "--no-patch", "--format=%ci", "HEAD").stdout.decode().strip().replace(" -", "-")[:-2]+":00")
+
@classmethod
def ordering(cls):
return (
diff --git a/dojo_theme/templates/admin_dojos.html b/dojo_theme/templates/admin_dojos.html
index d08a822a1..09ed28a55 100644
--- a/dojo_theme/templates/admin_dojos.html
+++ b/dojo_theme/templates/admin_dojos.html
@@ -9,6 +9,16 @@
{% for admin in dojo.admins %}
From e3aa45c8875df4f0f1e7923d6cc2c99457e24ea0 Mon Sep 17 00:00:00 2001
From: Yan
Date: Mon, 29 Jan 2024 01:06:22 -0700
Subject: [PATCH 088/123] move official example dojos to the dojo create page
---
dojo_plugin/pages/dojos.py | 3 +++
dojo_theme/templates/dojo_create.html | 12 ++++++++++++
2 files changed, 15 insertions(+)
diff --git a/dojo_plugin/pages/dojos.py b/dojo_plugin/pages/dojos.py
index 653a50629..8cbbba008 100644
--- a/dojo_plugin/pages/dojos.py
+++ b/dojo_plugin/pages/dojos.py
@@ -41,6 +41,8 @@ def listing():
typed_dojos["Courses"].append(dojo)
elif dojo.type == "hidden":
continue
+ elif dojo.type == "example" and dojo.official:
+ continue
else:
typed_dojos["More"].append(dojo)
@@ -55,6 +57,7 @@ def dojo_create():
"dojo_create.html",
public_key=public_key,
private_key=private_key,
+ example_dojos=Dojos.viewable().where(Dojos.data["type"] == "example").all()
)
diff --git a/dojo_theme/templates/dojo_create.html b/dojo_theme/templates/dojo_create.html
index 794a09300..fa5a3cb42 100644
--- a/dojo_theme/templates/dojo_create.html
+++ b/dojo_theme/templates/dojo_create.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+{% from "macros/widgets.html" import card %}
{% block content %}
@@ -7,6 +8,17 @@
Create Dojo
+
pwn.college dojos are defined in a git repository. You can use the example repositories listed below, or our official dojo repositories, for examples on how to build a dojo!
+
+ {% for dojo in example_dojos %}
+ {{ card(url_for("pwncollege_dojos.view_dojo", dojo=dojo.reference_id),
+ title=dojo.name,
+ text="{} Modules : ".format(dojo.modules | length) + "{} / {}".format(dojo.solves(user=user, ignore_visibility=True, ignore_admins=False).count() if user else 0, dojo.challenges | length),
+ icon="/themes/dojo_theme/static/img/dojo/{}.svg".format(dojo.award.belt) if (dojo.award.belt and dojo.official) else None,
+ emoji=dojo.award.emoji ) }}
+ {% endfor %}
+
+
When you are ready, create your dojo from your repository using the form below!