Merge pull request #2344 from CTFd/1691-challenge-preview-improvements

* Changes challenge previews to be in a full page context using `page.html`
* Closes #1691
This commit is contained in:
Kevin Chung
2023-06-27 19:03:22 -04:00
committed by GitHub
7 changed files with 272 additions and 189 deletions

View File

@@ -3,7 +3,10 @@ from flask import abort, render_template, request, url_for
from CTFd.admin import admin
from CTFd.models import Challenges, Flags, Solves
from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class
from CTFd.schemas.tags import TagSchema
from CTFd.utils.decorators import admins_only
from CTFd.utils.security.signing import serialize
from CTFd.utils.user import get_current_team, get_current_user
@admin.route("/admin/challenges")
@@ -71,6 +74,43 @@ def challenges_detail(challenge_id):
)
@admin.route("/admin/challenges/preview/<int:challenge_id>")
@admins_only
def challenges_preview(challenge_id):
challenge = Challenges.query.filter_by(id=challenge_id).first_or_404()
chal_class = get_chal_class(challenge.type)
user = get_current_user()
team = get_current_team()
files = []
for f in challenge.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)))
tags = [
tag["value"] for tag in TagSchema("user", many=True).dump(challenge.tags).data
]
content = render_template(
chal_class.templates["view"].lstrip("/"),
solves=None,
solved_by_me=False,
files=files,
tags=tags,
hints=challenge.hints,
max_attempts=challenge.max_attempts,
attempts=0,
challenge=challenge,
)
return render_template(
"admin/challenges/preview.html", content=content, challenge=challenge
)
@admin.route("/admin/challenges/new")
@admins_only
def challenges_new():

View File

