diff --git a/CTFd/api/v1/hints.py b/CTFd/api/v1/hints.py index eacfb968..236295cd 100644 --- a/CTFd/api/v1/hints.py +++ b/CTFd/api/v1/hints.py @@ -3,7 +3,7 @@ from flask_restplus import Namespace, Resource from CTFd.models import db, Hints, HintUnlocks from CTFd.plugins.challenges import get_chal_class from CTFd.utils.dates import ctf_ended -from CTFd.utils.user import get_current_user +from CTFd.utils.user import get_current_user, is_admin from CTFd.schemas.hints import HintSchema from CTFd.utils.decorators import ( during_ctf_time_only, @@ -75,6 +75,10 @@ class Hint(Resource): if unlocked: view = 'unlocked' + if is_admin(): + if request.args.get('preview', False): + view = 'admin' + response = HintSchema(view=view).dump(hint) if response.errors: diff --git a/CTFd/themes/admin/static/js/challenges/hints.js b/CTFd/themes/admin/static/js/challenges/hints.js index 361be5d6..e006ce2c 100644 --- a/CTFd/themes/admin/static/js/challenges/hints.js +++ b/CTFd/themes/admin/static/js/challenges/hints.js @@ -1,6 +1,55 @@ +function hint(id) { + return fetch(script_root + '/api/v1/hints/' + id + '?preview=true', { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }).then(function (response) { + return response.json(); + }); +} + +function loadhint(hintid) { + var md = window.markdownit({ + html: true, + }); + + hint(hintid).then(function (response) { + if (response.data.content) { + ezal({ + title: "Hint", + body: md.render(response.data.content), + button: "Got it!" + }); + } else { + ezal({ + title: "Error", + body: "Error loading hint!", + button: "OK" + }); + } + }); +} + $(document).ready(function () { $('#hint-add-button').click(function (e) { $('#hint-edit-modal form').find("input, textarea").val(""); + + // Markdown Preview + $('#new-hint-edit').on('shown.bs.tab', function (event) { + console.log(event.target.hash); + if (event.target.hash == '#hint-preview') { + console.log(event.target.hash); + var renderer = window.markdownit({ + html: true, + }); + var editor_value = $('#hint-write textarea').val(); + $(event.target.hash).html(renderer.render(editor_value)); + } + }); + $('#hint-edit-modal').modal(); }); @@ -26,7 +75,7 @@ $(document).ready(function () { e.preventDefault(); var hint_id = $(this).attr('hint-id'); - fetch(script_root + '/api/v1/hints/' + hint_id, { + fetch(script_root + '/api/v1/hints/' + hint_id + '?preview=true', { method: 'GET', credentials: 'same-origin', headers: { @@ -41,6 +90,19 @@ $(document).ready(function () { $('#hint-edit-form input[name=cost]').val(response.data.cost); $('#hint-edit-form input[name=id]').val(response.data.id); + // Markdown Preview + $('#new-hint-edit').on('shown.bs.tab', function (event) { + console.log(event.target.hash); + if (event.target.hash == '#hint-preview') { + console.log(event.target.hash); + var renderer = new markdownit({ + html: true, + }); + var editor_value = $('#hint-write textarea').val(); + $(event.target.hash).html(renderer.render(editor_value)); + } + }); + $('#hint-edit-modal').modal(); } }); diff --git a/CTFd/themes/admin/templates/challenges/challenge.html b/CTFd/themes/admin/templates/challenges/challenge.html index a590d942..8866ad3a 100644 --- a/CTFd/themes/admin/templates/challenges/challenge.html +++ b/CTFd/themes/admin/templates/challenges/challenge.html @@ -114,10 +114,12 @@ var CHALLENGE_ID = {{ challenge.id }}; var CHALLENGE_NAME = {{ challenge.name | tojson }}; + + diff --git a/tests/api/v1/test_hints.py b/tests/api/v1/test_hints.py index 4d5eadad..b21987f1 100644 --- a/tests/api/v1/test_hints.py +++ b/tests/api/v1/test_hints.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from CTFd.models import Hints from tests.helpers import * @@ -11,6 +12,7 @@ def test_api_hint_get_non_admin(): with login_as_user(app) as client: r = client.get('/api/v1/hints', json="") assert r.status_code == 403 + assert Hints.query.count() == 0 destroy_ctfd(app) @@ -45,4 +47,44 @@ def test_api_hint_post_admin(): "cost": "1", "challenge": 1}) assert r.status_code == 200 + assert Hints.query.count() == 1 + destroy_ctfd(app) + + +def test_admins_can_preview_hints(): + """Test that admins are able to bypass restrictions and preview hints with ?preview=true""" + app = create_ctfd() + with app.app_context(): + gen_challenge(app.db) + gen_hint(app.db, challenge_id=1, cost=100) + client = login_as_user(app, name="admin", password="password") + r = client.get('/api/v1/hints/1') + assert r.status_code == 200 + hint = r.get_json() + assert hint.get('content') is None + + r = client.get('/api/v1/hints/1?preview=true') + assert r.status_code == 200 + hint = r.get_json() + assert hint['data']['content'] == "This is a hint" + destroy_ctfd(app) + + +def test_users_cannot_preview_hints(): + """Test that users aren't able to preview hints""" + app = create_ctfd() + with app.app_context(): + gen_challenge(app.db) + gen_hint(app.db, challenge_id=1, cost=100) + register_user(app) + client = login_as_user(app) + r = client.get('/api/v1/hints/1') + assert r.status_code == 200 + hint = r.get_json() + assert hint.get('content') is None + + r = client.get('/api/v1/hints/1?preview=true') + assert r.status_code == 200 + hint = r.get_json() + assert hint['data'].get('content') is None destroy_ctfd(app)