From ec8f7f8724860a99859637affdd12daccb6bd354 Mon Sep 17 00:00:00 2001 From: Connor Nelson Date: Thu, 17 Jul 2025 12:54:03 -0700 Subject: [PATCH 1/2] Site: Improve challenge transfer edgecases --- dojo_plugin/utils/dojo.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/dojo_plugin/utils/dojo.py b/dojo_plugin/utils/dojo.py index a93151904..90ff8108e 100644 --- a/dojo_plugin/utils/dojo.py +++ b/dojo_plugin/utils/dojo.py @@ -16,7 +16,7 @@ from flask import abort, g from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.exc import NoResultFound -from CTFd.models import db, Challenges, Flags +from CTFd.models import db, Challenges, Flags, Solves from CTFd.utils.user import get_current_user, is_admin from ..models import DojoAdmins, Dojos, DojoModules, DojoChallenges, DojoResources, DojoChallengeVisibilities, DojoResourceVisibilities, DojoModuleVisibilities @@ -341,19 +341,23 @@ def assert_import_one(query, error_message): existing_challenges = {(challenge.module.id, challenge.id): challenge.challenge for challenge in dojo.challenges} 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 + existing_challenge = (existing_challenges.get((module_id, challenge_id)) or + Challenges.query.filter_by(category=dojo.hex_dojo_id, name=f"{module_id}:{challenge_id}").first()) if transfer: - assert dojo.official or (is_admin() and not Dojos.from_id(dojo.id).first()) - old_dojo_id, old_module_id, old_challenge_id = transfer["dojo"], transfer["module"], transfer["challenge"] - old_dojo = Dojos.from_id(old_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_dojo and old_challenge, f"unable to find source dojo/module/challenge in database for {old_dojo_id}:{old_module_id}:{old_challenge_id}" - old_challenge.category = dojo.hex_dojo_id - old_challenge.name = f"{module_id}:{challenge_id}" - return old_challenge + if not (dojo.official or (is_admin() and not Dojos.from_id(dojo.id).first())): + raise RuntimeError("Permission denied: only official dojos and admins can transfer challenges") + if existing_challenge and Solves.query.filter(challenge=existing_challenge).count() > 0: + raise RuntimeError("Cannot transfer when existing challenge already has solves") + transfer_challenge = (Challenges.query.filter_by(category=transfer_dojo.hex_dojo_id, name=f"{transfer['module']}:{transfer['challenge']}").first() + if (transfer_dojo := Dojos.from_id(transfer["dojo"]).first()) else None) + if transfer_challenge: + transfer_challenge.category = dojo.hex_dojo_id + transfer_challenge.name = f"{module_id}:{challenge_id}" + return transfer_challenge + if existing_challenge: + return existing_challenge + if transfer: + raise RuntimeError("Failed to find transfer challenge, or existing challenge.") return Challenges(type="dojo", category=dojo.hex_dojo_id, name=f"{module_id}:{challenge_id}", flags=[Flags(type="dojo")]) def visibility(cls, *args): From 662d2378f719f1bb0f9356696c62e29fca6a4ecb Mon Sep 17 00:00:00 2001 From: Connor Nelson Date: Thu, 17 Jul 2025 13:22:42 -0700 Subject: [PATCH 2/2] Update to just refuse --- dojo_plugin/utils/dojo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dojo_plugin/utils/dojo.py b/dojo_plugin/utils/dojo.py index 90ff8108e..02396491d 100644 --- a/dojo_plugin/utils/dojo.py +++ b/dojo_plugin/utils/dojo.py @@ -346,8 +346,8 @@ def challenge(module_id, challenge_id, transfer=None): if transfer: if not (dojo.official or (is_admin() and not Dojos.from_id(dojo.id).first())): raise RuntimeError("Permission denied: only official dojos and admins can transfer challenges") - if existing_challenge and Solves.query.filter(challenge=existing_challenge).count() > 0: - raise RuntimeError("Cannot transfer when existing challenge already has solves") + if existing_challenge: + raise RuntimeError("Cannot transfer when existing challenge already exists") transfer_challenge = (Challenges.query.filter_by(category=transfer_dojo.hex_dojo_id, name=f"{transfer['module']}:{transfer['challenge']}").first() if (transfer_dojo := Dojos.from_id(transfer["dojo"]).first()) else None) if transfer_challenge: