From 36c83b59bc616f12af5578b5be6c1082b1701cb2 Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Thu, 3 May 2018 18:04:39 -0400 Subject: [PATCH] 1.2.0 (#627) * Changing to a new plugin oriented challenge type plugin and fixing extra width on admin chal description * Add window.challenge.submit, renderSubmissionResponse, and csrf_nonce * Update admin side renderer calls * Updating to Flask 1.0 and adding files for flask run * Adding a preliminary case-insensitive key * Adding case insensitive keys * Adding CTF Logo * Reducing the amount of team information shown on the main page * Add better base64 helpers * Switch from button to badge * Rudimentary solve checking from admin panel * Refine admin chals solves view & fix PEP8 * Compare base64 encoded data with bytestring * Removing need to urlencode/urldecode in base64 wrappers * Adding decorator documentation * Randomly order tests & add test for case_insensitive flags * Add regex flag case_insensitive test * Add tests for /admin/chal/1/solves and ctf_logo --- .flaskenv | 2 + .travis.yml | 2 +- CTFd/admin/__init__.py | 9 + CTFd/admin/challenges.py | 13 + CTFd/admin/teams.py | 33 ++- CTFd/auth.py | 4 +- CTFd/plugins/challenges/__init__.py | 2 +- .../assets/standard-challenge-create.js | 20 +- .../assets/standard-challenge-modal.js | 52 ++-- .../assets/standard-challenge-update.js | 14 +- CTFd/plugins/keys/__init__.py | 25 +- .../keys/assets/regex/create-regex-modal.njk | 6 +- .../keys/assets/regex/edit-regex-modal.njk | 57 +++-- .../assets/static/create-static-modal.njk | 4 + .../keys/assets/static/edit-static-modal.njk | 58 +++-- .../admin/static/css/challenge-board.css | 2 - .../admin/static/js/challenges/chal-new.js | 15 +- .../admin/static/js/challenges/chalboard.js | 225 ++++++++++-------- .../themes/admin/static/js/keys/keys-utils.js | 31 +-- CTFd/themes/admin/templates/base.html | 1 + CTFd/themes/admin/templates/challenges.html | 12 +- CTFd/themes/admin/templates/config.html | 28 ++- .../modals/challenges/challenges-solves.html | 38 +++ .../modals/challenges/challenges.html | 1 + CTFd/themes/admin/templates/teams.html | 81 ++++--- CTFd/themes/core/static/js/chalboard.js | 215 +++++++++-------- CTFd/themes/core/templates/base.html | 9 +- CTFd/utils/__init__.py | 23 +- CTFd/utils/decorators.py | 24 +- CTFd/utils/helpers.py | 0 development.txt | 2 + requirements.txt | 3 +- tests/admin/test_admin_config.py | 2 + tests/admin/test_admin_facing.py | 20 +- tests/helpers.py | 4 +- tests/test_utils.py | 33 +-- tests/user/test_auth.py | 6 +- tests/user/test_challenges.py | 40 ++++ tests/user/test_user_facing.py | 2 +- wsgi.py | 6 + 40 files changed, 695 insertions(+), 429 deletions(-) create mode 100644 .flaskenv create mode 100644 CTFd/themes/admin/templates/modals/challenges/challenges-solves.html delete mode 100644 CTFd/utils/helpers.py create mode 100644 wsgi.py diff --git a/.flaskenv b/.flaskenv new file mode 100644 index 0000000..d41e74c --- /dev/null +++ b/.flaskenv @@ -0,0 +1,2 @@ +FLASK_ENV=development +FLASK_RUN_PORT=4000 diff --git a/.travis.yml b/.travis.yml index 49a25aa..5e3a9af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,6 @@ before_script: - psql -c 'create database ctfd;' -U postgres script: - pep8 --ignore E501,E712 CTFd/ tests/ - - nosetests -d + - nosetests -v -d --with-randomly after_success: - codecov diff --git a/CTFd/admin/__init__.py b/CTFd/admin/__init__.py index a26a789..a39bed6 100644 --- a/CTFd/admin/__init__.py +++ b/CTFd/admin/__init__.py @@ -147,6 +147,13 @@ def admin_config(): utils.set_config("mail_username", None) utils.set_config("mail_password", None) + if request.files.get('ctf_logo', None): + ctf_logo = request.files['ctf_logo'] + file_id, file_loc = utils.upload_file(ctf_logo, None) + utils.set_config("ctf_logo", file_loc) + elif request.form.get('ctf_logo') == '': + utils.set_config("ctf_logo", None) + utils.set_config("ctf_name", request.form.get('ctf_name', None)) utils.set_config("ctf_theme", request.form.get('ctf_theme', None)) utils.set_config('css', request.form.get('css', None)) @@ -176,6 +183,7 @@ def admin_config(): cache.clear() ctf_name = utils.get_config('ctf_name') + ctf_logo = utils.get_config('ctf_logo') ctf_theme = utils.get_config('ctf_theme') hide_scores = utils.get_config('hide_scores') css = utils.get_config('css') @@ -216,6 +224,7 @@ def admin_config(): return render_template( 'admin/config.html', ctf_name=ctf_name, + ctf_logo=ctf_logo, ctf_theme_config=ctf_theme, css=css, start=start, diff --git a/CTFd/admin/challenges.py b/CTFd/admin/challenges.py index 32c66d6..a0ad6a6 100644 --- a/CTFd/admin/challenges.py +++ b/CTFd/admin/challenges.py @@ -100,6 +100,19 @@ def admin_chal_detail(chalid): return jsonify(data) +@admin_challenges.route('/admin/chal//solves', methods=['GET']) +@admins_only +def admin_chal_solves(chalid): + response = {'teams': []} + if utils.hide_scores(): + return jsonify(response) + solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid).order_by( + Solves.date.asc()) + for solve in solves: + response['teams'].append({'id': solve.team.id, 'name': solve.team.name, 'date': solve.date}) + return jsonify(response) + + @admin_challenges.route('/admin/tags/', methods=['GET', 'POST']) @admins_only def admin_tags(chalid): diff --git a/CTFd/admin/teams.py b/CTFd/admin/teams.py index a1674d0..dfa38bc 100644 --- a/CTFd/admin/teams.py +++ b/CTFd/admin/teams.py @@ -57,6 +57,10 @@ def admin_create_team(): affiliation = request.form.get('affiliation', None) country = request.form.get('country', None) + admin_user = True if request.form.get('admin', None) == 'on' else False + verified = True if request.form.get('verified', None) == 'on' else False + hidden = True if request.form.get('hidden', None) == 'on' else False + errors = [] if not name: @@ -92,6 +96,10 @@ def admin_create_team(): team.affiliation = affiliation team.country = country + team.admin = admin_user + team.verified = verified + team.hidden = hidden + db.session.add(team) db.session.commit() db.session.close() @@ -119,24 +127,6 @@ def admin_team(teamid): return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing, place=place, wrong_keys=wrong_keys, awards=awards) elif request.method == 'POST': - admin_user = request.form.get('admin', None) - if admin_user: - admin_user = True if admin_user == 'true' else False - user.admin = admin_user - # Set user.banned to hide admins from scoreboard - user.banned = admin_user - db.session.commit() - db.session.close() - return jsonify({'data': ['success']}) - - verified = request.form.get('verified', None) - if verified: - verified = True if verified == 'true' else False - user.verified = verified - db.session.commit() - db.session.close() - return jsonify({'data': ['success']}) - name = request.form.get('name', None) password = request.form.get('password', None) email = request.form.get('email', None) @@ -144,6 +134,10 @@ def admin_team(teamid): affiliation = request.form.get('affiliation', None) country = request.form.get('country', None) + admin_user = True if request.form.get('admin', None) == 'on' else False + verified = True if request.form.get('verified', None) == 'on' else False + hidden = True if request.form.get('hidden', None) == 'on' else False + errors = [] if email: @@ -177,6 +171,9 @@ def admin_team(teamid): user.website = website user.affiliation = affiliation user.country = country + user.admin = admin_user + user.verified = verified + user.banned = hidden db.session.commit() db.session.close() return jsonify({'data': ['success']}) diff --git a/CTFd/auth.py b/CTFd/auth.py index e797b42..2de6fc5 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -29,7 +29,7 @@ def confirm_user(data=None): if data and request.method == "GET": try: s = TimedSerializer(app.config['SECRET_KEY']) - email = s.loads(utils.base64decode(data, urldecode=True), max_age=1800) + email = s.loads(utils.base64decode(data), max_age=1800) except BadTimeSignature: return render_template('confirm.html', errors=['Your confirmation link has expired']) except (BadSignature, TypeError, base64.binascii.Error): @@ -86,7 +86,7 @@ def reset_password(data=None): if data is not None: try: s = TimedSerializer(app.config['SECRET_KEY']) - name = s.loads(utils.base64decode(data, urldecode=True), max_age=1800) + name = s.loads(utils.base64decode(data), max_age=1800) except BadTimeSignature: return render_template('reset_password.html', errors=['Your link has expired']) except (BadSignature, TypeError, base64.binascii.Error): diff --git a/CTFd/plugins/challenges/__init__.py b/CTFd/plugins/challenges/__init__.py index e06faea..e17631b 100644 --- a/CTFd/plugins/challenges/__init__.py +++ b/CTFd/plugins/challenges/__init__.py @@ -146,7 +146,7 @@ class CTFdStandardChallenge(BaseChallenge): provided_key = request.form['key'].strip() chal_keys = Keys.query.filter_by(chal=chal.id).all() for chal_key in chal_keys: - if get_key_class(chal_key.type).compare(chal_key.flag, provided_key): + if get_key_class(chal_key.type).compare(chal_key, provided_key): return True, 'Correct' return False, 'Incorrect' diff --git a/CTFd/plugins/challenges/assets/standard-challenge-create.js b/CTFd/plugins/challenges/assets/standard-challenge-create.js index e719f99..ba308b8 100644 --- a/CTFd/plugins/challenges/assets/standard-challenge-create.js +++ b/CTFd/plugins/challenges/assets/standard-challenge-create.js @@ -1,28 +1,22 @@ // Markdown Preview $('#desc-edit').on('shown.bs.tab', function (event) { - var md = window.markdownit({ - html: true, - }); - if (event.target.hash == '#desc-preview'){ + if (event.target.hash == '#desc-preview') { var editor_value = $('#desc-editor').val(); $(event.target.hash).html( - md.render(editor_value) + window.challenge.render(editor_value) ); } }); $('#new-desc-edit').on('shown.bs.tab', function (event) { - var md = window.markdownit({ - html: true, - }); - if (event.target.hash == '#new-desc-preview'){ + if (event.target.hash == '#new-desc-preview') { var editor_value = $('#new-desc-editor').val(); $(event.target.hash).html( - md.render(editor_value) + window.challenge.render(editor_value) ); } }); -$("#solve-attempts-checkbox").change(function() { - if(this.checked) { +$("#solve-attempts-checkbox").change(function () { + if (this.checked) { $('#solve-attempts-input').show(); } else { $('#solve-attempts-input').hide(); @@ -30,6 +24,6 @@ $("#solve-attempts-checkbox").change(function() { } }); -$(document).ready(function(){ +$(document).ready(function () { $('[data-toggle="tooltip"]').tooltip(); }); diff --git a/CTFd/plugins/challenges/assets/standard-challenge-modal.js b/CTFd/plugins/challenges/assets/standard-challenge-modal.js index 409e155..f30985d 100644 --- a/CTFd/plugins/challenges/assets/standard-challenge-modal.js +++ b/CTFd/plugins/challenges/assets/standard-challenge-modal.js @@ -1,25 +1,35 @@ -$('#submit-key').unbind('click'); -$('#submit-key').click(function (e) { - e.preventDefault(); - submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val()) +window.challenge.renderer = new markdownit({ + html: true, }); -$("#answer-input").keyup(function(event){ - if(event.keyCode == 13){ - $("#submit-key").click(); - } -}); +window.challenge.preRender = function(){ -$(".input-field").bind({ - focus: function() { - $(this).parent().addClass('input--filled' ); - $label = $(this).siblings(".input-label"); - }, - blur: function() { - if ($(this).val() === '') { - $(this).parent().removeClass('input--filled' ); - $label = $(this).siblings(".input-label"); - $label.removeClass('input--hide' ); - } +}; + +window.challenge.render = function(markdown){ + return window.challenge.renderer.render(markdown); +}; + + +window.challenge.postRender = function(){ + +}; + + +window.challenge.submit = function(cb, preview){ + var chal_id = $('#chal-id').val(); + var answer = $('#answer-input').val(); + var nonce = $('#nonce').val(); + + var url = "/chal/"; + if (preview) { + url = "/admin/chal/"; } -}); \ No newline at end of file + + $.post(script_root + url + chal_id, { + key: answer, + nonce: nonce + }, function (data) { + cb(data); + }); +}; \ No newline at end of file diff --git a/CTFd/plugins/challenges/assets/standard-challenge-update.js b/CTFd/plugins/challenges/assets/standard-challenge-update.js index 368c985..842dbd4 100644 --- a/CTFd/plugins/challenges/assets/standard-challenge-update.js +++ b/CTFd/plugins/challenges/assets/standard-challenge-update.js @@ -18,24 +18,18 @@ $('#limit_max_attempts').change(function() { // Markdown Preview $('#desc-edit').on('shown.bs.tab', function (event) { - var md = window.markdownit({ - html: true, - }); - if (event.target.hash == '#desc-preview'){ + if (event.target.hash == '#desc-preview') { var editor_value = $('#desc-editor').val(); $(event.target.hash).html( - md.render(editor_value) + window.challenge.render(editor_value) ); } }); $('#new-desc-edit').on('shown.bs.tab', function (event) { - var md = window.markdownit({ - html: true, - }); - if (event.target.hash == '#new-desc-preview'){ + if (event.target.hash == '#new-desc-preview') { var editor_value = $('#new-desc-editor').val(); $(event.target.hash).html( - md.render(editor_value) + window.challenge.render(editor_value) ); } }); diff --git a/CTFd/plugins/keys/__init__.py b/CTFd/plugins/keys/__init__.py index 92a0c22..4c4845f 100644 --- a/CTFd/plugins/keys/__init__.py +++ b/CTFd/plugins/keys/__init__.py @@ -24,12 +24,20 @@ class CTFdStaticKey(BaseKey): } @staticmethod - def compare(saved, provided): + def compare(chal_key_obj, provided): + saved = chal_key_obj.flag + data = chal_key_obj.data + if len(saved) != len(provided): return False result = 0 - for x, y in zip(saved, provided): - result |= ord(x) ^ ord(y) + + if data == "case_insensitive": + for x, y in zip(saved.lower(), provided.lower()): + result |= ord(x) ^ ord(y) + else: + for x, y in zip(saved, provided): + result |= ord(x) ^ ord(y) return result == 0 @@ -42,8 +50,15 @@ class CTFdRegexKey(BaseKey): } @staticmethod - def compare(saved, provided): - res = re.match(saved, provided, re.IGNORECASE) + def compare(chal_key_obj, provided): + saved = chal_key_obj.flag + data = chal_key_obj.data + + if data == "case_insensitive": + res = re.match(saved, provided, re.IGNORECASE) + else: + res = re.match(saved, provided) + return res and res.group() == provided diff --git a/CTFd/plugins/keys/assets/regex/create-regex-modal.njk b/CTFd/plugins/keys/assets/regex/create-regex-modal.njk index abd2c23..a7a1d2e 100644 --- a/CTFd/plugins/keys/assets/regex/create-regex-modal.njk +++ b/CTFd/plugins/keys/assets/regex/create-regex-modal.njk @@ -1,2 +1,6 @@ - \ No newline at end of file + +
+ + +
\ No newline at end of file diff --git a/CTFd/plugins/keys/assets/regex/edit-regex-modal.njk b/CTFd/plugins/keys/assets/regex/edit-regex-modal.njk index c088b9d..e8f0b73 100644 --- a/CTFd/plugins/keys/assets/regex/edit-regex-modal.njk +++ b/CTFd/plugins/keys/assets/regex/edit-regex-modal.njk @@ -1,28 +1,33 @@ \ No newline at end of file diff --git a/CTFd/plugins/keys/assets/static/create-static-modal.njk b/CTFd/plugins/keys/assets/static/create-static-modal.njk index 79bcd21..5321506 100644 --- a/CTFd/plugins/keys/assets/static/create-static-modal.njk +++ b/CTFd/plugins/keys/assets/static/create-static-modal.njk @@ -1,2 +1,6 @@ +
+ + +
\ No newline at end of file diff --git a/CTFd/plugins/keys/assets/static/edit-static-modal.njk b/CTFd/plugins/keys/assets/static/edit-static-modal.njk index 7ea85e5..ad2ca17 100644 --- a/CTFd/plugins/keys/assets/static/edit-static-modal.njk +++ b/CTFd/plugins/keys/assets/static/edit-static-modal.njk @@ -1,28 +1,34 @@ \ No newline at end of file diff --git a/CTFd/themes/admin/static/css/challenge-board.css b/CTFd/themes/admin/static/css/challenge-board.css index d5532de..f9229f8 100644 --- a/CTFd/themes/admin/static/css/challenge-board.css +++ b/CTFd/themes/admin/static/css/challenge-board.css @@ -4,8 +4,6 @@ pre { } .chal-desc { - padding-left: 30px; - padding-right: 30px; font-size: 14px; } diff --git a/CTFd/themes/admin/static/js/challenges/chal-new.js b/CTFd/themes/admin/static/js/challenges/chal-new.js index 19389e5..cd6ae74 100644 --- a/CTFd/themes/admin/static/js/challenges/chal-new.js +++ b/CTFd/themes/admin/static/js/challenges/chal-new.js @@ -1,11 +1,16 @@ $.ajaxSetup({ cache: false }); +window.challenge = new Object(); + function load_chal_template(challenge){ - $.get(script_root + challenge.templates.create, function(template_data){ - var template = nunjucks.compile(template_data); - $("#create-chal-entry-div").html(template.render({'nonce':nonce, 'script_root':script_root})); - $.getScript(script_root + challenge.scripts.create, function(){ - console.log('loaded'); + $.getScript(script_root + challenge.scripts.modal, function () { + console.log('loaded renderer'); + $.get(script_root + challenge.templates.create, function (template_data) { + var template = nunjucks.compile(template_data); + $("#create-chal-entry-div").html(template.render({'nonce': nonce, 'script_root': script_root})); + $.getScript(script_root + challenge.scripts.create, function () { + console.log('loaded'); + }); }); }); } diff --git a/CTFd/themes/admin/static/js/challenges/chalboard.js b/CTFd/themes/admin/static/js/challenges/chalboard.js index c1f2861..7ab0744 100644 --- a/CTFd/themes/admin/static/js/challenges/chalboard.js +++ b/CTFd/themes/admin/static/js/challenges/chalboard.js @@ -1,24 +1,24 @@ var challenges = {}; - +window.challenge = new Object(); function load_chal_template(id, success_cb){ - var obj = $.grep(challenges['game'], function (e) { - return e.id == id; - })[0]; - $.get(script_root + "/admin/chal/" + id, function (challenge_data) { - $.get(script_root + obj.type_data.templates.update, function (template_data) { - var template = nunjucks.compile(template_data); + $.get(script_root + "/admin/chal/" + id, function (obj) { + $.getScript(script_root + obj.type_data.scripts.modal, function () { + console.log('loaded renderer'); + $.get(script_root + obj.type_data.templates.update, function (template_data) { + var template = nunjucks.compile(template_data); - challenge_data['nonce'] = $('#nonce').val(); - challenge_data['script_root'] = script_root; + obj['nonce'] = $('#nonce').val(); + obj['script_root'] = script_root; - $("#update-modals-entry-div").html(template.render(challenge_data)); + $("#update-modals-entry-div").html(template.render(obj)); - $.ajax({ - url: script_root + obj.type_data.scripts.update, - dataType: "script", - success: success_cb, - cache: false, + $.ajax({ + url: script_root + obj.type_data.scripts.update, + dataType: "script", + success: success_cb, + cache: false, + }); }); }); }); @@ -30,55 +30,63 @@ function load_challenge_preview(id){ function render_challenge_preview(chal_id){ var preview_window = $('#challenge-preview'); - var md = window.markdownit({ - html: true, - }); - $.get(script_root + "/admin/chal/" + chal_id, function(challenge_data){ - $.get(script_root + challenge_data.type_data.templates.modal, function (template_data) { - preview_window.empty(); - var template = nunjucks.compile(template_data); + $.get(script_root + "/admin/chal/" + chal_id, function(obj){ + $.getScript(script_root + obj.type_data.scripts.modal, function () { + console.log('loaded renderer'); - challenge_data['description'] = md.render(challenge_data['description']); - challenge_data['script_root'] = script_root; + $.get(script_root + obj.type_data.templates.modal, function (template_data) { + var template = nunjucks.compile(template_data); - var challenge = template.render(challenge_data); + window.challenge.preRender() - preview_window.append(challenge); + obj['description'] = window.challenge.render(obj['description']); + obj['script_root'] = script_root; + + var challenge = template.render(obj); + + preview_window.html(challenge); + + $('#submit-key').click(function (e) { + e.preventDefault(); + $('#submit-key').addClass("disabled-button"); + $('#submit-key').prop('disabled', true); + + window.challenge.submit(function (data) { + renderSubmissionResponse(data) + }, preview=true); + }); + + $("#answer-input").keyup(function (event) { + if (event.keyCode == 13) { + $("#submit-key").click(); + } + }); + + window.challenge.postRender() - $.getScript(script_root + challenge_data.type_data.scripts.modal, function () { preview_window.modal(); }); }); }); } -function loadchals(cb){ - $.post(script_root + "/admin/chals", { - 'nonce': $('#nonce').val() - }, function (data) { - var categories = []; - challenges = $.parseJSON(JSON.stringify(data)); - for (var i = challenges['game'].length - 1; i >= 0; i--) { - if ($.inArray(challenges['game'][i].category, categories) == -1) { - categories.push(challenges['game'][i].category) - } - } - - if (cb) { - cb(); +function loadsolves(id) { + $.get(script_root + '/admin/chal/' + id + '/solves', function (data) { + var teams = data['teams']; + var box = $('#challenge-solves-body'); + var modal = $('#challenge-solves-modal') + box.empty(); + for (var i = 0; i < teams.length; i++) { + var id = teams[i].id; + var name = teams[i].name; + var date = moment(teams[i].date).local().format('MMMM Do, h:mm:ss A'); + box.append('{1}{2}'.format(id, htmlentities(name), date)); } + modal.modal(); }); } -loadchals(function(){ - $('.edit-challenge').click(function (e) { - var id = $(this).attr('chal-id'); - load_chal_template(id, function () { - openchal(id); - }); - }); -}); function loadhint(hintid) { var md = window.markdownit({ @@ -107,64 +115,62 @@ function loadhint(hintid) { }); } -function submitkey(chal, key, nonce){ - $.post(script_root + "/admin/chal/" + chal, { - key: key, - nonce: nonce - }, function (data) { - console.log(data); - var result = $.parseJSON(JSON.stringify(data)); +function renderSubmissionResponse(data, cb) { + var result = $.parseJSON(JSON.stringify(data)); - var result_message = $('#result-message'); - var result_notification = $('#result-notification'); - var answer_input = $("#answer-input"); - result_notification.removeClass(); - result_message.text(result.message); + var result_message = $('#result-message'); + var result_notification = $('#result-notification'); + var answer_input = $("#answer-input"); + result_notification.removeClass(); + result_message.text(result.message); - if (result.status == -1) { - window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash - return - } - else if (result.status == 0) { // Incorrect key - result_notification.addClass('alert alert-danger alert-dismissable text-center'); - result_notification.slideDown(); - - answer_input.removeClass("correct"); - answer_input.addClass("wrong"); - setTimeout(function () { - answer_input.removeClass("wrong"); - }, 3000); - } - else if (result.status == 1) { // Challenge Solved - result_notification.addClass('alert alert-success alert-dismissable text-center'); - result_notification.slideDown(); - - answer_input.val(""); - answer_input.removeClass("wrong"); - answer_input.addClass("correct"); - } - else if (result.status == 2) { // Challenge already solved - result_notification.addClass('alert alert-info alert-dismissable text-center'); - result_notification.slideDown(); - - answer_input.addClass("correct"); - } - else if (result.status == 3) { // Keys per minute too high - result_notification.addClass('alert alert-warning alert-dismissable text-center'); - result_notification.slideDown(); - - answer_input.addClass("too-fast"); - setTimeout(function () { - answer_input.removeClass("too-fast"); - }, 3000); - } + if (result.status == -1) { + window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash + return + } + else if (result.status == 0) { // Incorrect key + result_notification.addClass('alert alert-danger alert-dismissable text-center'); + result_notification.slideDown(); + answer_input.removeClass("correct"); + answer_input.addClass("wrong"); setTimeout(function () { - $('.alert').slideUp(); - $('#submit-key').removeClass("disabled-button"); - $('#submit-key').prop('disabled', false); + answer_input.removeClass("wrong"); }, 3000); - }); + } + else if (result.status == 1) { // Challenge Solved + result_notification.addClass('alert alert-success alert-dismissable text-center'); + result_notification.slideDown(); + + answer_input.val(""); + answer_input.removeClass("wrong"); + answer_input.addClass("correct"); + } + else if (result.status == 2) { // Challenge already solved + result_notification.addClass('alert alert-info alert-dismissable text-center'); + result_notification.slideDown(); + + answer_input.addClass("correct"); + } + else if (result.status == 3) { // Keys per minute too high + result_notification.addClass('alert alert-warning alert-dismissable text-center'); + result_notification.slideDown(); + + answer_input.addClass("too-fast"); + setTimeout(function () { + answer_input.removeClass("too-fast"); + }, 3000); + } + + setTimeout(function () { + $('.alert').slideUp(); + $('#submit-key').removeClass("disabled-button"); + $('#submit-key').prop('disabled', false); + }, 3000); + + if (cb) { + cb(result); + } } $(document).ready(function () { @@ -194,9 +200,24 @@ $(document).ready(function () { }); }); + $('.edit-challenge').click(function (e) { + var id = $(this).attr('chal-id'); + load_chal_template(id, function () { + openchal(id); + }); + }); + $('.preview-challenge').click(function (e) { var chal_id = $(this).attr('chal-id'); load_challenge_preview(chal_id); }); + + $('.stats-challenge').click(function (e) { + var chal_id = $(this).attr('chal-id'); + var title = $(this).attr('title') || $(this).attr('data-original-title'); + $('#challenge-solves-title').text(title); + + loadsolves(chal_id); + }); }); \ No newline at end of file diff --git a/CTFd/themes/admin/static/js/keys/keys-utils.js b/CTFd/themes/admin/static/js/keys/keys-utils.js index e0f0331..69379da 100644 --- a/CTFd/themes/admin/static/js/keys/keys-utils.js +++ b/CTFd/themes/admin/static/js/keys/keys-utils.js @@ -17,13 +17,9 @@ function load_edit_key_modal(key_id, key_type_name) { } -function create_key(chal, key, key_type) { - $.post(script_root + "/admin/keys", { - chal: chal, - key: key, - key_type: key_type, - nonce: $('#nonce').val() - }, function (data) { +function create_key(chal, chal_data) { + chal_data.push({name: 'nonce', value: $('#nonce').val()}); + $.post(script_root + "/admin/keys", chal_data, function (data) { if (data == "1"){ loadkeys(chal); $("#create-keys").modal('toggle'); @@ -73,17 +69,12 @@ function deletekey(key_id){ } function updatekey(){ + var edit_key_modal = $('#edit-keys form').serializeArray(); + var key_id = $('#key-id').val(); var chal = $("#update-keys").attr('chal-id'); - var key_data = $('#key-data').val(); - var key_type = $('#key-type').val(); - var nonce = $('#nonce').val(); - $.post(script_root + '/admin/keys/'+key_id, { - 'chal':chal, - 'key':key_data, - 'key_type': key_type, - 'nonce': nonce - }, function(data){ + + $.post(script_root + '/admin/keys/'+key_id, edit_key_modal, function(data){ if (data == "1") { loadkeys(chal); $('#edit-keys').modal('toggle'); @@ -132,9 +123,13 @@ $(document).ready(function () { $('#create-keys-submit').click(function (e) { e.preventDefault(); + var chal_data = $('#create-keys-entry-div :input').serializeArray(); + var chalid = $("#update-keys").attr('chal-id'); - var key_data = $('#create-keys').find('input[name=key]').val(); + chal_data.push({name: 'chal', value: chalid}); + var key_type = $('#create-keys-select').val(); - create_key(chalid, key_data, key_type); + chal_data.push({name: 'key_type', value: key_type}); + create_key(chalid, chal_data); }); }); \ No newline at end of file diff --git a/CTFd/themes/admin/templates/base.html b/CTFd/themes/admin/templates/base.html index 387eeeb..16e098d 100644 --- a/CTFd/themes/admin/templates/base.html +++ b/CTFd/themes/admin/templates/base.html @@ -19,6 +19,7 @@ {% block stylesheets %} {% endblock %} diff --git a/CTFd/themes/admin/templates/challenges.html b/CTFd/themes/admin/templates/challenges.html index afb2c6e..1ede08a 100644 --- a/CTFd/themes/admin/templates/challenges.html +++ b/CTFd/themes/admin/templates/challenges.html @@ -7,6 +7,7 @@ {% block content %} {% include "admin/modals/challenges/challenges.html" %} + {% include "admin/modals/challenges/challenges-solves.html" %} {% include "admin/modals/tags/tags.html" %} {% include "admin/modals/files/files.html" %} @@ -57,9 +58,9 @@ {{ challenge.type }} {% if challenge.hidden %} - + hidden {% else %} - + visible {% endif %} @@ -75,6 +76,13 @@ + + + +    
-
+ {% for error in errors %}