From c8efdd3df4f0ce3ed41bb943dd30fa331b24ba1e Mon Sep 17 00:00:00 2001 From: James Wang Date: Thu, 7 Apr 2016 16:36:30 -0400 Subject: [PATCH 01/21] Add basic implementation for creating new problems --- deploy | 0 server/api/models.py | 6 ++++-- server/api/problem.py | 13 +++++++++---- server/api/utils.py | 2 +- web/js/admin.js | 24 +++++++++++++++++++++++- web/pages/admin/problems.html | 26 ++++++++++++++++++++++---- 6 files changed, 59 insertions(+), 12 deletions(-) mode change 100644 => 100755 deploy diff --git a/deploy b/deploy old mode 100644 new mode 100755 diff --git a/server/api/models.py b/server/api/models.py index 3270b09..0a5b34d 100644 --- a/server/api/models.py +++ b/server/api/models.py @@ -132,9 +132,10 @@ class Teams(db.Model): return False class Problems(db.Model): - pid = db.Column(db.String(128), primary_key=True, autoincrement=False) + pid = db.Column(db.String(32), primary_key=True, autoincrement=False) title = db.Column(db.String(128)) category = db.Column(db.String(128)) + flag = db.Column(db.String(128)) description = db.Column(db.Text) value = db.Column(db.Integer) hint = db.Column(db.Text) @@ -143,11 +144,12 @@ class Problems(db.Model): threshold = db.Column(db.Integer) weightmap = db.Column(db.PickleType) - def __init__(self, pid, title, category, description, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}): + def __init__(self, pid, title, category, description, flag, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}): self.pid = pid self.title = title self.category = category self.description = description + self.flag = flag self.value = value self.hint = hint self.autogen = autogen diff --git a/server/api/problem.py b/server/api/problem.py index 8e410a4..338fb7c 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -1,6 +1,7 @@ import hashlib import logger import os +import utils from flask import Blueprint, jsonify, session, request from flask import current_app as app @@ -18,14 +19,18 @@ def problem_add(): name = request.form["name"] category = request.form["category"] description = request.form["description"] - hint = request.form["problem-hint"] + hint = request.form["hint"] flag = request.form["flag"] value = request.form["value"] + pid = utils.generate_string() + while Problems.query.filter_by(pid=pid).first(): + pid = utils.generate_string() - name_exists = Problems.query.filter_by(name=name).first() + name_exists = Problems.query.filter_by(title=name).first() if name_exists: raise WebException("Problem name already taken.") - problem = Problems(name, category, description, hint, flag, value) + + problem = Problems(pid, name, category, description, flag, value, hint=hint) db.session.add(problem) db.session.commit() @@ -160,4 +165,4 @@ def get_problem(title=None, pid=None): match.update({ "pid": pid }) with app.app_context(): result = Problems.query.filter_by(**match) - return result \ No newline at end of file + return result diff --git a/server/api/utils.py b/server/api/utils.py index c2adef0..3c64f0f 100644 --- a/server/api/utils.py +++ b/server/api/utils.py @@ -99,4 +99,4 @@ def generate_identicon(email, filename): draw.rectangle([(4*cell + margin, (i-10)*cell + margin), (5*cell + margin, (i-9)*cell + margin)], fill=c) image.save(open("pfp/%s.png" % filename, "w"), "PNG") - return \ No newline at end of file + return diff --git a/web/js/admin.js b/web/js/admin.js index c6b7fd6..5874aa1 100644 --- a/web/js/admin.js +++ b/web/js/admin.js @@ -2,4 +2,26 @@ $(document).ready(function() { $(".panel-title > a[data-toggle=collapse]").click(function(e) { e.preventDefault(); }); -}); \ No newline at end of file +}); + +var create_problem = function() { + var input = "#new_problem_form input"; + var data = $("#new_problem_form").serializeObject(); + $(input).attr("disabled", "disabled"); + api_call("POST", "/api/problem/add", data, function(result) { + if (result["success"] == 1) { + display_message("add-status", "success", result["message"], function() { + $(input).removeAttr("disabled"); + }); + } else { + display_message("add-status", "danger", result["message"], function() { + $(input).removeAttr("disabled"); + }); + } + }, function(jqXHR, status, error) { + var result = jqXHR["responseText"]; + display_message("add-status", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() { + $(input).removeAttr("disabled"); + }); + }); +} diff --git a/web/pages/admin/problems.html b/web/pages/admin/problems.html index 03712a5..992d77c 100644 --- a/web/pages/admin/problems.html +++ b/web/pages/admin/problems.html @@ -25,6 +25,24 @@

