Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to see their previous submissions #2315

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions CTFd/api/v1/challenges.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
get_solve_counts_for_challenges,
get_solve_ids_for_user_id,
get_solves_for_challenge_id,
get_submissions_for_user_id_for_challenge_id,
)
from CTFd.utils.config.visibility import (
accounts_visible,
Expand All @@ -34,6 +35,7 @@
from CTFd.utils.dates import ctf_ended, ctf_paused, ctftime
from CTFd.utils.decorators import (
admins_only,
authed_only,
during_ctf_time_only,
require_verified_emails,
)
Expand Down Expand Up @@ -727,6 +729,37 @@ def get(self, challenge_id):
return {"success": True, "data": response}


@challenges_namespace.route("/<challenge_id>/submissions")
class ChallengeSubmissions(Resource):
@authed_only
@check_challenge_visibility
@during_ctf_time_only
@require_verified_emails
def get(self, challenge_id):
response = []
challenge = Challenges.query.filter_by(id=challenge_id).first_or_404()

# TODO: Need a generic challenge visibility call.
# However, it should be stated that a submission on a gated challenge is not considered private.
if challenge.state == "hidden" and is_admin() is False:
abort(404)

freeze = get_config("freeze")
if freeze:
preview = request.args.get("preview")
if (is_admin() is False) or (is_admin() is True and preview):
freeze = True
elif is_admin() is True:
freeze = False

user = get_current_user()
response = get_submissions_for_user_id_for_challenge_id(
challenge_id=challenge_id, user_id=user.id
)

return {"success": True, "data": response}


@challenges_namespace.route("/<challenge_id>/files")
class ChallengeFiles(Resource):
@admins_only
Expand Down
2 changes: 2 additions & 0 deletions CTFd/cache/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,13 @@ def clear_standings():
def clear_challenges():
from CTFd.utils.challenges import get_all_challenges # noqa: I001
from CTFd.utils.challenges import get_solves_for_challenge_id
from CTFd.utils.challenges import get_submissions_for_user_id_for_challenge_id
from CTFd.utils.challenges import get_solve_ids_for_user_id
from CTFd.utils.challenges import get_solve_counts_for_challenges

cache.delete_memoized(get_all_challenges)
cache.delete_memoized(get_solves_for_challenge_id)
cache.delete_memoized(get_submissions_for_user_id_for_challenge_id)
cache.delete_memoized(get_solve_ids_for_user_id)
cache.delete_memoized(get_solve_counts_for_challenges)

Expand Down
2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/core.dev.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/challenge.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/challenges.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/configs.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/editor.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/main.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/notifications.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/pages.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/reset.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/scoreboard.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/statistics.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/submissions.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/team.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/teams.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/user.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CTFd/themes/admin/static/js/pages/users.min.js

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions CTFd/themes/core/assets/js/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,55 @@ let API = (function() {

return deferred.promise;
};
/**
*
* @method
* @name API#get_challenge_submissions
* @param {object} parameters - method options and parameters
* @param {string} parameters.id - A Challenge ID
* @param {string} parameters.challengeId -
*/
API.prototype.get_challenge_submissions = function(parameters) {
if (parameters === undefined) {
parameters = {};
}
let deferred = Q.defer();
let domain = this.domain,
path = "/challenges/{challenge_id}/submissions";
let body = {},
queryParameters = {},
headers = {},
form = {};

headers["Accept"] = ["application/json"];
headers["Content-Type"] = ["application/json"];

if (parameters["id"] !== undefined) {
queryParameters["id"] = parameters["id"];
}

path = path.replace("{challenge_id}", parameters["challengeId"]);

if (parameters["challengeId"] === undefined) {
deferred.reject(new Error("Missing required parameter: challengeId"));
return deferred.promise;
}

queryParameters = mergeQueryParams(parameters, queryParameters);

this.request(
"GET",
domain + path,
parameters,
body,
headers,
queryParameters,
form,
deferred
);

return deferred.promise;
};
/**
*
* @method
Expand Down
42 changes: 42 additions & 0 deletions CTFd/themes/core/assets/js/pages/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ const displayChal = chal => {
$(".challenge-solves").click(function(_event) {
getSolves($("#challenge-id").val());
});
$(".challenge-submissions").click(function(_event) {
getSubmissions($("#challenge-id").val());
});
$(".nav-tabs a").click(function(event) {
event.preventDefault();
$(this).tab("show");
Expand Down Expand Up @@ -276,6 +279,41 @@ function getSolves(id) {
});
}

function getSubmissions(id) {
return CTFd.api
.get_challenge_submissions({ challengeId: id })
.then(response => {
const data = response.data;
const box = $("#challenge-submissions-names");
box.empty();
for (let i = 0; i < data.length; i++) {
const user_name = data[i].user_name;
const provided = data[i].provided;
const date = dayjs(data[i].date).fromNow();
const type = data[i].type;
const user_url = data[i].user_url;
let tableline =
'<tr title="{5}"><td>{2}</td><td><i class="fas {4}" title="{3}"></i></td></tr>';
if (box.attr("ref") == "teams") {
tableline =
'<tr title="{5}"><td><a href="{0}">{1}</td><td>{2}</td><td><i class="fas {4}" title="{3}"></i></td></tr>';
}
box.append(
tableline.format(
user_url,
htmlEntities(user_name),
htmlEntities(provided),
type,
type === "incorrect"
? "fa-times text-danger"
: "fa-check text-success",
date
)
);
}
});
}

function loadChals() {
return CTFd.api.get_challenge_list().then(function(response) {
const categories = [];
Expand Down Expand Up @@ -388,6 +426,10 @@ $(() => {
getSolves($("#challenge-id").val());
});

$(".challenge-submissions").click(function(_event) {
getSubmissions($("#challenge-id").val());
});

$("#challenge-window").on("hide.bs.modal", function(_event) {
$("#challenge-input").removeClass("wrong");
$("#challenge-input").removeClass("correct");
Expand Down
2 changes: 1 addition & 1 deletion CTFd/themes/core/static/js/core.dev.js

Large diffs are not rendered by default.