def gen_hint( db, challenge_id, content="This is a hint", cost=0, type="standard", **kwargs ): hint = Hints( challenge_id=challenge_id, content=content, cost=cost, type=type, **kwargs ) db.session.add(hint) db.session.commit() return hint
def admin_hints(hintid): if hintid: hint = Hints.query.filter_by(id=hintid).first_or_404() if request.method == 'POST': hint.hint = request.form.get('hint') hint.chal = int(request.form.get('chal')) hint.cost = int(request.form.get('cost') or 0) db.session.commit() elif request.method == 'DELETE': db.session.delete(hint) db.session.commit() db.session.close() return ('', 204) json_data = { 'hint': hint.hint, 'type': hint.type, 'chal': hint.chal, 'cost': hint.cost, 'id': hint.id } db.session.close() return jsonify(json_data) else: if request.method == 'GET': hints = Hints.query.all() json_data = [] for hint in hints: json_data.append({ 'hint': hint.hint, 'type': hint.type, 'chal': hint.chal, 'cost': hint.cost, 'id': hint.id }) return jsonify({'results': json_data}) elif request.method == 'POST': hint = request.form.get('hint') chalid = int(request.form.get('chal')) cost = int(request.form.get('cost') or 0) hint_type = request.form.get('type', 0) hint = Hints(chal=chalid, hint=hint, cost=cost) db.session.add(hint) db.session.commit() json_data = { 'hint': hint.hint, 'type': hint.type, 'chal': hint.chal, 'cost': hint.cost, 'id': hint.id } db.session.close() return jsonify(json_data)
def update_hints(chal, chal_dbobj): from CTFd.models import db, Hints old_hints = Hints.query.filter_by(challenge_id=chal_dbobj.id).all() for hint in old_hints: db.session.delete(hint) for hint in chal.get('hints', []): hint_db = Hints(challenge_id=chal_dbobj.id, content=hint['hint'], type=hint['type'], cost=int(hint['cost'])) db.session.add(hint_db) return []
def load_challenges_csv(dict_reader): schema = ChallengeSchema() errors = [] for i, line in enumerate(dict_reader): # Throw away fields that we can't trust if provided _ = line.pop("id", None) _ = line.pop("requirements", None) flags = line.pop("flags", None) tags = line.pop("tags", None) hints = line.pop("hints", None) challenge_type = line.pop("type", "standard") # Load in custom type_data type_data = json.loads(line.pop("type_data", "{}") or "{}") line.update(type_data) response = schema.load(line) if response.errors: errors.append((i + 1, response.errors)) continue ChallengeClass = get_chal_class(challenge_type) challenge = ChallengeClass.challenge_model(**line) db.session.add(challenge) db.session.commit() if flags: flags = [flag.strip() for flag in flags.split(",")] for flag in flags: f = Flags(type="static", challenge_id=challenge.id, content=flag,) db.session.add(f) db.session.commit() if tags: tags = [tag.strip() for tag in tags.split(",")] for tag in tags: t = Tags(challenge_id=challenge.id, value=tag,) db.session.add(t) db.session.commit() if hints: hints = [hint.strip() for hint in hints.split(",")] for hint in hints: h = Hints(challenge_id=challenge.id, content=hint,) db.session.add(h) db.session.commit() if errors: return errors return True
def load_challenges_csv(dict_reader): for line in dict_reader: flags = line.pop("flags", None) tags = line.pop("tags", None) hints = line.pop("hints", None) challenge_type = line.pop("type", "standard") # Load in custom type_data type_data = json.loads(line.pop("type_data", "{}") or "{}") line.update(type_data) ChallengeClass = get_chal_class(challenge_type) challenge = ChallengeClass.challenge_model(**line) db.session.add(challenge) db.session.commit() if flags: flags = [flag.strip() for flag in flags.split(",")] for flag in flags: f = Flags( type="static", challenge_id=challenge.id, content=flag, ) db.session.add(f) db.session.commit() if tags: tags = [tag.strip() for tag in tags.split(",")] for tag in tags: t = Tags( challenge_id=challenge.id, value=tag, ) db.session.add(t) db.session.commit() if hints: hints = [hint.strip() for hint in hints.split(",")] for hint in hints: h = Hints( challenge_id=challenge.id, content=hint, ) db.session.add(h) db.session.commit() return True
def get(self, challenge_id): if is_admin(): chal = Challenges.query.filter( Challenges.id == challenge_id).first_or_404() else: chal = Challenges.query.filter( Challenges.id == challenge_id, and_(Challenges.state != "hidden", Challenges.state != "locked"), ).first_or_404() try: chal_class = get_chal_class(chal.type) except KeyError: abort( 500, f"The underlying challenge type ({chal.type}) is not installed. This challenge can not be loaded.", ) if chal.requirements: requirements = chal.requirements.get("prerequisites", []) anonymize = chal.requirements.get("anonymize") if challenges_visible(): user = get_current_user() if user: solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) else: # We need to handle the case where a user is viewing challenges anonymously solve_ids = [] solve_ids = set([value for value, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs or is_admin(): pass else: if anonymize: return { "success": True, "data": { "id": chal.id, "type": "hidden", "name": "???", "value": 0, "category": "???", "tags": [], "template": "", "script": "", }, } abort(403) else: abort(403) tags = [ tag["value"] for tag in TagSchema("user", many=True).dump(chal.tags).data ] unlocked_hints = set() hints = [] if authed(): user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and team is None: abort(403) unlocked_hints = set([ u.target for u in HintUnlocks.query.filter_by( type="hints", account_id=user.account_id) ]) files = [] for f in chal.files: token = { "user_id": user.id, "team_id": team.id if team else None, "file_id": f.id, } files.append( url_for("views.files", path=f.location, token=serialize(token))) else: files = [ url_for("views.files", path=f.location) for f in chal.files ] for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({ "id": hint.id, "cost": hint.cost, "content": hint.content }) else: hints.append({"id": hint.id, "cost": hint.cost}) response = chal_class.read(challenge=chal) Model = get_model() if scores_visible() is True and accounts_visible() is True: solves = Solves.query.join(Model, Solves.account_id == Model.id).filter( Solves.challenge_id == chal.id, Model.banned == False, Model.hidden == False, ) # Only show solves that happened before freeze time if configured freeze = get_config("freeze") if not is_admin() and freeze: solves = solves.filter(Solves.date < unix_time_to_utc(freeze)) solves = solves.count() response["solves"] = solves else: response["solves"] = None solves = None if authed(): # Get current attempts for the user attempts = Submissions.query.filter_by( account_id=user.account_id, challenge_id=challenge_id).count() else: attempts = 0 response["attempts"] = attempts response["files"] = files response["tags"] = tags response["hints"] = hints response["view"] = render_template( chal_class.templates["view"].lstrip("/"), solves=solves, files=files, tags=tags, hints=[Hints(**h) for h in hints], max_attempts=chal.max_attempts, attempts=attempts, challenge=chal, ) db.session.close() return {"success": True, "data": response}
def get(self, challenge_id): if is_admin(): chal = Challenges.query.filter( Challenges.id == challenge_id).first_or_404() else: chal = Challenges.query.filter( Challenges.id == challenge_id, and_(Challenges.state != "hidden", Challenges.state != "locked"), ).first_or_404() try: chal_class = get_chal_class(chal.type) except KeyError: abort( 500, f"The underlying challenge type ({chal.type}) is not installed. This challenge can not be loaded.", ) if chal.requirements: requirements = chal.requirements.get("prerequisites", []) anonymize = chal.requirements.get("anonymize") # Gather all challenge IDs so that we can determine invalid challenge prereqs all_challenge_ids = { c.id for c in Challenges.query.with_entities(Challenges.id).all() } if challenges_visible(): user = get_current_user() if user: solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) else: # We need to handle the case where a user is viewing challenges anonymously solve_ids = [] solve_ids = {value for value, in solve_ids} prereqs = set(requirements).intersection(all_challenge_ids) if solve_ids >= prereqs or is_admin(): pass else: if anonymize: return { "success": True, "data": { "id": chal.id, "type": "hidden", "name": "???", "value": 0, "solves": None, "solved_by_me": False, "category": "???", "tags": [], "template": "", "script": "", }, } abort(403) else: abort(403) tags = [ tag["value"] for tag in TagSchema("user", many=True).dump(chal.tags).data ] unlocked_hints = set() hints = [] if authed(): user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and team is None: abort(403) unlocked_hints = { u.target for u in HintUnlocks.query.filter_by( type="hints", account_id=user.account_id) } files = [] for f in chal.files: token = { "user_id": user.id, "team_id": team.id if team else None, "file_id": f.id, } files.append( url_for("views.files", path=f.location, token=serialize(token))) else: files = [ url_for("views.files", path=f.location) for f in chal.files ] for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({ "id": hint.id, "cost": hint.cost, "content": hint.content }) else: hints.append({"id": hint.id, "cost": hint.cost}) response = chal_class.read(challenge=chal) solves_q, user_solves = _build_solves_query( extra_filters=(Solves.challenge_id == chal.id, )) # If there are no solves for this challenge ID then we have 0 rows maybe_row = solves_q.first() if maybe_row: challenge_id, solve_count = maybe_row solved_by_user = challenge_id in user_solves else: solve_count, solved_by_user = 0, False # Hide solve counts if we are hiding solves/accounts if scores_visible() is False or accounts_visible() is False: solve_count = None if authed(): # Get current attempts for the user attempts = Submissions.query.filter_by( account_id=user.account_id, challenge_id=challenge_id).count() else: attempts = 0 response["solves"] = solve_count response["solved_by_me"] = solved_by_user response["attempts"] = attempts response["files"] = files response["tags"] = tags response["hints"] = hints response["view"] = render_template( chal_class.templates["view"].lstrip("/"), solves=solve_count, solved_by_me=solved_by_user, files=files, tags=tags, hints=[Hints(**h) for h in hints], max_attempts=chal.max_attempts, attempts=attempts, challenge=chal, ) db.session.close() return {"success": True, "data": response}
def import_challenges(in_file, dst_attachments, exit_on_error=True, move=False): from CTFd.models import db, Challenges, Flags, Tags, Hints, ChallengeFiles chals = [] with open(in_file, 'r') as in_stream: chals = yaml.safe_load_all(in_stream) for chal in chals: skip = False for req_field in REQ_FIELDS: if req_field not in chal: if exit_on_error: raise MissingFieldError(req_field) else: print("Skipping challenge: Missing field '{}'".format( req_field)) skip = True break if skip: continue for flag in chal['flags']: if 'flag' not in flag: if exit_on_error: raise MissingFieldError('flag') else: print("Skipping flag: Missing field 'flag'") continue flag['flag'] = flag['flag'].strip() if 'type' not in flag: flag['type'] = "static" if 'data' not in flag: flag['data'] = "" if 'files' in chal: norm_files = [] for file in chal['files']: # make sure we have only relative paths in the yaml file file = os.path.normpath("/" + file).lstrip('/') # skip files that do not exists if not os.path.exists( os.path.join(os.path.dirname(in_file), file)): print( "Skipping file '{}' in challenge '{}': File not found" .format(file, chal['name'].strip())) continue else: norm_files.append(file) chal['files'] = norm_files for hint in chal['hints']: if 'type' not in hint: hint['type'] = "standard" # Check what type the challenge is and create a DB object of the appropriate type. if chal['type'] == 'dynamic': # Lazy load the DynamicChallenge plugin on encountering a challenge of that type. try: from CTFd.plugins.dynamic_challenges import DynamicChallenge except ImportError as err: print("Failed to import plugin for challenge type {}: {}". format(chal['type'], err)) continue initial = int(chal['value']) if 'initial' in chal: initial = int(chal['initial']) minimum = 0 if 'minimum' in chal: minimum = int(chal['minimum']) decay = 0 if 'decay' in chal: decay = int(chal['decay']) chal_dbobj = DynamicChallenge( name=chal['name'].strip(), description=chal['description'].strip(), value=int(chal['value']), category=chal['category'].strip(), initial=initial, decay=decay, minimum=minimum) elif chal['type'] == 'naumachia': # Lazy load the Naumachia plugin on encountering a challenge of that type. try: # Here we use a fixed name, which is the repository name, even though it does # not conform to a proper Python package name. Users may install the package # using any file name they want, but this version of thsi plugin does not # support it. naumachia_plugin = importlib.import_module( '.ctfd-naumachia-plugin', package="CTFd.plugins") except ImportError as err: print("Failed to import plugin for challenge type {}: {}". format(chal['type'], err)) continue naumachia_name = chal.get('naumachia_name', "").strip(), chal_dbobj = naumachia_plugin.NaumachiaChallengeModel( name=chal['name'].strip(), naumachia_name=chal['naumachia_name'], description=chal['description'].strip(), value=int(chal['value']), category=chal['category'].strip(), ) else: # We ignore traling and leading whitespace when importing challenges chal_dbobj = Challenges( name=chal['name'].strip(), description=chal['description'].strip(), value=int(chal['value']), category=chal['category'].strip()) chal_dbobj.state = 'visible' if 'hidden' in chal and chal['hidden']: if bool(chal['hidden']): chal_dbobj.state = 'hidden' chal_dbobj.max_attempts = 0 if 'max_attempts' in chal and chal['max_attempts']: chal_dbobj.max_attempts = chal['max_attempts'] chal_dbobj.type = 'standard' if 'type' in chal and chal['type']: chal_dbobj.type = chal['type'] matching_chals = Challenges.query.filter_by( name=chal_dbobj.name, description=chal_dbobj.description, value=chal_dbobj.value, category=chal_dbobj.category, state=chal_dbobj.state, type=chal_dbobj.type).all() for match in matching_chals: if 'tags' in chal: tags_db = [ tag.tag for tag in Tags.query.add_columns('tag').filter_by( challenge_id=match.id).all() ] if all([tag not in tags_db for tag in chal['tags']]): continue if 'files' in chal: files_db = [ f.location for f in ChallengeFiles.query.add_columns( 'location').filter_by(challenge_id=match.id).all() ] if len(files_db) != len(chal['files']): continue hashes = [] for file_db in files_db: with open(os.path.join(dst_attachments, file_db), 'rb') as f: hash = hashlib.md5(f.read()).digest() hashes.append(hash) mismatch = False for file in chal['files']: filepath = os.path.join(os.path.dirname(in_file), file) with open(filepath, 'rb') as f: hash = hashlib.md5(f.read()).digest() if hash in hashes: hashes.remove(hash) else: mismatch = True break if mismatch: continue flags_db = Flags.query.filter_by(challenge_id=match.id).all() for flag in chal['flags']: for flag_db in flags_db: if flag['flag'] != flag_db.content: continue if flag['type'] != flag_db.type: continue skip = True break if skip: print("Skipping '{}': Duplicate challenge found in DB".format( chal['name'].encode('utf8'))) continue print("Adding {}".format(chal['name'].encode('utf8'))) db.session.add(chal_dbobj) db.session.commit() if 'tags' in chal: for tag in chal['tags']: tag_dbobj = Tags(chal_dbobj.id, tag) db.session.add(tag_dbobj) for flag in chal['flags']: flag_db = Flags(challenge_id=chal_dbobj.id, content=flag['flag'], type=flag['type'], data=flag['data']) db.session.add(flag_db) for hint in chal['hints']: hint_db = Hints(challenge_id=chal_dbobj.id, content=hint['hint'], type=hint['type'], cost=int(hint['cost'])) db.session.add(hint_db) if 'files' in chal: for file in chal['files']: filename = os.path.basename(file) dst_filename = secure_filename(filename) dst_dir = None while not dst_dir or os.path.exists(dst_dir): md5hash = hashlib.md5(os.urandom(64)).hexdigest() dst_dir = os.path.join(dst_attachments, md5hash) os.makedirs(dst_dir) dstpath = os.path.join(dst_dir, dst_filename) srcpath = os.path.join(os.path.dirname(in_file), file) if move: shutil.move(srcpath, dstpath) else: shutil.copy(srcpath, dstpath) file_dbobj = ChallengeFiles(challenge_id=chal_dbobj.id, location=os.path.relpath( dstpath, start=dst_attachments)) db.session.add(file_dbobj) db.session.commit() db.session.close()
def import_challenges(in_file, dst_attachments, exit_on_error=True, move=False): from CTFd.models import db, Challenges, Keys, Tags, Files, Hints, Unlocks with open(in_file, 'r') as in_stream: chals = yaml.safe_load_all(in_stream) for chal in chals: # ensure all required fields are present before adding or updating a challenge try: validate_yaml(chal) except MissingFieldError as err: if exit_on_error: raise else: print "Skipping challenge: " + str(err) continue # if the challenge already exists, update it chal_db = Challenges.query.filter_by(name=chal['name']).first() if chal_db is not None: print "Updating {}".format(chal['name'].encode('utf8')) chal_db.description = chal['description'] chal_db.value = chal['value'] chal_db.category = chal['category'] else: print "Adding {}".format(chal['name'].encode('utf8')) chal_db = Challenges( chal['name'], chal['description'], chal['value'], chal['category']) chal_db.type = chal['type'] chal_db.hidden = chal['hidden'] db.session.add(chal_db) db.session.commit() # delete all tags and re-add them Tags.query.filter_by(chal=chal_db.id).delete() for tag in chal['tags']: tag_dbobj = Tags(chal_db.id, tag) db.session.add(tag_dbobj) # delete all flags and re-add them Keys.query.filter_by(chal=chal_db.id).delete() for flag in chal['flags']: flag_db = Keys(chal_db.id, flag['flag'], flag['type']) db.session.add(flag_db) # delete or update existing hints hints = {h['id']: h for h in chal['hints']} hints_db = Hints.query.filter_by(chal=chal_db.id).all() for hint_db in hints_db: if hint_db.type in hints: # the hint is being updated hint_db.hint = hints[hint_db.type]['hint'] hint_db.cost = hints[hint_db.type]['cost'] del hints[hint_db.type] else: # the hint is being deleted - delete the hint and any related unlocks print " Removing hint {:d}".format(hint_db.type) Unlocks.query.filter_by(model='hints', itemid=hint_db.id).delete() Hints.query.filter_by(id=hint_db.id).delete() # add new hints for hint in hints.values(): print " Adding hint {:d}".format(hint['id']) hint_db = Hints(chal_db.id, hint['hint'], cost=hint['cost'], type=hint['id']) db.session.add(hint_db) # hash and compare existing files with the new uploaded files hashes_db = {} files_db = Files.query.filter_by(chal=chal_db.id).all() for file_db in files_db: with open(os.path.join(dst_attachments, file_db.location), 'rb') as f: h = hashlib.md5(f.read()).digest() hashes_db[h] = file_db to_upload = [] for file in chal['files']: path = os.path.join(os.path.dirname(in_file), file) with open(path, "rb") as f: h = hashlib.md5(f.read()).digest() if h in hashes_db and os.path.basename(file) == os.path.basename(hashes_db[h].location): # the file is up to date del hashes_db[h] else: # the file has changed name or content to_upload.append(path) # remove out of date files and add new uploads for file_db in hashes_db.values(): print " Removing file {}".format(file_db.location) utils.delete_file(file_db.id) for path in to_upload: basename = os.path.basename(path) print " Adding file {}".format(basename) with open(path, "rb") as f: f = FileStorage(stream=f, filename=basename) utils.upload_file(file=f, chalid=chal_db.id) if move: os.unlink(path) db.session.commit() db.session.commit() db.session.close()
def admin_hints(hintid): if hintid: hint = Hints.query.filter_by(id=hintid).first_or_404() if request.method == 'POST': hint.hint = request.form.get('hint') hint.chal = int(request.form.get('chal')) hint.cost = int(request.form.get('cost') or 0) db.session.commit() elif request.method == 'DELETE': db.session.delete(hint) db.session.commit() db.session.close() return ('', 204) json_data = { 'hint': hint.hint, 'type': hint.type, 'chal': hint.chal, 'cost': hint.cost, 'id': hint.id } db.session.close() return jsonify(json_data) else: if request.method == 'GET': hints = Hints.query.all() json_data = [] for hint in hints: json_data.append({ 'hint': hint.hint, 'type': hint.type, 'chal': hint.chal, 'cost': hint.cost, 'id': hint.id }) return jsonify({'results': json_data}) elif request.method == 'POST': hint = request.form.get('hint') chalid = int(request.form.get('chal')) cost = int(request.form.get('cost') or 0) hint_type = request.form.get('type', 0) hint = Hints(chal=chalid, hint=hint, cost=cost) filter = Challenges.query.filter_by(id=chalid).all() chalname = filter[0].name chal_category = filter[0].category chal_value = filter[0].value t = datetime.datetime.fromtimestamp( time.time()).strftime('%c').split(' ')[3] broadcast(t + ": Thử thách " + chal_category + " " + str(chal_value) + ": " + chalname + " đã thêm gợi ý") db.session.add(hint) db.session.commit() json_data = { 'hint': hint.hint, 'type': hint.type, 'chal': hint.chal, 'cost': hint.cost, 'id': hint.id } db.session.close() return jsonify(json_data)
def import_challenges(in_file, dst_attachments, move=False): from CTFd.models import db, Challenges, Flags, Tags, ChallengeFiles, Hints from CTFd.utils import uploads from CTFd.plugins.dynamic_challenges import DynamicChallenge, DynamicValueChallenge with open(in_file, "r") as in_stream: data = list(yaml.safe_load_all(in_stream)) if len(data) == 0 or data[0] is None or "challs" not in data[ 0] or data[0]["challs"] is None: raise ValueError("Invalid YAML format. Missing field 'challs'.") for chal in data[0]["challs"]: for req_field in REQ_FIELDS: if req_field not in chal: raise ValueError( "Invalid YAML format. Missing field '{0}'.".format( req_field)) if chal.get("type", "standard") == "dynamic": for req_field in ["minimum", "decay"]: if req_field not in chal: raise ValueError( "Invalid YAML format. Missing field '{0}'.".format( req_field)) if chal["flags"] is None: raise ValueError("Invalid YAML format. Missing field 'flag'.") for flag in chal["flags"]: if "flag" not in flag: raise ValueError( "Invalid YAML format. Missing field 'flag'.") flag["flag"] = flag["flag"].strip() if "type" not in flag: flag["type"] = "static" matching_chal = Challenges.query.filter_by( name=chal["name"].strip()).first() if matching_chal: print(("Updating {}: Duplicate challenge " "found in DB (id: {})").format( chal["name"].encode("utf8"), matching_chal.id)) Tags.query.filter_by(challenge_id=matching_chal.id).delete() ChallengeFiles.query.filter_by( challenge_id=matching_chal.id).delete() Flags.query.filter_by(challenge_id=matching_chal.id).delete() Hints.query.filter_by(challenge_id=matching_chal.id).delete() matching_chal.name = chal["name"].strip() matching_chal.description = chal["description"].strip() matching_chal.category = chal["category"].strip() if chal.get("type", "standard") == "standard": matching_chal.value = chal["value"] if chal.get("type", "standard") == "dynamic": matching_chal.minimum = chal["minimum"] matching_chal.decay = chal["decay"] matching_chal.initial = chal["value"] DynamicValueChallenge.calculate_value(matching_chal) db.session.commit() chal_dbobj = matching_chal else: print("Adding {}".format(chal["name"].encode("utf8"))) chal_type = chal.get("type", "standard") if chal_type == "standard": # We ignore traling and leading whitespace when # importing challenges chal_dbobj = Challenges( name=chal["name"].strip(), description=chal["description"].strip(), value=chal["value"], category=chal["category"].strip(), ) elif chal_type == "dynamic": # We ignore traling and leading whitespace when # importing challenges chal_dbobj = DynamicChallenge( name=chal["name"].strip(), description=chal["description"].strip(), category=chal["category"].strip(), value=int(chal["value"]), minimum=int(chal["minimum"]), decay=int(chal["decay"]), ) else: raise ValueError("Unknown type of challenge") db.session.add(chal_dbobj) db.session.commit() for tag in chal.get("tags", []): tag_dbobj = Tags(challenge_id=chal_dbobj.id, value=tag) db.session.add(tag_dbobj) for flag in chal["flags"]: flag_db = Flags(challenge_id=chal_dbobj.id, content=flag["flag"], type=flag["type"]) db.session.add(flag_db) for hint in chal.get("hints", []): hint_dbobj = Hints(challenge_id=chal_dbobj.id, content=hint["content"], cost=hint["cost"]) db.session.add(hint_dbobj) chal_dbobj.state = "hidden" if ( "hidden" in chal and chal["hidden"] == True) else "visible" chal_dbobj.max_attempts = chal[ "max_attempts"] if "max_attempts" in chal else 0 if "files" in chal: from io import FileIO for filename in chal["files"]: try: # upload_file takes a werkzeug.FileStorage object, but we # can get close enough with a file object with a # filename property added filepath = os.path.join(os.path.dirname(in_file), filename) with FileIO(filepath, mode="rb") as f: f.filename = os.path.basename(f.name) uploads.upload_file(file=f, challenge=chal_dbobj.id, type="challenge") except FileNotFoundError: raise ValueError( "Unable to import challenges. Missing file: " + filename) db.session.commit() # update challenge prerequisites after all the challenges were created with open(in_file, "r") as in_stream: data = list(yaml.safe_load_all(in_stream)) for chal in data[0]["challs"]: chal_dbobj = Challenges.query.filter_by( name=chal["name"].strip()).first() prerequisites = set() for prerequisite in chal.get("prerequisites", []): prerequisites.update([ c.id for c in Challenges.query.filter_by( name=prerequisite).all() ]) chal_dbobj.requirements = {"prerequisites": list(prerequisites)} db.session.commit() db.session.close()