New Problem

+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
From 6aa0a119ffc45f18a440fcbb011af8a09adce118 Mon Sep 17 00:00:00 2001 From: James Wang Date: Thu, 7 Apr 2016 20:58:56 -0400 Subject: [PATCH 03/21] Refactor name to title --- server/api/admin.py | 2 +- server/api/models.py | 6 +++--- server/api/problem.py | 26 +++++++++++++------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/server/api/admin.py b/server/api/admin.py index 6c172a7..68b65b7 100644 --- a/server/api/admin.py +++ b/server/api/admin.py @@ -16,7 +16,7 @@ def problem_data(): for problem in problems: problems_return.append({ "pid": problem.pid, - "name": problem.name, + "title": problem.title, "category": problem.category, "description": problem.description, "hint": problem.hint, diff --git a/server/api/models.py b/server/api/models.py index 98abf5e..0a5b34d 100644 --- a/server/api/models.py +++ b/server/api/models.py @@ -133,7 +133,7 @@ class Teams(db.Model): class Problems(db.Model): pid = db.Column(db.String(32), primary_key=True, autoincrement=False) - name = db.Column(db.String(128)) + title = db.Column(db.String(128)) category = db.Column(db.String(128)) flag = db.Column(db.String(128)) description = db.Column(db.Text) @@ -144,9 +144,9 @@ class Problems(db.Model): threshold = db.Column(db.Integer) weightmap = db.Column(db.PickleType) - def __init__(self, pid, name, category, description, flag, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}): + def __init__(self, pid, title, category, description, flag, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}): self.pid = pid - self.name = name + self.title = title self.category = category self.description = description self.flag = flag diff --git a/server/api/problem.py b/server/api/problem.py index d45c8fe..7f782e3 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -16,7 +16,7 @@ blueprint = Blueprint("problem", __name__) @admins_only @api_wrapper def problem_add(): - name = request.form["name"] + title = request.form["title"] category = request.form["category"] description = request.form["description"] hint = request.form["hint"] @@ -26,11 +26,11 @@ def problem_add(): while Problems.query.filter_by(pid=pid).first(): pid = utils.generate_string() - name_exists = Problems.query.filter_by(name=name).first() - if name_exists: + title_exist = Problems.query.filter_by(title=title).first() + if title_exist: raise WebException("Problem name already taken.") - problem = Problems(pid, name, category, description, flag, value, hint=hint) + problem = Problems(pid, title, category, description, flag, value, hint=hint) db.session.add(problem) db.session.commit() @@ -69,7 +69,7 @@ def problem_delete(): @api_wrapper def problem_update(): pid = request.form["pid"] - name = request.form["name"] + title = request.form["title"] category = request.form["category"] description = request.form["description"] hint = request.form["hint"] @@ -78,7 +78,7 @@ def problem_update(): problem = Problems.query.filter_by(pid=pid).first() if problem: - problem.name = name + problem.title = title problem.category = category problem.description = description problem.hint = hint @@ -111,11 +111,11 @@ def problem_submit(): db.session.add(problem) db.session.commit() - logger.log(__name__, logger.WARNING, "%s has solved %s by submitting %s" % (team.name, problem.name, flag)) + logger.log(__name__, logger.WARNING, "%s has solved %s by submitting %s" % (team.name, problem.title, flag)) return { "success": 1, "message": "Correct!" } else: - logger.log(__name__, logger.WARNING, "%s has incorrectly submitted %s to %s" % (team.name, flag, problem.name)) + logger.log(__name__, logger.WARNING, "%s has incorrectly submitted %s to %s" % (team.name, flag, problem.title)) raise WebException("Incorrect.") else: @@ -130,7 +130,7 @@ def problem_data(): for problem in problems: problem_files = [ str(_file.location) for _file in Files.query.filter_by(pid=int(problem.pid)).all() ] - jason.append({"pid": problem[1], "name": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "files": problem_files}) + jason.append({"pid": problem[1], "title": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "files": problem_files}) return jsonify(data=jason) @@ -144,7 +144,7 @@ def insert_problem(data, force=False): else: raise InternalException("Problem already exists.") - insert = Problems(data["pid"], data["name"], data["category"], data["description"], data["value"]) + insert = Problems(data["pid"], data["title"], data["category"], data["description"], data["value"]) if "hint" in data: insert.hint = data["hint"] if "autogen" in data: insert.autogen = data["autogen"] if "bonus" in data: insert.bonus = data["bonus"] @@ -155,10 +155,10 @@ def insert_problem(data, force=False): return True -def get_problem(name=None, pid=None): +def get_problem(title=None, pid=None): match = {} - if name != None: - match.update({ "name": name }) + if title != None: + match.update({ "title": title }) elif pid != None: match.update({ "pid": pid }) with app.app_context(): From 19d9fe32eb118892b9dc44f2fce36a1a7fde12be Mon Sep 17 00:00:00 2001 From: James Wang Date: Thu, 7 Apr 2016 22:48:12 -0400 Subject: [PATCH 04/21] Implement grader scripts --- .gitignore | 1 + server/api/admin.py | 2 +- server/api/models.py | 5 ++--- server/api/problem.py | 19 +++++++++++++++---- server/app.py | 4 +++- server/config.py | 3 ++- web/js/admin.js | 6 ++++++ web/js/easyctf.js | 28 ++++++++++++++++++++++++++++ web/pages/admin/problems.html | 12 +++++------- 9 files changed, 63 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 3052973..06599fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .vagrant .secret_key .bundle/config +graders/ logs/ files/ pfp/ diff --git a/server/api/admin.py b/server/api/admin.py index 68b65b7..d9e78f8 100644 --- a/server/api/admin.py +++ b/server/api/admin.py @@ -23,7 +23,7 @@ def problem_data(): "value": problem.value, "threshold": problem.threshold, "weightmap": problem.weightmap, - "flag": problem.flag + "grader_contents": open(problem.grader, "r").read() }) problems_return.sort(key=lambda prob: prob["value"]) return { "success": 1, "problems": problems_return } diff --git a/server/api/models.py b/server/api/models.py index 0a5b34d..a872856 100644 --- a/server/api/models.py +++ b/server/api/models.py @@ -135,7 +135,6 @@ class Problems(db.Model): pid = db.Column(db.String(32), primary_key=True, autoincrement=False) title = db.Column(db.String(128)) category = db.Column(db.String(128)) - flag = db.Column(db.String(128)) description = db.Column(db.Text) value = db.Column(db.Integer) hint = db.Column(db.Text) @@ -143,13 +142,13 @@ class Problems(db.Model): bonus = db.Column(db.Integer) threshold = db.Column(db.Integer) weightmap = db.Column(db.PickleType) + grader = db.Column(db.Text) - def __init__(self, pid, title, category, description, flag, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}): + def __init__(self, pid, title, category, description, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}): self.pid = pid self.title = title self.category = category self.description = description - self.flag = flag self.value = value self.hint = hint self.autogen = autogen diff --git a/server/api/problem.py b/server/api/problem.py index 7f782e3..634b45a 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -20,8 +20,8 @@ def problem_add(): category = request.form["category"] description = request.form["description"] hint = request.form["hint"] - flag = request.form["flag"] value = request.form["value"] + grader_contents = request.form["grader_contents"] pid = utils.generate_string() while Problems.query.filter_by(pid=pid).first(): pid = utils.generate_string() @@ -30,7 +30,7 @@ def problem_add(): if title_exist: raise WebException("Problem name already taken.") - problem = Problems(pid, title, category, description, flag, value, hint=hint) + problem = Problems(pid, title, category, description, value, hint=hint) db.session.add(problem) db.session.commit() @@ -47,6 +47,14 @@ def problem_add(): db_file = Files(problem.pid, "/".join(file_path.split("/")[2:])) db.session.add(db_file) + grader_folder = os.path.join(app.config["GRADER_FOLDER"], pid) + if not os.path.exists(grader_folder): + os.makedirs(grader_folder) + grader_path = os.path.join(grader_folder, "grader.py") + grader_file = open(grader_path, "w") + grader_file.write(grader_contents) + grader_file.close() + problem.grader = grader_path db.session.commit() return { "success": 1, "message": "Success!" } @@ -73,8 +81,8 @@ def problem_update(): category = request.form["category"] description = request.form["description"] hint = request.form["hint"] - flag = request.form["flag"] value = request.form["value"] + grader_contents = request.form["grader_contents"] problem = Problems.query.filter_by(pid=pid).first() if problem: @@ -82,9 +90,12 @@ def problem_update(): problem.category = category problem.description = description problem.hint = hint - problem.flag = flag problem.value = value + grader = open(problem.grader, "w") + grader.write(grader_contents) + grader.close() + db.session.add(problem) db.session.commit() diff --git a/server/app.py b/server/app.py index ec403b2..6b4a282 100644 --- a/server/app.py +++ b/server/app.py @@ -18,6 +18,8 @@ app.config.from_object(config) if not os.path.exists(app.config["UPLOAD_FOLDER"]): os.makedirs(app.config["UPLOAD_FOLDER"]) +if not os.path.exists(app.config["GRADER_FOLDER"]): + os.makedirs(app.config["GRADER_FOLDER"]) if not os.path.exists("pfp"): os.makedirs("pfp") @@ -117,4 +119,4 @@ def main(): parser.print_help() if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/server/config.py b/server/config.py index 44f2c2e..f1d7569 100644 --- a/server/config.py +++ b/server/config.py @@ -16,6 +16,7 @@ SQLALCHEMY_DATABASE_URI = "mysql://root:i_hate_passwords@localhost/easyctf" SQLALCHEMY_TRACK_MODIFICATIONS = False UPLOAD_FOLDER = os.path.normpath("../web/files") +GRADER_FOLDER = os.path.normpath("graders") CTF_BEGIN = 0 # To be used later CTF_END = 0 # To be used later @@ -24,4 +25,4 @@ MG_HOST = "" MG_API_KEY = "" ADMIN_EMAIL = "" -PROBLEM_DIR = "../problems" \ No newline at end of file +PROBLEM_DIR = "../problems" diff --git a/web/js/admin.js b/web/js/admin.js index b3640ef..b700dd1 100644 --- a/web/js/admin.js +++ b/web/js/admin.js @@ -7,6 +7,8 @@ $(document).ready(function() { var create_problem = function() { var input = "#new_problem_form input"; var data = $("#new_problem_form").serializeObject(); + var grader_contents = ace.edit("new_grader").getValue(); + data["grader_contents"] = grader_contents; $(input).attr("disabled", "disabled"); api_call("POST", "/api/problem/add", data, function(result) { if (result["success"] == 1) { @@ -30,6 +32,10 @@ var update_problem = function(form_id) { var input = "#" + form_id + " input"; var data = $("#" + form_id).serializeObject(); pid = data["pid"]; + + var grader_contents = ace.edit(pid + "_grader").getValue(); + data["grader_contents"] = grader_contents; + $(input).attr("disabled", "disabled"); api_call("POST", "/api/problem/update", data, function(result) { if (result["success"] == 1) { diff --git a/web/js/easyctf.js b/web/js/easyctf.js index 0f290c9..afa9f86 100644 --- a/web/js/easyctf.js +++ b/web/js/easyctf.js @@ -216,6 +216,34 @@ app.controller("adminProblemsController", ["$controller", "$scope", "$http", fun $scope.problems = []; } $scope.$apply(); + $scope.problems.forEach(function(problem) { + + $(".selectpicker").selectpicker(); + var config = { + toolbar: [ + { name: "basicstyles", items: [ "Bold", "Italic", "Underline" ] }, + { name: "links", items: [ "Link" ] }, + { name: "paragraph", items: [ "NumberedList", "BulletedList", "-", "Outdent", "Indent", "-", "Blockquote" ] }, + { name: "tools", items: [ "Maximize" ] }, + { name: "document", items: [ "Source" ] }, + ] + }; + var editor = new EpicEditor({ + container: "new_grader", + theme: { + base: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/base/epiceditor.css", + preview: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/preview/github.css", + editor: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/editor/epic-light.css" + }, + button: { + bar: "show" + } + }).load(); + var grader = ace.edit(problem.pid + "_grader"); + grader.setTheme("ace/theme/tomorrow"); + grader.getSession().setMode("ace/mode/python"); + grader.setValue(problem.grader_contents); + }); }); }]); diff --git a/web/pages/admin/problems.html b/web/pages/admin/problems.html index ec4d7dc..3fb183f 100644 --- a/web/pages/admin/problems.html +++ b/web/pages/admin/problems.html @@ -14,7 +14,7 @@
@@ -25,7 +25,7 @@
- +