@@ -16,111 +16,6 @@ import TagsList from "../components/tags/TagsList.vue";
import ChallengeFilesList from "../components/files/ChallengeFilesList.vue";
import HintsList from "../components/hints/HintsList.vue";
import NextChallenge from "../components/next/NextChallenge.vue";
import hljs from "highlight.js";
const displayHint = data => {
ezAlert({
title: "Hint",
body: data.html,
button: "Got it!"
});
};
const loadHint = id => {
CTFd.api.get_hint({ hintId: id, preview: true }).then(response => {
if (response.data.content) {
displayHint(response.data);
return;
}
// displayUnlock(id);
});
};
function renderSubmissionResponse(response, cb) {
var result = response.data;
var result_message = $("#result-message");
var result_notification = $("#result-notification");
var answer_input = $("#submission-input");
result_notification.removeClass();
result_message.text(result.message);
if (result.status === "authentication_required") {
window.location =
CTFd.config.urlRoot +
"/login?next=" +
CTFd.config.urlRoot +
window.location.pathname +
window.location.hash;
return;
} else if (result.status === "incorrect") {
// 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 === "correct") {
// Challenge Solved
result_notification.addClass(
"alert alert-success alert-dismissable text-center"
);
result_notification.slideDown();
$(".challenge-solves").text(
parseInt(
$(".challenge-solves")
.text()
.split(" ")[0]
) +
1 +
" Solves"
);
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
} else if (result.status === "already_solved") {
// Challenge already solved
result_notification.addClass(
"alert alert-info alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("correct");
} else if (result.status === "paused") {
// CTF is paused
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
} else if (result.status === "ratelimited") {
// 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();
$("#challenge-submit").removeClass("disabled-button");
$("#challenge-submit").prop("disabled", false);
}, 3000);
if (cb) {
cb(result);
}
}
function loadChalTemplate(challenge) {
CTFd._internal.challenge = {};
@@ -244,87 +139,13 @@ function handleChallengeOptions(event) {
$(() => {
$(".preview-challenge").click(function(_e) {
CTFd._internal.challenge = {};
$.get(
CTFd.config.urlRoot + "/api/v1/challenges/" + window.CHALLENGE_ID,
function(response) {
// Preview should not show any solves
var challenge_data = response.data;
challenge_data["solves"] = null;
$.getScript(
CTFd.config.urlRoot + challenge_data.type_data.scripts.view,
function() {
const challenge = CTFd._internal.challenge;
// Inject challenge data into the plugin
challenge.data = response.data;
$("#challenge-window").empty();
// Call preRender function in plugin
challenge.preRender();
$("#challenge-window").append(challenge_data.view);
$("#challenge-window #challenge-input").addClass("form-control");
$("#challenge-window #challenge-submit").addClass(
"btn btn-md btn-outline-secondary float-right"
);
$(".challenge-solves").hide();
$(".nav-tabs a").click(function(e) {
e.preventDefault();
$(this).tab("show");
});
// Handle modal toggling
$("#challenge-window").on("hide.bs.modal", function(_event) {
$("#challenge-input").removeClass("wrong");
$("#challenge-input").removeClass("correct");
$("#incorrect-key").slideUp();
$("#correct-key").slideUp();
$("#already-solved").slideUp();
$("#too-fast").slideUp();
});
$(".load-hint").on("click", function(_event) {
loadHint($(this).data("hint-id"));
});
$("#challenge-submit").click(function(e) {
e.preventDefault();
$("#challenge-submit").addClass("disabled-button");
$("#challenge-submit").prop("disabled", true);
CTFd._internal.challenge
.submit(true)
.then(renderSubmissionResponse);
// Preview passed as true
});
$("#challenge-input").keyup(function(event) {
if (event.keyCode == 13) {
$("#challenge-submit").click();
}
});
challenge.postRender();
$("#challenge-window")
.find("pre code")
.each(function(_idx) {
hljs.highlightBlock(this);
});
window.location.replace(
window.location.href.split("#")[0] + "#preview"
);
$("#challenge-window").modal();
}
);
}
let url = `${CTFd.config.urlRoot}/admin/challenges/preview/${
window.CHALLENGE_ID
}`;
$("#challenge-window").html(
`<iframe src="${url}" height="100%" width="100%" frameBorder=0></iframe>`
);
$("#challenge-modal").modal();
});
$(".comments-challenge").click(function(_event) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,24 @@
{% endblock %}
{% block content %}
<div class="modal fade" id="challenge-window" role="dialog">
<div class="modal fade" id="challenge-modal" role="dialog">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h3 class="text-center w-100 p-0 m-0">Challenge Preview</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="challenge-window" style="height: 80vh;">
</div>
<div class="text-center">
<h5>
Preview may be slightly different from the user-facing interface
</h5>
</div>
</div>
</div>
</div>
<div class="modal fade" id="challenge-comments-window" role="dialog">

View File

@@ -0,0 +1,205 @@
{% extends "page.html" %}
{% block scripts %}
<script>
var CHALLENGE_ID = {{ challenge.id }};
</script>
{% if "beta" in Configs.ctf_theme %}
{{ Assets.js("assets/js/challenges.js") }}
<script type="module">
CTFd.config.preview = true;
CTFd.pages.challenge.displayChallenge(CHALLENGE_ID);
</script>
{% else %}
{# TODO: Remove this in CTFd 4.0 when old themes have been deprecated #}
<script type="module">
const displayHint = data => {
alert(data.html);
};
const loadHint = id => {
CTFd.api.get_hint({ hintId: id, preview: true }).then(response => {
if (response.data.content) {
displayHint(response.data);
return;
}
});
};
function renderSubmissionResponse(response) {
const result = response.data;
const result_message = $("#result-message");
const result_notification = $("#result-notification");
const answer_input = $("#challenge-input");
result_notification.removeClass();
result_message.text(result.message);
const next_btn = $(
`<div class='col-md-12 pb-3'><button class='btn btn-info w-100'>Next Challenge</button></div>`
).click(function () {
$("#challenge-window").modal("toggle");
setTimeout(function () {
loadChal(CTFd._internal.challenge.data.next_id);
}, 500);
});
if (result.status === "authentication_required") {
window.location =
CTFd.config.urlRoot +
"/login?next=" +
CTFd.config.urlRoot +
window.location.pathname +
window.location.hash;
return;
} else if (result.status === "incorrect") {
// 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 === "correct") {
// Challenge Solved
result_notification.addClass(
"alert alert-success alert-dismissable text-center"
);
result_notification.slideDown();
if (
$(".challenge-solves")
.text()
.trim()
) {
// Only try to increment solves if the text isn't hidden
$(".challenge-solves").text(
parseInt(
$(".challenge-solves")
.text()
.split(" ")[0]
) +
1 +
" Solves"
);
}
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
if (CTFd._internal.challenge.data.next_id) {
$(".submit-row").html(next_btn);
}
} else if (result.status === "already_solved") {
// Challenge already solved
result_notification.addClass(
"alert alert-info alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("correct");
if (CTFd._internal.challenge.data.next_id) {
$(".submit-row").html(next_btn);
}
} else if (result.status === "paused") {
// CTF is paused
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
} else if (result.status === "ratelimited") {
// 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();
$("#challenge-submit").removeClass("disabled-button");
$("#challenge-submit").prop("disabled", false);
}, 3000);
}
CTFd._internal.challenge = {};
$.get(
CTFd.config.urlRoot + "/api/v1/challenges/" + window.CHALLENGE_ID,
function (response) {
// Preview should not show any solves
var challenge_data = response.data;
challenge_data["solves"] = null;
$.getScript(
CTFd.config.urlRoot + challenge_data.type_data.scripts.view,
function () {
const challenge = CTFd._internal.challenge;
// Inject challenge data into the plugin
challenge.data = response.data;
challenge.preRender();
$("#challenge-input").addClass("form-control");
$("#challenge-submit").addClass(
"btn btn-md btn-outline-secondary float-right"
);
$(".challenge-solves").hide();
$(".nav-tabs a").click(function (e) {
e.preventDefault();
$(this).tab("show");
});
// Handle modal toggling
$("#challenge-window").on("hide.bs.modal", function (_event) {
$("#challenge-input").removeClass("wrong");
$("#challenge-input").removeClass("correct");
$("#incorrect-key").slideUp();
$("#correct-key").slideUp();
$("#already-solved").slideUp();
$("#too-fast").slideUp();
});
$(".load-hint").on("click", function (_event) {
loadHint($(this).data("hint-id"));
});
$("#challenge-submit").click(function (e) {
e.preventDefault();
$("#challenge-submit").addClass("disabled-button");
$("#challenge-submit").prop("disabled", true);
CTFd._internal.challenge
.submit(true)
.then(renderSubmissionResponse);
// Preview passed as true
});
$("#challenge-input").keyup(function (event) {
if (event.keyCode == 13) {
$("#challenge-submit").click();
}
});
challenge.postRender();
$("pre code")
.each(function (_idx) {
hljs.highlightBlock(this);
});
}
);
}
);
</script>
{% endif %}
{% endblock %}