Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions dojo_plugin/utils/dojo.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
Optional("dojo"): UNIQUE_ID_REGEX,
Optional("module"): ID_REGEX,
"challenge": ID_REGEX,
Optional("overwrite", default=False): bool,
},
Optional("survey"): {
Optional("probability"): float,
Expand Down Expand Up @@ -384,16 +385,22 @@ def assert_import_one(query, error_message):
def challenge(module_id, challenge_id, transfer=None):
if (module_id, challenge_id) in existing_challenges:
return existing_challenges[(module_id, challenge_id)]
if chal := Challenges.query.filter_by(category=dojo.hex_dojo_id, name=f"{module_id}:{challenge_id}").first():
return chal
destination_challenge = Challenges.query.filter_by(category=dojo.hex_dojo_id, name=f"{module_id}:{challenge_id}").first()
if destination_challenge and not (transfer and transfer["overwrite"]):
return destination_challenge
Comment on lines +389 to +390

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor overwrite when the destination is still present

When an update renames old into new while new is still part of the current dojo, the earlier existing_challenges guard returns the existing new challenge before this overwrite check is reached, so transfer: {challenge: "old", overwrite: true} silently keeps the destination challenge and drops the source challenge_id/solves. This only works after a separate update first removes the destination, as in the new regression; direct overwrites of active destinations should bypass existing_challenges too.

Useful? React with 👍 / 👎.

if transfer:
old_dojo_id = transfer.get("dojo", dojo.reference_id)
old_module_id = transfer.get("module", module_id)
old_challenge_id = transfer["challenge"]
old_dojo = Dojos.from_id(old_dojo_id).first()
assert old_dojo and (old_dojo.dojo_id == dojo.dojo_id or dojo.official or (is_admin() and not Dojos.from_id(dojo.id).first()))
old_challenge = Challenges.query.filter_by(category=old_dojo.hex_dojo_id, name=f"{old_module_id}:{old_challenge_id}").first()
assert old_challenge, f"unable to find source dojo/module/challenge in database for {old_dojo_id}:{old_module_id}:{old_challenge_id}"
old_dojo_challenge = DojoChallenges.from_id(old_dojo.reference_id, old_module_id, old_challenge_id).first()
assert (
old_dojo_challenge
and old_dojo_challenge.challenge.category == old_dojo.hex_dojo_id
and old_dojo_challenge.challenge.name == f"{old_module_id}:{old_challenge_id}"
), f"unable to find source dojo/module/challenge in database for {old_dojo_id}:{old_module_id}:{old_challenge_id}"
old_challenge = old_dojo_challenge.challenge
old_challenge.category = dojo.hex_dojo_id
old_challenge.name = f"{module_id}:{challenge_id}"
return old_challenge
Expand Down
28 changes: 27 additions & 1 deletion test/test_dojos.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def test_community_dojo_internal_transfer(admin_session, guest_dojo_admin):
username, session = guest_dojo_admin
suffix = "".join(random.choices(string.ascii_lowercase, k=8))
dojo_id = f"internal-transfer-{suffix}"
spec = {"id": dojo_id, "image": "pwncollege/challenge-simple", "modules": [{"id": "m", "challenges": [{"id": "old"}]}]}
spec = {"id": dojo_id, "image": "pwncollege/challenge-simple", "modules": [{"id": "m", "challenges": [{"id": "old"}, {"id": "new"}]}]}
dojo = create_dojo_yml(yaml.safe_dump(spec), session=admin_session)
assert session.get(f"{DOJO_URL}/dojo/{dojo}/join/").status_code == 200
response = admin_session.post(
Expand All @@ -286,13 +286,39 @@ def test_community_dojo_internal_transfer(admin_session, guest_dojo_admin):
)
assert response.status_code == 200
challenge_id = get_dojo_challenge_id(dojo_id, "m", "old")
stale_challenge_id = get_dojo_challenge_id(dojo_id, "m", "new")

spec["modules"] = [{
"id": "m",
"resources": [{"type": "challenge", "id": "old", "name": "Old"}],
}]
response = session.post(f"{DOJO_URL}/pwncollege_api/v1/dojos/{dojo}/update", json=spec)
assert response.status_code == 200
assert get_dojo_challenge_id(dojo_id, "m", "old") == challenge_id
assert int(db_sql(f"SELECT count(*) FROM challenges WHERE id = {stale_challenge_id}")) == 1

spec["modules"] = [{
"id": "m",
"resources": [{"type": "challenge", "id": "new", "name": "New", "transfer": {"challenge": "old"}}],
}]
response = session.post(f"{DOJO_URL}/pwncollege_api/v1/dojos/{dojo}/update", json=spec)
assert response.status_code == 200
assert get_dojo_challenge_id(dojo_id, "m", "new") == stale_challenge_id

spec["modules"] = [{
"id": "m",
"resources": [{"type": "challenge", "id": "old", "name": "Old"}],
}]
response = session.post(f"{DOJO_URL}/pwncollege_api/v1/dojos/{dojo}/update", json=spec)
assert response.status_code == 200
assert get_dojo_challenge_id(dojo_id, "m", "old") == challenge_id

spec["modules"] = [{
"id": "m",
"resources": [{"type": "challenge", "id": "new", "name": "New", "transfer": {"challenge": "old", "overwrite": True}}],
}]
response = session.post(f"{DOJO_URL}/pwncollege_api/v1/dojos/{dojo}/update", json=spec)
assert response.status_code == 200
assert get_dojo_challenge_id(dojo_id, "m", "new") == challenge_id
assert int(db_sql(f"SELECT count(*) FROM flags WHERE challenge_id = {challenge_id}")) == 1

Expand Down
Loading