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)