mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 14:04:20 +01:00
Bootstrap beta 3, Regression fixes, bugfixes (#543)
* Upgrade to Bootstrap v4 beta 3 * Fix incorrect FontAwesome5 icon * Fixing regressions & code quality issues. Files, Tags & Hints now appear in the admin challenge preview. Fixed color issues with file buttons and badges. Pass script_root into challenge type plugin. * Fixing incorrect FontAwesome5 icon * Fix test for /admin/chals/<chalid> * Expand test to include tags, hints, files
This commit is contained in:
@@ -31,22 +31,31 @@ def admin_chal_types():
|
|||||||
@admins_only
|
@admins_only
|
||||||
def admin_chals():
|
def admin_chals():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
chals = Challenges.query.add_columns('id', 'type', 'name', 'value', 'description', 'category', 'hidden', 'max_attempts').order_by(Challenges.value).all()
|
chals = Challenges.query.order_by(Challenges.value).all()
|
||||||
|
|
||||||
json_data = {'game': []}
|
json_data = {'game': []}
|
||||||
for x in chals:
|
for chal in chals:
|
||||||
type_class = CHALLENGE_CLASSES.get(x.type)
|
tags = [tag.tag for tag in Tags.query.add_columns('tag').filter_by(chal=chal.id).all()]
|
||||||
|
files = [str(f.location) for f in Files.query.filter_by(chal=chal.id).all()]
|
||||||
|
hints = []
|
||||||
|
for hint in Hints.query.filter_by(chal=chal.id).all():
|
||||||
|
hints.append({'id': hint.id, 'cost': hint.cost, 'hint': hint.hint})
|
||||||
|
|
||||||
|
type_class = CHALLENGE_CLASSES.get(chal.type)
|
||||||
type_name = type_class.name if type_class else None
|
type_name = type_class.name if type_class else None
|
||||||
|
|
||||||
json_data['game'].append({
|
json_data['game'].append({
|
||||||
'id': x.id,
|
'id': chal.id,
|
||||||
'name': x.name,
|
'name': chal.name,
|
||||||
'value': x.value,
|
'value': chal.value,
|
||||||
'description': x.description,
|
'description': chal.description,
|
||||||
'category': x.category,
|
'category': chal.category,
|
||||||
'hidden': x.hidden,
|
'files': files,
|
||||||
'max_attempts': x.max_attempts,
|
'tags': tags,
|
||||||
'type': x.type,
|
'hints': hints,
|
||||||
|
'hidden': chal.hidden,
|
||||||
|
'max_attempts': chal.max_attempts,
|
||||||
|
'type': chal.type,
|
||||||
'type_name': type_name,
|
'type_name': type_name,
|
||||||
'type_data': {
|
'type_data': {
|
||||||
'id': type_class.id,
|
'id': type_class.id,
|
||||||
@@ -77,6 +86,17 @@ def admin_chal_detail(chalid):
|
|||||||
return jsonify({'status': 0, 'message': message})
|
return jsonify({'status': 0, 'message': message})
|
||||||
elif request.method == 'GET':
|
elif request.method == 'GET':
|
||||||
obj, data = chal_class.read(chal)
|
obj, data = chal_class.read(chal)
|
||||||
|
|
||||||
|
tags = [tag.tag for tag in Tags.query.add_columns('tag').filter_by(chal=chal.id).all()]
|
||||||
|
files = [str(f.location) for f in Files.query.filter_by(chal=chal.id).all()]
|
||||||
|
hints = []
|
||||||
|
for hint in Hints.query.filter_by(chal=chal.id).all():
|
||||||
|
hints.append({'id': hint.id, 'cost': hint.cost, 'hint': hint.hint})
|
||||||
|
|
||||||
|
data['tags'] = tags
|
||||||
|
data['files'] = files
|
||||||
|
data['hints'] = hints
|
||||||
|
|
||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<div class="row chal-files text-center pb-3">
|
<div class="row chal-files text-center pb-3">
|
||||||
{% for file in files %}
|
{% for file in files %}
|
||||||
<div class='col-md-4 col-sm-4 col-xs-12 file-button-wrapper d-block'>
|
<div class='col-md-4 col-sm-4 col-xs-12 file-button-wrapper d-block'>
|
||||||
<a class='btn btn-info btn-file mb-1 d-inline-block px-2 w-100 text-truncate' href='files/{{file}}'>
|
<a class='btn btn-info btn-file mb-1 d-inline-block px-2 w-100 text-truncate' href='{{script_root}}/files/{{file}}'>
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
<small>
|
<small>
|
||||||
{{ file.split('/')[1] }}
|
{{ file.split('/')[1] }}
|
||||||
|
|||||||
@@ -106,6 +106,11 @@ pre {
|
|||||||
|
|
||||||
.btn-info {
|
.btn-info {
|
||||||
background-color: #5B7290 !important;
|
background-color: #5B7290 !important;
|
||||||
|
border-color: #5B7290 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info {
|
||||||
|
background-color: #5B7290 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.chal-desc {
|
.chal-desc {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
@@ -24,7 +29,3 @@
|
|||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.key-submit .btn {
|
|
||||||
height: 51px;
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -27,18 +27,19 @@ function get_challenge(id){
|
|||||||
|
|
||||||
|
|
||||||
function load_challenge_preview(id){
|
function load_challenge_preview(id){
|
||||||
|
loadchal(id, function(){
|
||||||
var chal = get_challenge(id);
|
var chal = get_challenge(id);
|
||||||
var modal_template = chal.type_data.templates.modal;
|
var modal_template = chal.type_data.templates.modal;
|
||||||
var modal_script = chal.type_data.scripts.modal;
|
var modal_script = chal.type_data.scripts.modal;
|
||||||
|
|
||||||
render_challenge_preview(chal, modal_template, modal_script)
|
render_challenge_preview(chal, modal_template, modal_script);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_challenge_preview(chal, modal_template, modal_script){
|
function render_challenge_preview(chal, modal_template, modal_script){
|
||||||
var preview_window = $('#challenge-preview');
|
var preview_window = $('#challenge-preview');
|
||||||
$.get(script_root + modal_template, function (template_data) {
|
$.get(script_root + modal_template, function (template_data) {
|
||||||
preview_window.empty();
|
preview_window.empty();
|
||||||
console.log(chal.description);
|
|
||||||
var template = nunjucks.compile(template_data);
|
var template = nunjucks.compile(template_data);
|
||||||
var data = {
|
var data = {
|
||||||
id: chal.id,
|
id: chal.id,
|
||||||
@@ -47,7 +48,8 @@ function render_challenge_preview(chal, modal_template, modal_script){
|
|||||||
tags: chal.tags,
|
tags: chal.tags,
|
||||||
desc: chal.description,
|
desc: chal.description,
|
||||||
files: chal.files,
|
files: chal.files,
|
||||||
hints: chal.hints
|
hints: chal.hints,
|
||||||
|
script_root: script_root
|
||||||
};
|
};
|
||||||
|
|
||||||
var challenge = template.render(data);
|
var challenge = template.render(data);
|
||||||
@@ -61,6 +63,24 @@ function render_challenge_preview(chal, modal_template, modal_script){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadchal(chalid, cb){
|
||||||
|
$.get(script_root + "/admin/chal/"+chalid, {
|
||||||
|
}, function (data) {
|
||||||
|
var categories = [];
|
||||||
|
var challenge = $.parseJSON(JSON.stringify(data));
|
||||||
|
|
||||||
|
for (var i = challenges['game'].length - 1; i >= 0; i--) {
|
||||||
|
if (challenges['game'][i]['id'] == challenge.id) {
|
||||||
|
challenges['game'][i] = challenge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function loadchals(cb){
|
function loadchals(cb){
|
||||||
$.post(script_root + "/admin/chals", {
|
$.post(script_root + "/admin/chals", {
|
||||||
'nonce': $('#nonce').val()
|
'nonce': $('#nonce').val()
|
||||||
@@ -68,7 +88,6 @@ function loadchals(cb){
|
|||||||
var categories = [];
|
var categories = [];
|
||||||
challenges = $.parseJSON(JSON.stringify(data));
|
challenges = $.parseJSON(JSON.stringify(data));
|
||||||
|
|
||||||
|
|
||||||
for (var i = challenges['game'].length - 1; i >= 0; i--) {
|
for (var i = challenges['game'].length - 1; i >= 0; i--) {
|
||||||
if ($.inArray(challenges['game'][i].category, categories) == -1) {
|
if ($.inArray(challenges['game'][i].category, categories) == -1) {
|
||||||
categories.push(challenges['game'][i].category)
|
categories.push(challenges['game'][i].category)
|
||||||
@@ -90,6 +109,29 @@ loadchals(function(){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function loadhint(hintid) {
|
||||||
|
ezq({
|
||||||
|
title: "Unlock Hint?",
|
||||||
|
body: "Are you sure you want to open this hint?",
|
||||||
|
success: function () {
|
||||||
|
$.post(script_root + "/hints/" + hintid, {'nonce': $('#nonce').val()}, function (data) {
|
||||||
|
if (data.errors) {
|
||||||
|
ezal({
|
||||||
|
title: "Error!",
|
||||||
|
body: data.errors,
|
||||||
|
button: "Okay"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ezal({
|
||||||
|
title: "Hint",
|
||||||
|
body: marked(data.hint, {'gfm': true, 'breaks': true}),
|
||||||
|
button: "Got it!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function submitkey(chal, key, nonce){
|
function submitkey(chal, key, nonce){
|
||||||
$.post(script_root + "/admin/chal/" + chal, {
|
$.post(script_root + "/admin/chal/" + chal, {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
function updatefiles(){
|
function updatefiles(){
|
||||||
chal = $('#files-chal').val();
|
var chal = $('#files-chal').val();
|
||||||
var form = $('#update-files form')[0];
|
var form = $('#update-files form')[0];
|
||||||
var formData = new FormData(form);
|
var formData = new FormData(form);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -20,12 +20,12 @@ function loadfiles(chal, cb){
|
|||||||
$('#update-files form').attr('action', script_root+'/admin/files/'+chal)
|
$('#update-files form').attr('action', script_root+'/admin/files/'+chal)
|
||||||
$.get(script_root + '/admin/files/' + chal, function(data){
|
$.get(script_root + '/admin/files/' + chal, function(data){
|
||||||
$('#files-chal').val(chal);
|
$('#files-chal').val(chal);
|
||||||
files = $.parseJSON(JSON.stringify(data));
|
var files = $.parseJSON(JSON.stringify(data));
|
||||||
files = files['files'];
|
var files = files['files'];
|
||||||
$('#current-files').empty();
|
$('#current-files').empty();
|
||||||
for(x=0; x<files.length; x++){
|
for(var x = 0; x < files.length; x++){
|
||||||
filename = files[x].file.split('/');
|
var filename = files[x].file.split('/');
|
||||||
filename = filename[filename.length - 1];
|
var filename = filename[filename.length - 1];
|
||||||
|
|
||||||
var curr_file = '<div class="col-md-12"><a href="{2}/files/{3}">{4}</a> <i class="btn-fa fas fa-times float-right" onclick="deletefile({0}, {1}, $(this))" value="{2}" ></i></div>'.format(
|
var curr_file = '<div class="col-md-12"><a href="{2}/files/{3}">{4}</a> <i class="btn-fa fas fa-times float-right" onclick="deletefile({0}, {1}, $(this))" value="{2}" ></i></div>'.format(
|
||||||
chal,
|
chal,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,7 @@
|
|||||||
{% extends "admin/base.html" %}
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
{% block stylesheets %}
|
{% block stylesheets %}
|
||||||
<style>
|
<link rel="stylesheet" href="{{ request.script_root }}/themes/admin/static/css/challenge-board.css">
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
@@ -97,13 +92,13 @@
|
|||||||
<span> </span>
|
<span> </span>
|
||||||
|
|
||||||
<i class="btn-fa fas fa-flag edit-keys" aria-hidden="true" data-toggle="tooltip" data-placement="top"
|
<i class="btn-fa fas fa-flag edit-keys" aria-hidden="true" data-toggle="tooltip" data-placement="top"
|
||||||
title="Edit Flags" chal-id="{{ challenge.id }}"></i>
|
title="Edit {{ challenge.name }} flags" chal-id="{{ challenge.id }}"></i>
|
||||||
<i class="btn-fa fas fa-download edit-files" aria-hidden="true" data-toggle="tooltip"
|
<i class="btn-fa fas fa-download edit-files" aria-hidden="true" data-toggle="tooltip"
|
||||||
data-placement="top" title="Edit Files" chal-id="{{ challenge.id }}"></i>
|
data-placement="top" title="Edit {{ challenge.name }} files" chal-id="{{ challenge.id }}"></i>
|
||||||
<i class="btn-fa fas fa-tags edit-tags" aria-hidden="true" data-toggle="tooltip" data-placement="top"
|
<i class="btn-fa fas fa-tags edit-tags" aria-hidden="true" data-toggle="tooltip" data-placement="top"
|
||||||
title="Edit Tags" chal-id="{{ challenge.id }}"></i>
|
title="Edit {{ challenge.name }} tags" chal-id="{{ challenge.id }}"></i>
|
||||||
<i class="btn-fa fas fa-question-circle edit-hints" aria-hidden="true" data-toggle="tooltip"
|
<i class="btn-fa fas fa-question-circle edit-hints" aria-hidden="true" data-toggle="tooltip"
|
||||||
data-placement="top" title="Edit Hints" chal-id="{{ challenge.id }}"></i>
|
data-placement="top" title="Edit {{ challenge.name }} hints" chal-id="{{ challenge.id }}"></i>
|
||||||
|
|
||||||
<span> </span>
|
<span> </span>
|
||||||
|
|
||||||
|
|||||||
@@ -225,7 +225,7 @@
|
|||||||
'svg': 'fa-file-image',
|
'svg': 'fa-file-image',
|
||||||
|
|
||||||
// Text Files
|
// Text Files
|
||||||
'txt': 'fa-file-text',
|
'txt': 'fa-file-alt',
|
||||||
|
|
||||||
// Video Files
|
// Video Files
|
||||||
'mov': 'fa-file-video',
|
'mov': 'fa-file-video',
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
link.attr('href', '##');
|
link.attr('href', '##');
|
||||||
|
|
||||||
if (mapping[ext] == undefined)
|
if (mapping[ext] == undefined)
|
||||||
link.append('<i class="far fa-file-o" aria-hidden="true"></i> '.format(mapping[ext]));
|
link.append('<i class="far fa-file" aria-hidden="true"></i> '.format(mapping[ext]));
|
||||||
else
|
else
|
||||||
link.append('<i class="far {0}" aria-hidden="true"></i> '.format(mapping[ext]));
|
link.append('<i class="far {0}" aria-hidden="true"></i> '.format(mapping[ext]));
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ table > thead > tr > td {
|
|||||||
border-color: #5B7290 !important;
|
border-color: #5B7290 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-info {
|
||||||
|
background-color: #5B7290 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
padding: 0.8em;
|
padding: 0.8em;
|
||||||
|
|||||||
@@ -19,16 +19,6 @@
|
|||||||
background-color: #5B7290 !important;
|
background-color: #5B7290 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-file:nth-child(1):before {
|
|
||||||
/*content: "\f0ed";*/
|
|
||||||
/*display: inline-block;*/
|
|
||||||
/*font: normal normal normal 14px/1 FontAwesome;*/
|
|
||||||
/*font-size: inherit;*/
|
|
||||||
/*text-rendering: auto;*/
|
|
||||||
/*-webkit-font-smoothing: antialiased;*/
|
|
||||||
/*-moz-osx-font-smoothing: grayscale;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.challenge-button {
|
.challenge-button {
|
||||||
box-shadow: 3px 3px 3px grey;
|
box-shadow: 3px 3px 3px grey;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -21,9 +21,6 @@ function loadchalbyname(chalname) {
|
|||||||
function updateChalWindow(obj) {
|
function updateChalWindow(obj) {
|
||||||
$.get(script_root + obj.template, function(template_data){
|
$.get(script_root + obj.template, function(template_data){
|
||||||
$('#chal-window').empty();
|
$('#chal-window').empty();
|
||||||
templates[obj.type] = template_data;
|
|
||||||
var template_data = templates[obj.type];
|
|
||||||
template_data['script_root'] = script_root;
|
|
||||||
var template = nunjucks.compile(template_data);
|
var template = nunjucks.compile(template_data);
|
||||||
var solves = obj.solves == 1 ? " Solve" : " Solves";
|
var solves = obj.solves == 1 ? " Solve" : " Solves";
|
||||||
var solves = obj.solves + solves;
|
var solves = obj.solves + solves;
|
||||||
@@ -37,7 +34,8 @@ function updateChalWindow(obj) {
|
|||||||
desc: obj.description,
|
desc: obj.description,
|
||||||
solves: solves,
|
solves: solves,
|
||||||
files: obj.files,
|
files: obj.files,
|
||||||
hints: obj.hints
|
hints: obj.hints,
|
||||||
|
script_root: script_root
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#chal-window').append(template.render(wrapper));
|
$('#chal-window').append(template.render(wrapper));
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -209,6 +209,10 @@ def test_admin_chal_detail_returns_proper_data():
|
|||||||
client = login_as_user(app, name="admin", password="password")
|
client = login_as_user(app, name="admin", password="password")
|
||||||
|
|
||||||
chal = gen_challenge(app.db)
|
chal = gen_challenge(app.db)
|
||||||
|
tag = gen_tag(app.db, chal.id, 'test-tag')
|
||||||
|
hint = gen_hint(app.db, chal.id, 'test-hint', 5)
|
||||||
|
f = gen_file(app.db, chal.id, '0bf1a55a5cd327c07af15df260979668/bird.swf')
|
||||||
|
|
||||||
chal_class = get_chal_class(chal.type)
|
chal_class = get_chal_class(chal.type)
|
||||||
data = {
|
data = {
|
||||||
'id': chal.id,
|
'id': chal.id,
|
||||||
@@ -216,6 +220,9 @@ def test_admin_chal_detail_returns_proper_data():
|
|||||||
'value': chal.value,
|
'value': chal.value,
|
||||||
'description': chal.description,
|
'description': chal.description,
|
||||||
'category': chal.category,
|
'category': chal.category,
|
||||||
|
'files': ['0bf1a55a5cd327c07af15df260979668/bird.swf'],
|
||||||
|
'tags': ['test-tag'],
|
||||||
|
'hints': [{'id': 1, 'cost': 5, 'hint': 'test-hint'}],
|
||||||
'hidden': chal.hidden,
|
'hidden': chal.hidden,
|
||||||
'max_attempts': chal.max_attempts,
|
'max_attempts': chal.max_attempts,
|
||||||
'type': chal.type,
|
'type': chal.type,
|
||||||
|
|||||||
@@ -110,8 +110,11 @@ def gen_tag(db, chal, tag='tag_tag'):
|
|||||||
return tag
|
return tag
|
||||||
|
|
||||||
|
|
||||||
def gen_file():
|
def gen_file(db, chal, location):
|
||||||
pass
|
f = Files(chal, location)
|
||||||
|
db.session.add(f)
|
||||||
|
db.session.commit()
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
def gen_flag(db, chal, flag='flag', key_type='static'):
|
def gen_flag(db, chal, flag='flag', key_type='static'):
|
||||||
|
|||||||
Reference in New Issue
Block a user