@@ -63,7 +61,7 @@

- +

From a94df0dd49434904a365707989604d085eca3dc1 Mon Sep 17 00:00:00 2001 From: James Wang Date: Thu, 7 Apr 2016 22:54:47 -0400 Subject: [PATCH 05/21] Delete graders when deleting problems --- server/api/problem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/api/problem.py b/server/api/problem.py index 634b45a..b36af60 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -1,6 +1,7 @@ import hashlib import logger import os +import shutil import utils from flask import Blueprint, jsonify, session, request @@ -68,6 +69,8 @@ def problem_delete(): if problem: Solves.query.filter_by(pid=pid).delete() Problems.query.filter_by(pid=pid).delete() + grader_folder = "/".join(problem.grader.split("/")[:-1]) + shutil.rmtree(grader_folder) db.session.commit() return { "success": 1, "message": "Success!" } raise WebException("Problem does not exist!") From 765f6e0ec15db7272606c42e7388c5f672c44e5e Mon Sep 17 00:00:00 2001 From: James Wang Date: Thu, 7 Apr 2016 23:41:35 -0400 Subject: [PATCH 06/21] Show confirm dialog before deleting problem --- web/js/admin.js | 36 +++++++++++++++++++---------------- web/pages/admin/problems.html | 12 ++++++++++++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/web/js/admin.js b/web/js/admin.js index b700dd1..ad4e3a5 100644 --- a/web/js/admin.js +++ b/web/js/admin.js @@ -56,23 +56,27 @@ var update_problem = function(form_id) { }; var delete_problem = function(form_id) { - var input = "#" + form_id + " input"; - var pid = form_id.split("_")[1]; - $(input).attr("disabled", "disabled"); - api_call("POST", "/api/problem/delete", {"pid": pid}, function(result) { - if (result["success"] == 1) { - display_message(pid + "_status", "success", result["message"], function() { + $('#confirm').modal("show", { backdrop: 'static', keyboard: false }) + .one('click', '#delete', function() { + var input = "#" + form_id + " input"; + var pid = form_id.split("_")[1]; + $(input).attr("disabled", "disabled"); + api_call("POST", "/api/problem/delete", {"pid": pid}, function(result) { + if (result["success"] == 1) { + display_message(pid + "_status", "success", result["message"], function() { + $(input).removeAttr("disabled"); + }); + } else { + display_message(pid + "_status", "danger", result["message"], function() { + $(input).removeAttr("disabled"); + }); + } + }, function(jqXHR, status, error) { + var result = jqXHR["responseText"]; + display_message(pid + "_status", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() { $(input).removeAttr("disabled"); }); - } else { - display_message(pid + "_status", "danger", result["message"], function() { - $(input).removeAttr("disabled"); - }); - } - }, function(jqXHR, status, error) { - var result = jqXHR["responseText"]; - display_message(pid + "_status", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() { - $(input).removeAttr("disabled"); }); - }); + + }); } diff --git a/web/pages/admin/problems.html b/web/pages/admin/problems.html index 3fb183f..cc318f8 100644 --- a/web/pages/admin/problems.html +++ b/web/pages/admin/problems.html @@ -73,6 +73,18 @@
+ + diff --git a/web/pages/settings.html b/web/pages/settings.html index d2bc0fe..39e61a7 100644 --- a/web/pages/settings.html +++ b/web/pages/settings.html @@ -189,4 +189,4 @@ document.getElementById("file").addEventListener("change", uploadListener, false); }; }); - \ No newline at end of file + From 34fe70600b58fe66e39d612b58c52e2756dc0eb6 Mon Sep 17 00:00:00 2001 From: James Wang Date: Sat, 16 Apr 2016 22:15:57 -0400 Subject: [PATCH 09/21] Implement problem viewing and problem submitting --- server/api/logger.py | 2 +- server/api/models.py | 5 ++-- server/api/problem.py | 46 ++++++++++++++++++---------- web/js/easyctf.js | 14 +++++++++ web/pages/problems.html | 66 +++++++++++++++++++++++++++++++++++++++-- 5 files changed, 110 insertions(+), 23 deletions(-) diff --git a/server/api/logger.py b/server/api/logger.py index 1d886b7..81cd451 100644 --- a/server/api/logger.py +++ b/server/api/logger.py @@ -31,4 +31,4 @@ def initialize_logs(): def log(logname, message, level=INFO): logger = logging.getLogger(logname) message = "[%s] %s" % (datetime.datetime.now().strftime("%m/%d/%Y %X"), message) - logger.log(level, message) \ No newline at end of file + logger.log(level, message) diff --git a/server/api/models.py b/server/api/models.py index 2366a22..7bb3cb0 100644 --- a/server/api/models.py +++ b/server/api/models.py @@ -166,9 +166,8 @@ class Files(db.Model): self.location = location class Solves(db.Model): - __table_args__ = (db.UniqueConstraint("pid", "tid"), {}) sid = db.Column(db.Integer, primary_key=True) - pid = db.Column(db.Integer) + pid = db.Column(db.String(32)) tid = db.Column(db.Integer) date = db.Column(db.Integer, default=utils.get_time_since_epoch()) correct = db.Column(db.Boolean) @@ -214,7 +213,7 @@ class TeamInvitations(db.Model): class Settings(db.Model): sid = db.Column(db.Integer, primary_key=True) - key = db.Column(db.Text, unique=True) + key = db.Column(db.Text) value = db.Column(db.Text) def __init__(self, key, value): diff --git a/server/api/problem.py b/server/api/problem.py index b36af60..8f639e2 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -1,4 +1,5 @@ import hashlib +import imp import logger import os import shutil @@ -115,38 +116,51 @@ def problem_submit(): problem = Problems.query.filter_by(pid=pid).first() team = Teams.query.filter_by(tid=tid).first() + solved = Solves.query.filter_by(pid=pid, tid=tid, correct=1).first() + if solved: + raise WebException("You already solved this problem.") if problem: - if flag == problem.flag: - solve = Solves(pid, tid) - team.score += problem.value - problem.solves += 1 + grader = imp.load_source("grader.py", problem.grader) + if grader.grade(flag): + solve = Solves(pid, tid, flag, True) db.session.add(solve) - db.session.add(team) - db.session.add(problem) db.session.commit() - logger.log(__name__, logger.WARNING, "%s has solved %s by submitting %s" % (team.name, problem.title, flag)) + logger.log(__name__, "%s has solved %s by submitting %s" % (team.teamname, problem.title, flag), level=logger.WARNING) return { "success": 1, "message": "Correct!" } else: - logger.log(__name__, logger.WARNING, "%s has incorrectly submitted %s to %s" % (team.name, flag, problem.title)) + solve = Solves(pid, tid, flag, False) + db.session.add(solve) + db.session.commit() + logger.log(__name__, "%s has incorrectly submitted %s to %s" % (team.teamname, flag, problem.title), level=logger.WARNING) raise WebException("Incorrect.") else: raise WebException("Problem does not exist!") -@blueprint.route("/data", methods=["POST"]) -#@api_wrapper # Disable atm due to json serialization issues: will fix +@blueprint.route("/data", methods=["GET"]) @login_required +@api_wrapper def problem_data(): - problems = Problems.query.add_columns("pid", "name", "category", "description", "hint", "value", "solves").order_by(Problems.value).filter_by(disabled=False).all() - jason = [] + problems = Problems.query.order_by(Problems.value).all() + problems_return = [] for problem in problems: - problem_files = [ str(_file.location) for _file in Files.query.filter_by(pid=int(problem.pid)).all() ] - jason.append({"pid": problem[1], "title": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "files": problem_files}) - - return jsonify(data=jason) + solves = Solves.query.filter_by(pid=problem.pid, correct=1).count() + solved = Solves.query.filter_by(pid=problem.pid, tid=session.get("tid", None), correct=1) + solved = ["Solved", "Unsolved"][solved is None] + problems_return.append({ + "pid": problem.pid, + "title": problem.title, + "category": problem.category, + "description": problem.description, + "hint": problem.hint, + "value": problem.value, + "solves": solves, + "solved": solved + }) + return { "success": 1, "problems": problems_return } def insert_problem(data, force=False): with app.app_context(): diff --git a/web/js/easyctf.js b/web/js/easyctf.js index 72c8d32..b51deb9 100644 --- a/web/js/easyctf.js +++ b/web/js/easyctf.js @@ -48,6 +48,10 @@ app.config(function($routeProvider, $locationProvider) { templateUrl: "pages/settings.html", controller: "settingsController" }) + .when("/problems", { + templateUrl: "pages/problems.html", + controller: "problemsController" + }) .when("/forgot", { templateUrl: "pages/forgot.html", controller: "resetController" @@ -271,6 +275,16 @@ app.controller("settingsController", ["$controller", "$scope", "$http", function }); }]); +app.controller("problemsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) { + $controller("loginController", { $scope: $scope }); + api_call("GET", "/api/problem/data", {}, function(result) { + if (result["success"] == 1) { + $scope.problems = result["problems"]; + } + $scope.$apply(); + }); +}]); + $.fn.serializeObject = function() { var a, o; o = {}; diff --git a/web/pages/problems.html b/web/pages/problems.html index 614ed91..e189f66 100644 --- a/web/pages/problems.html +++ b/web/pages/problems.html @@ -5,7 +5,67 @@ 3) remove these instructions (:P) -->
-

Problems

-
+

Problems

+
+
+
+
+

+ {{ problem["title"] }} {{ problem["value"] }} points +
{{ problem["category"] }} - {{ problem["solved"] }} ({{ problem["solves"] }} {{ problem["solves"] === 1 ? "solve" : "solves" }})
+

+
+
+
+ + + +
+ + + + + + + + +
+ +
+
+
+
- From db91af5d828dd44506448f8d5b7778b070821176 Mon Sep 17 00:00:00 2001 From: James Wang Date: Sat, 16 Apr 2016 22:19:04 -0400 Subject: [PATCH 10/21] Store date as Text, not Integer --- server/api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/models.py b/server/api/models.py index 7bb3cb0..e29314b 100644 --- a/server/api/models.py +++ b/server/api/models.py @@ -169,7 +169,7 @@ class Solves(db.Model): sid = db.Column(db.Integer, primary_key=True) pid = db.Column(db.String(32)) tid = db.Column(db.Integer) - date = db.Column(db.Integer, default=utils.get_time_since_epoch()) + date = db.Column(db.Text, default=utils.get_time_since_epoch()) correct = db.Column(db.Boolean) flag = db.Column(db.Text) From aa4f19d9769111665632159d1ff9bacf88b38b85 Mon Sep 17 00:00:00 2001 From: James Wang Date: Sat, 16 Apr 2016 22:45:14 -0400 Subject: [PATCH 11/21] Add grader blueprint --- web/pages/admin/problems.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/pages/admin/problems.html b/web/pages/admin/problems.html index cc318f8..7050fb2 100644 --- a/web/pages/admin/problems.html +++ b/web/pages/admin/problems.html @@ -111,5 +111,14 @@ var new_grader = ace.edit("new_grader"); new_grader.setTheme("ace/theme/tomorrow"); new_grader.getSession().setMode("ace/mode/python"); + new_grader.setValue( + 'flag = "easyctf{FLAG}"\n\n'+ + 'def grade(candidate):\n'+ + ' if candidate == flag:\n'+ + ' return True, "Correct!"\n'+ + ' return False, "Incorrect."\n\n'+ + '# Return True if the flag is correct, False otherwise, along with\n'+ + '# a corresponding message to send to the user.' + ); }); From d3371e5417052319c81ebba1287bbb53dfe50913 Mon Sep 17 00:00:00 2001 From: James Wang Date: Sun, 17 Apr 2016 08:58:32 -0400 Subject: [PATCH 12/21] Check for syntax errors in new graders --- server/api/problem.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/api/problem.py b/server/api/problem.py index 8f639e2..d1d0f6b 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -32,6 +32,11 @@ def problem_add(): if title_exist: raise WebException("Problem name already taken.") + try: + exec(grader_contents) + except Exception, e: + raise WebException("There is a syntax error in the grader: %s" % e) + problem = Problems(pid, title, category, description, value, hint=hint) db.session.add(problem) db.session.commit() @@ -87,6 +92,10 @@ def problem_update(): hint = request.form["hint"] value = request.form["value"] grader_contents = request.form["grader_contents"] + try: + exec(grader_contents) + except Exception, e: + raise WebException("There is a syntax error in the grader: %s" % e) problem = Problems.query.filter_by(pid=pid).first() if problem: From bc13bdce80c7a0b98c9aaf204b9f2260ede9bd4c Mon Sep 17 00:00:00 2001 From: James Wang Date: Sun, 17 Apr 2016 09:27:46 -0400 Subject: [PATCH 13/21] Add support for custom responses in the grader --- server/api/problem.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/server/api/problem.py b/server/api/problem.py index d1d0f6b..821e68f 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -128,22 +128,22 @@ def problem_submit(): solved = Solves.query.filter_by(pid=pid, tid=tid, correct=1).first() if solved: raise WebException("You already solved this problem.") - if problem: - grader = imp.load_source("grader.py", problem.grader) - if grader.grade(flag): - solve = Solves(pid, tid, flag, True) - db.session.add(solve) - db.session.commit() + if problem: + grader = imp.load_source("grader", problem.grader) + correct, response = grader.grade(flag) + + solve = Solves(pid, tid, flag, correct) + db.session.add(solve) + db.session.commit() + + if correct: logger.log(__name__, "%s has solved %s by submitting %s" % (team.teamname, problem.title, flag), level=logger.WARNING) - return { "success": 1, "message": "Correct!" } + return { "success": 1, "message": response } else: - solve = Solves(pid, tid, flag, False) - db.session.add(solve) - db.session.commit() logger.log(__name__, "%s has incorrectly submitted %s to %s" % (team.teamname, flag, problem.title), level=logger.WARNING) - raise WebException("Incorrect.") + raise WebException(response) else: raise WebException("Problem does not exist!") From dbeb86edbca78a458337a54f73027f9d5e9be506 Mon Sep 17 00:00:00 2001 From: James Wang Date: Sun, 17 Apr 2016 21:30:49 -0400 Subject: [PATCH 14/21] Fix bug where new grader editor would be uneditable --- web/js/easyctf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/easyctf.js b/web/js/easyctf.js index b51deb9..681d49c 100644 --- a/web/js/easyctf.js +++ b/web/js/easyctf.js @@ -237,7 +237,7 @@ app.controller("adminProblemsController", ["$controller", "$scope", "$http", fun ] }; var editor = new EpicEditor({ - container: "new_grader", + container: problem.pid + "_grader", theme: { base: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/base/epiceditor.css", preview: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/preview/github.css", From 398b413095eb92985b7f14218f8d8f3a426c71ec Mon Sep 17 00:00:00 2001 From: James Wang Date: Sun, 17 Apr 2016 22:37:01 -0400 Subject: [PATCH 15/21] Remove obsolete EpicEditor javascript --- web/js/easyctf.js | 22 ---------------------- web/pages/admin/problems.html | 21 --------------------- 2 files changed, 43 deletions(-) diff --git a/web/js/easyctf.js b/web/js/easyctf.js index 681d49c..2ed8b06 100644 --- a/web/js/easyctf.js +++ b/web/js/easyctf.js @@ -225,28 +225,6 @@ app.controller("adminProblemsController", ["$controller", "$scope", "$http", fun } $scope.$apply(); $scope.problems.forEach(function(problem) { - - $(".selectpicker").selectpicker(); - var config = { - toolbar: [ - { name: "basicstyles", items: [ "Bold", "Italic", "Underline" ] }, - { name: "links", items: [ "Link" ] }, - { name: "paragraph", items: [ "NumberedList", "BulletedList", "-", "Outdent", "Indent", "-", "Blockquote" ] }, - { name: "tools", items: [ "Maximize" ] }, - { name: "document", items: [ "Source" ] }, - ] - }; - var editor = new EpicEditor({ - container: problem.pid + "_grader", - theme: { - base: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/base/epiceditor.css", - preview: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/preview/github.css", - editor: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/editor/epic-light.css" - }, - button: { - bar: "show" - } - }).load(); var grader = ace.edit(problem.pid + "_grader"); grader.setTheme("ace/theme/tomorrow"); grader.getSession().setMode("ace/mode/python"); diff --git a/web/pages/admin/problems.html b/web/pages/admin/problems.html index 7050fb2..9c253b1 100644 --- a/web/pages/admin/problems.html +++ b/web/pages/admin/problems.html @@ -87,27 +87,6 @@ From b428bef8083bf460c66fc6e719b03fcbe9d30bc2 Mon Sep 17 00:00:00 2001 From: James Wang Date: Tue, 19 Apr 2016 10:14:56 -0400 Subject: [PATCH 19/21] Fix syntax highlighting --- web/pages/programming.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/pages/programming.html b/web/pages/programming.html index c5575d0..8591e33 100644 --- a/web/pages/programming.html +++ b/web/pages/programming.html @@ -28,10 +28,11 @@