mirror of https://github.com/JohnHammond/CTFd.git
1.2.0 (#627)
* Changing to a new plugin oriented challenge type plugin and fixing extra width on admin chal description * Add window.challenge.submit, renderSubmissionResponse, and csrf_nonce * Update admin side renderer calls * Updating to Flask 1.0 and adding files for flask run * Adding a preliminary case-insensitive key * Adding case insensitive keys * Adding CTF Logo * Reducing the amount of team information shown on the main page * Add better base64 helpers * Switch from button to badge * Rudimentary solve checking from admin panel * Refine admin chals solves view & fix PEP8 * Compare base64 encoded data with bytestring * Removing need to urlencode/urldecode in base64 wrappers * Adding decorator documentation * Randomly order tests & add test for case_insensitive flags * Add regex flag case_insensitive test * Add tests for /admin/chal/1/solves and ctf_logoselenium-screenshot-testing
parent
9c812ad52e
commit
36c83b59bc
|
@ -15,6 +15,6 @@ before_script:
|
||||||
- psql -c 'create database ctfd;' -U postgres
|
- psql -c 'create database ctfd;' -U postgres
|
||||||
script:
|
script:
|
||||||
- pep8 --ignore E501,E712 CTFd/ tests/
|
- pep8 --ignore E501,E712 CTFd/ tests/
|
||||||
- nosetests -d
|
- nosetests -v -d --with-randomly
|
||||||
after_success:
|
after_success:
|
||||||
- codecov
|
- codecov
|
||||||
|
|
|
@ -147,6 +147,13 @@ def admin_config():
|
||||||
utils.set_config("mail_username", None)
|
utils.set_config("mail_username", None)
|
||||||
utils.set_config("mail_password", None)
|
utils.set_config("mail_password", None)
|
||||||
|
|
||||||
|
if request.files.get('ctf_logo', None):
|
||||||
|
ctf_logo = request.files['ctf_logo']
|
||||||
|
file_id, file_loc = utils.upload_file(ctf_logo, None)
|
||||||
|
utils.set_config("ctf_logo", file_loc)
|
||||||
|
elif request.form.get('ctf_logo') == '':
|
||||||
|
utils.set_config("ctf_logo", None)
|
||||||
|
|
||||||
utils.set_config("ctf_name", request.form.get('ctf_name', None))
|
utils.set_config("ctf_name", request.form.get('ctf_name', None))
|
||||||
utils.set_config("ctf_theme", request.form.get('ctf_theme', None))
|
utils.set_config("ctf_theme", request.form.get('ctf_theme', None))
|
||||||
utils.set_config('css', request.form.get('css', None))
|
utils.set_config('css', request.form.get('css', None))
|
||||||
|
@ -176,6 +183,7 @@ def admin_config():
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
||||||
ctf_name = utils.get_config('ctf_name')
|
ctf_name = utils.get_config('ctf_name')
|
||||||
|
ctf_logo = utils.get_config('ctf_logo')
|
||||||
ctf_theme = utils.get_config('ctf_theme')
|
ctf_theme = utils.get_config('ctf_theme')
|
||||||
hide_scores = utils.get_config('hide_scores')
|
hide_scores = utils.get_config('hide_scores')
|
||||||
css = utils.get_config('css')
|
css = utils.get_config('css')
|
||||||
|
@ -216,6 +224,7 @@ def admin_config():
|
||||||
return render_template(
|
return render_template(
|
||||||
'admin/config.html',
|
'admin/config.html',
|
||||||
ctf_name=ctf_name,
|
ctf_name=ctf_name,
|
||||||
|
ctf_logo=ctf_logo,
|
||||||
ctf_theme_config=ctf_theme,
|
ctf_theme_config=ctf_theme,
|
||||||
css=css,
|
css=css,
|
||||||
start=start,
|
start=start,
|
||||||
|
|
|
@ -100,6 +100,19 @@ def admin_chal_detail(chalid):
|
||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_challenges.route('/admin/chal/<int:chalid>/solves', methods=['GET'])
|
||||||
|
@admins_only
|
||||||
|
def admin_chal_solves(chalid):
|
||||||
|
response = {'teams': []}
|
||||||
|
if utils.hide_scores():
|
||||||
|
return jsonify(response)
|
||||||
|
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid).order_by(
|
||||||
|
Solves.date.asc())
|
||||||
|
for solve in solves:
|
||||||
|
response['teams'].append({'id': solve.team.id, 'name': solve.team.name, 'date': solve.date})
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
|
||||||
@admin_challenges.route('/admin/tags/<int:chalid>', methods=['GET', 'POST'])
|
@admin_challenges.route('/admin/tags/<int:chalid>', methods=['GET', 'POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_tags(chalid):
|
def admin_tags(chalid):
|
||||||
|
|
|
@ -57,6 +57,10 @@ def admin_create_team():
|
||||||
affiliation = request.form.get('affiliation', None)
|
affiliation = request.form.get('affiliation', None)
|
||||||
country = request.form.get('country', None)
|
country = request.form.get('country', None)
|
||||||
|
|
||||||
|
admin_user = True if request.form.get('admin', None) == 'on' else False
|
||||||
|
verified = True if request.form.get('verified', None) == 'on' else False
|
||||||
|
hidden = True if request.form.get('hidden', None) == 'on' else False
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
if not name:
|
if not name:
|
||||||
|
@ -92,6 +96,10 @@ def admin_create_team():
|
||||||
team.affiliation = affiliation
|
team.affiliation = affiliation
|
||||||
team.country = country
|
team.country = country
|
||||||
|
|
||||||
|
team.admin = admin_user
|
||||||
|
team.verified = verified
|
||||||
|
team.hidden = hidden
|
||||||
|
|
||||||
db.session.add(team)
|
db.session.add(team)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
@ -119,24 +127,6 @@ def admin_team(teamid):
|
||||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
||||||
place=place, wrong_keys=wrong_keys, awards=awards)
|
place=place, wrong_keys=wrong_keys, awards=awards)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
admin_user = request.form.get('admin', None)
|
|
||||||
if admin_user:
|
|
||||||
admin_user = True if admin_user == 'true' else False
|
|
||||||
user.admin = admin_user
|
|
||||||
# Set user.banned to hide admins from scoreboard
|
|
||||||
user.banned = admin_user
|
|
||||||
db.session.commit()
|
|
||||||
db.session.close()
|
|
||||||
return jsonify({'data': ['success']})
|
|
||||||
|
|
||||||
verified = request.form.get('verified', None)
|
|
||||||
if verified:
|
|
||||||
verified = True if verified == 'true' else False
|
|
||||||
user.verified = verified
|
|
||||||
db.session.commit()
|
|
||||||
db.session.close()
|
|
||||||
return jsonify({'data': ['success']})
|
|
||||||
|
|
||||||
name = request.form.get('name', None)
|
name = request.form.get('name', None)
|
||||||
password = request.form.get('password', None)
|
password = request.form.get('password', None)
|
||||||
email = request.form.get('email', None)
|
email = request.form.get('email', None)
|
||||||
|
@ -144,6 +134,10 @@ def admin_team(teamid):
|
||||||
affiliation = request.form.get('affiliation', None)
|
affiliation = request.form.get('affiliation', None)
|
||||||
country = request.form.get('country', None)
|
country = request.form.get('country', None)
|
||||||
|
|
||||||
|
admin_user = True if request.form.get('admin', None) == 'on' else False
|
||||||
|
verified = True if request.form.get('verified', None) == 'on' else False
|
||||||
|
hidden = True if request.form.get('hidden', None) == 'on' else False
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
if email:
|
if email:
|
||||||
|
@ -177,6 +171,9 @@ def admin_team(teamid):
|
||||||
user.website = website
|
user.website = website
|
||||||
user.affiliation = affiliation
|
user.affiliation = affiliation
|
||||||
user.country = country
|
user.country = country
|
||||||
|
user.admin = admin_user
|
||||||
|
user.verified = verified
|
||||||
|
user.banned = hidden
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return jsonify({'data': ['success']})
|
return jsonify({'data': ['success']})
|
||||||
|
|
|
@ -29,7 +29,7 @@ def confirm_user(data=None):
|
||||||
if data and request.method == "GET":
|
if data and request.method == "GET":
|
||||||
try:
|
try:
|
||||||
s = TimedSerializer(app.config['SECRET_KEY'])
|
s = TimedSerializer(app.config['SECRET_KEY'])
|
||||||
email = s.loads(utils.base64decode(data, urldecode=True), max_age=1800)
|
email = s.loads(utils.base64decode(data), max_age=1800)
|
||||||
except BadTimeSignature:
|
except BadTimeSignature:
|
||||||
return render_template('confirm.html', errors=['Your confirmation link has expired'])
|
return render_template('confirm.html', errors=['Your confirmation link has expired'])
|
||||||
except (BadSignature, TypeError, base64.binascii.Error):
|
except (BadSignature, TypeError, base64.binascii.Error):
|
||||||
|
@ -86,7 +86,7 @@ def reset_password(data=None):
|
||||||
if data is not None:
|
if data is not None:
|
||||||
try:
|
try:
|
||||||
s = TimedSerializer(app.config['SECRET_KEY'])
|
s = TimedSerializer(app.config['SECRET_KEY'])
|
||||||
name = s.loads(utils.base64decode(data, urldecode=True), max_age=1800)
|
name = s.loads(utils.base64decode(data), max_age=1800)
|
||||||
except BadTimeSignature:
|
except BadTimeSignature:
|
||||||
return render_template('reset_password.html', errors=['Your link has expired'])
|
return render_template('reset_password.html', errors=['Your link has expired'])
|
||||||
except (BadSignature, TypeError, base64.binascii.Error):
|
except (BadSignature, TypeError, base64.binascii.Error):
|
||||||
|
|
|
@ -146,7 +146,7 @@ class CTFdStandardChallenge(BaseChallenge):
|
||||||
provided_key = request.form['key'].strip()
|
provided_key = request.form['key'].strip()
|
||||||
chal_keys = Keys.query.filter_by(chal=chal.id).all()
|
chal_keys = Keys.query.filter_by(chal=chal.id).all()
|
||||||
for chal_key in chal_keys:
|
for chal_key in chal_keys:
|
||||||
if get_key_class(chal_key.type).compare(chal_key.flag, provided_key):
|
if get_key_class(chal_key.type).compare(chal_key, provided_key):
|
||||||
return True, 'Correct'
|
return True, 'Correct'
|
||||||
return False, 'Incorrect'
|
return False, 'Incorrect'
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
// Markdown Preview
|
// Markdown Preview
|
||||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
$('#desc-edit').on('shown.bs.tab', function (event) {
|
||||||
var md = window.markdownit({
|
|
||||||
html: true,
|
|
||||||
});
|
|
||||||
if (event.target.hash == '#desc-preview') {
|
if (event.target.hash == '#desc-preview') {
|
||||||
var editor_value = $('#desc-editor').val();
|
var editor_value = $('#desc-editor').val();
|
||||||
$(event.target.hash).html(
|
$(event.target.hash).html(
|
||||||
md.render(editor_value)
|
window.challenge.render(editor_value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||||
var md = window.markdownit({
|
|
||||||
html: true,
|
|
||||||
});
|
|
||||||
if (event.target.hash == '#new-desc-preview') {
|
if (event.target.hash == '#new-desc-preview') {
|
||||||
var editor_value = $('#new-desc-editor').val();
|
var editor_value = $('#new-desc-editor').val();
|
||||||
$(event.target.hash).html(
|
$(event.target.hash).html(
|
||||||
md.render(editor_value)
|
window.challenge.render(editor_value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,25 +1,35 @@
|
||||||
$('#submit-key').unbind('click');
|
window.challenge.renderer = new markdownit({
|
||||||
$('#submit-key').click(function (e) {
|
html: true,
|
||||||
e.preventDefault();
|
|
||||||
submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val())
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#answer-input").keyup(function(event){
|
window.challenge.preRender = function(){
|
||||||
if(event.keyCode == 13){
|
|
||||||
$("#submit-key").click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".input-field").bind({
|
};
|
||||||
focus: function() {
|
|
||||||
$(this).parent().addClass('input--filled' );
|
window.challenge.render = function(markdown){
|
||||||
$label = $(this).siblings(".input-label");
|
return window.challenge.renderer.render(markdown);
|
||||||
},
|
};
|
||||||
blur: function() {
|
|
||||||
if ($(this).val() === '') {
|
|
||||||
$(this).parent().removeClass('input--filled' );
|
window.challenge.postRender = function(){
|
||||||
$label = $(this).siblings(".input-label");
|
|
||||||
$label.removeClass('input--hide' );
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
window.challenge.submit = function(cb, preview){
|
||||||
|
var chal_id = $('#chal-id').val();
|
||||||
|
var answer = $('#answer-input').val();
|
||||||
|
var nonce = $('#nonce').val();
|
||||||
|
|
||||||
|
var url = "/chal/";
|
||||||
|
if (preview) {
|
||||||
|
url = "/admin/chal/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$.post(script_root + url + chal_id, {
|
||||||
|
key: answer,
|
||||||
|
nonce: nonce
|
||||||
|
}, function (data) {
|
||||||
|
cb(data);
|
||||||
});
|
});
|
||||||
|
};
|
|
@ -18,24 +18,18 @@ $('#limit_max_attempts').change(function() {
|
||||||
|
|
||||||
// Markdown Preview
|
// Markdown Preview
|
||||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
$('#desc-edit').on('shown.bs.tab', function (event) {
|
||||||
var md = window.markdownit({
|
|
||||||
html: true,
|
|
||||||
});
|
|
||||||
if (event.target.hash == '#desc-preview') {
|
if (event.target.hash == '#desc-preview') {
|
||||||
var editor_value = $('#desc-editor').val();
|
var editor_value = $('#desc-editor').val();
|
||||||
$(event.target.hash).html(
|
$(event.target.hash).html(
|
||||||
md.render(editor_value)
|
window.challenge.render(editor_value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||||
var md = window.markdownit({
|
|
||||||
html: true,
|
|
||||||
});
|
|
||||||
if (event.target.hash == '#new-desc-preview') {
|
if (event.target.hash == '#new-desc-preview') {
|
||||||
var editor_value = $('#new-desc-editor').val();
|
var editor_value = $('#new-desc-editor').val();
|
||||||
$(event.target.hash).html(
|
$(event.target.hash).html(
|
||||||
md.render(editor_value)
|
window.challenge.render(editor_value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,10 +24,18 @@ class CTFdStaticKey(BaseKey):
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compare(saved, provided):
|
def compare(chal_key_obj, provided):
|
||||||
|
saved = chal_key_obj.flag
|
||||||
|
data = chal_key_obj.data
|
||||||
|
|
||||||
if len(saved) != len(provided):
|
if len(saved) != len(provided):
|
||||||
return False
|
return False
|
||||||
result = 0
|
result = 0
|
||||||
|
|
||||||
|
if data == "case_insensitive":
|
||||||
|
for x, y in zip(saved.lower(), provided.lower()):
|
||||||
|
result |= ord(x) ^ ord(y)
|
||||||
|
else:
|
||||||
for x, y in zip(saved, provided):
|
for x, y in zip(saved, provided):
|
||||||
result |= ord(x) ^ ord(y)
|
result |= ord(x) ^ ord(y)
|
||||||
return result == 0
|
return result == 0
|
||||||
|
@ -42,8 +50,15 @@ class CTFdRegexKey(BaseKey):
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compare(saved, provided):
|
def compare(chal_key_obj, provided):
|
||||||
|
saved = chal_key_obj.flag
|
||||||
|
data = chal_key_obj.data
|
||||||
|
|
||||||
|
if data == "case_insensitive":
|
||||||
res = re.match(saved, provided, re.IGNORECASE)
|
res = re.match(saved, provided, re.IGNORECASE)
|
||||||
|
else:
|
||||||
|
res = re.match(saved, provided)
|
||||||
|
|
||||||
return res and res.group() == provided
|
return res and res.group() == provided
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
<label for="create-key-regex" class="control-label">Enter Regex Key Data</label>
|
<label for="create-key-regex" class="control-label">Enter Regex Key Data</label>
|
||||||
<input type="text" id="create-key-regex" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
|
<input type="text" id="create-key-regex" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive">
|
||||||
|
<label class="form-check-label" for="keydata">Case Insensitive</label>
|
||||||
|
</div>
|
|
@ -15,6 +15,11 @@
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form method="POST" action="{{ script_root }}/admin/keys/{{id}}">
|
<form method="POST" action="{{ script_root }}/admin/keys/{{id}}">
|
||||||
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
|
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive"
|
||||||
|
{% if data %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="keydata">Case Insensitive</label>
|
||||||
|
</div>
|
||||||
<input type="hidden" id="key-type" name="key_type" value="regex">
|
<input type="hidden" id="key-type" name="key_type" value="regex">
|
||||||
<input type="hidden" id="key-id">
|
<input type="hidden" id="key-id">
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
<label for="create-key-static" class="control-label">Enter Static Key Data</label>
|
<label for="create-key-static" class="control-label">Enter Static Key Data</label>
|
||||||
<input type="text" id="create-key-static" class="form-control" name="key" value="{{key}}" placeholder="Enter static key data">
|
<input type="text" id="create-key-static" class="form-control" name="key" value="{{key}}" placeholder="Enter static key data">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive">
|
||||||
|
<label class="form-check-label" for="keydata">Case Insensitive</label>
|
||||||
|
</div>
|
|
@ -14,7 +14,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form method="POST" action="{{ script_root }}/admin/keys/{{ id }}">
|
<form method="POST" action="{{ script_root }}/admin/keys/{{ id }}">
|
||||||
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter static key data">
|
<input type="text" id="key-data" class="form-control" name="key" value="{{ key }}"
|
||||||
|
placeholder="Enter static key data">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive"
|
||||||
|
{% if data %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="keydata">Case Insensitive</label>
|
||||||
|
</div>
|
||||||
<input type="hidden" id="key-type" name="key_type" value="static">
|
<input type="hidden" id="key-type" name="key_type" value="static">
|
||||||
<input type="hidden" id="key-id">
|
<input type="hidden" id="key-id">
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -4,8 +4,6 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
.chal-desc {
|
.chal-desc {
|
||||||
padding-left: 30px;
|
|
||||||
padding-right: 30px;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
$.ajaxSetup({ cache: false });
|
$.ajaxSetup({ cache: false });
|
||||||
|
|
||||||
|
window.challenge = new Object();
|
||||||
|
|
||||||
function load_chal_template(challenge){
|
function load_chal_template(challenge){
|
||||||
|
$.getScript(script_root + challenge.scripts.modal, function () {
|
||||||
|
console.log('loaded renderer');
|
||||||
$.get(script_root + challenge.templates.create, function (template_data) {
|
$.get(script_root + challenge.templates.create, function (template_data) {
|
||||||
var template = nunjucks.compile(template_data);
|
var template = nunjucks.compile(template_data);
|
||||||
$("#create-chal-entry-div").html(template.render({'nonce': nonce, 'script_root': script_root}));
|
$("#create-chal-entry-div").html(template.render({'nonce': nonce, 'script_root': script_root}));
|
||||||
|
@ -8,6 +12,7 @@ function load_chal_template(challenge){
|
||||||
console.log('loaded');
|
console.log('loaded');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$.get(script_root + '/admin/chal_types', function(data){
|
$.get(script_root + '/admin/chal_types', function(data){
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
var challenges = {};
|
var challenges = {};
|
||||||
|
window.challenge = new Object();
|
||||||
|
|
||||||
function load_chal_template(id, success_cb){
|
function load_chal_template(id, success_cb){
|
||||||
var obj = $.grep(challenges['game'], function (e) {
|
$.get(script_root + "/admin/chal/" + id, function (obj) {
|
||||||
return e.id == id;
|
$.getScript(script_root + obj.type_data.scripts.modal, function () {
|
||||||
})[0];
|
console.log('loaded renderer');
|
||||||
$.get(script_root + "/admin/chal/" + id, function (challenge_data) {
|
|
||||||
$.get(script_root + obj.type_data.templates.update, function (template_data) {
|
$.get(script_root + obj.type_data.templates.update, function (template_data) {
|
||||||
var template = nunjucks.compile(template_data);
|
var template = nunjucks.compile(template_data);
|
||||||
|
|
||||||
challenge_data['nonce'] = $('#nonce').val();
|
obj['nonce'] = $('#nonce').val();
|
||||||
challenge_data['script_root'] = script_root;
|
obj['script_root'] = script_root;
|
||||||
|
|
||||||
$("#update-modals-entry-div").html(template.render(challenge_data));
|
$("#update-modals-entry-div").html(template.render(obj));
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: script_root + obj.type_data.scripts.update,
|
url: script_root + obj.type_data.scripts.update,
|
||||||
|
@ -22,6 +21,7 @@ function load_chal_template(id, success_cb){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_challenge_preview(id){
|
function load_challenge_preview(id){
|
||||||
|
@ -30,55 +30,63 @@ function load_challenge_preview(id){
|
||||||
|
|
||||||
function render_challenge_preview(chal_id){
|
function render_challenge_preview(chal_id){
|
||||||
var preview_window = $('#challenge-preview');
|
var preview_window = $('#challenge-preview');
|
||||||
var md = window.markdownit({
|
$.get(script_root + "/admin/chal/" + chal_id, function(obj){
|
||||||
html: true,
|
$.getScript(script_root + obj.type_data.scripts.modal, function () {
|
||||||
});
|
console.log('loaded renderer');
|
||||||
$.get(script_root + "/admin/chal/" + chal_id, function(challenge_data){
|
|
||||||
$.get(script_root + challenge_data.type_data.templates.modal, function (template_data) {
|
$.get(script_root + obj.type_data.templates.modal, function (template_data) {
|
||||||
preview_window.empty();
|
|
||||||
var template = nunjucks.compile(template_data);
|
var template = nunjucks.compile(template_data);
|
||||||
|
|
||||||
challenge_data['description'] = md.render(challenge_data['description']);
|
window.challenge.preRender()
|
||||||
challenge_data['script_root'] = script_root;
|
|
||||||
|
|
||||||
var challenge = template.render(challenge_data);
|
obj['description'] = window.challenge.render(obj['description']);
|
||||||
|
obj['script_root'] = script_root;
|
||||||
|
|
||||||
preview_window.append(challenge);
|
var challenge = template.render(obj);
|
||||||
|
|
||||||
|
preview_window.html(challenge);
|
||||||
|
|
||||||
|
$('#submit-key').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#submit-key').addClass("disabled-button");
|
||||||
|
$('#submit-key').prop('disabled', true);
|
||||||
|
|
||||||
|
window.challenge.submit(function (data) {
|
||||||
|
renderSubmissionResponse(data)
|
||||||
|
}, preview=true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#answer-input").keyup(function (event) {
|
||||||
|
if (event.keyCode == 13) {
|
||||||
|
$("#submit-key").click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.challenge.postRender()
|
||||||
|
|
||||||
$.getScript(script_root + challenge_data.type_data.scripts.modal, function () {
|
|
||||||
preview_window.modal();
|
preview_window.modal();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadchals(cb){
|
|
||||||
$.post(script_root + "/admin/chals", {
|
|
||||||
'nonce': $('#nonce').val()
|
|
||||||
}, function (data) {
|
|
||||||
var categories = [];
|
|
||||||
challenges = $.parseJSON(JSON.stringify(data));
|
|
||||||
|
|
||||||
for (var i = challenges['game'].length - 1; i >= 0; i--) {
|
function loadsolves(id) {
|
||||||
if ($.inArray(challenges['game'][i].category, categories) == -1) {
|
$.get(script_root + '/admin/chal/' + id + '/solves', function (data) {
|
||||||
categories.push(challenges['game'][i].category)
|
var teams = data['teams'];
|
||||||
}
|
var box = $('#challenge-solves-body');
|
||||||
}
|
var modal = $('#challenge-solves-modal')
|
||||||
|
box.empty();
|
||||||
if (cb) {
|
for (var i = 0; i < teams.length; i++) {
|
||||||
cb();
|
var id = teams[i].id;
|
||||||
|
var name = teams[i].name;
|
||||||
|
var date = moment(teams[i].date).local().format('MMMM Do, h:mm:ss A');
|
||||||
|
box.append('<tr><td><a href="team/{0}">{1}</td><td><small>{2}</small></td></tr>'.format(id, htmlentities(name), date));
|
||||||
}
|
}
|
||||||
|
modal.modal();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadchals(function(){
|
|
||||||
$('.edit-challenge').click(function (e) {
|
|
||||||
var id = $(this).attr('chal-id');
|
|
||||||
load_chal_template(id, function () {
|
|
||||||
openchal(id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function loadhint(hintid) {
|
function loadhint(hintid) {
|
||||||
var md = window.markdownit({
|
var md = window.markdownit({
|
||||||
|
@ -107,12 +115,7 @@ function loadhint(hintid) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitkey(chal, key, nonce){
|
function renderSubmissionResponse(data, cb) {
|
||||||
$.post(script_root + "/admin/chal/" + chal, {
|
|
||||||
key: key,
|
|
||||||
nonce: nonce
|
|
||||||
}, function (data) {
|
|
||||||
console.log(data);
|
|
||||||
var result = $.parseJSON(JSON.stringify(data));
|
var result = $.parseJSON(JSON.stringify(data));
|
||||||
|
|
||||||
var result_message = $('#result-message');
|
var result_message = $('#result-message');
|
||||||
|
@ -164,7 +167,10 @@ function submitkey(chal, key, nonce){
|
||||||
$('#submit-key').removeClass("disabled-button");
|
$('#submit-key').removeClass("disabled-button");
|
||||||
$('#submit-key').prop('disabled', false);
|
$('#submit-key').prop('disabled', false);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
|
||||||
|
if (cb) {
|
||||||
|
cb(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -194,9 +200,24 @@ $(document).ready(function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.edit-challenge').click(function (e) {
|
||||||
|
var id = $(this).attr('chal-id');
|
||||||
|
load_chal_template(id, function () {
|
||||||
|
openchal(id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('.preview-challenge').click(function (e) {
|
$('.preview-challenge').click(function (e) {
|
||||||
var chal_id = $(this).attr('chal-id');
|
var chal_id = $(this).attr('chal-id');
|
||||||
|
|
||||||
load_challenge_preview(chal_id);
|
load_challenge_preview(chal_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.stats-challenge').click(function (e) {
|
||||||
|
var chal_id = $(this).attr('chal-id');
|
||||||
|
var title = $(this).attr('title') || $(this).attr('data-original-title');
|
||||||
|
$('#challenge-solves-title').text(title);
|
||||||
|
|
||||||
|
loadsolves(chal_id);
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -17,13 +17,9 @@ function load_edit_key_modal(key_id, key_type_name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function create_key(chal, key, key_type) {
|
function create_key(chal, chal_data) {
|
||||||
$.post(script_root + "/admin/keys", {
|
chal_data.push({name: 'nonce', value: $('#nonce').val()});
|
||||||
chal: chal,
|
$.post(script_root + "/admin/keys", chal_data, function (data) {
|
||||||
key: key,
|
|
||||||
key_type: key_type,
|
|
||||||
nonce: $('#nonce').val()
|
|
||||||
}, function (data) {
|
|
||||||
if (data == "1"){
|
if (data == "1"){
|
||||||
loadkeys(chal);
|
loadkeys(chal);
|
||||||
$("#create-keys").modal('toggle');
|
$("#create-keys").modal('toggle');
|
||||||
|
@ -73,17 +69,12 @@ function deletekey(key_id){
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatekey(){
|
function updatekey(){
|
||||||
|
var edit_key_modal = $('#edit-keys form').serializeArray();
|
||||||
|
|
||||||
var key_id = $('#key-id').val();
|
var key_id = $('#key-id').val();
|
||||||
var chal = $("#update-keys").attr('chal-id');
|
var chal = $("#update-keys").attr('chal-id');
|
||||||
var key_data = $('#key-data').val();
|
|
||||||
var key_type = $('#key-type').val();
|
$.post(script_root + '/admin/keys/'+key_id, edit_key_modal, function(data){
|
||||||
var nonce = $('#nonce').val();
|
|
||||||
$.post(script_root + '/admin/keys/'+key_id, {
|
|
||||||
'chal':chal,
|
|
||||||
'key':key_data,
|
|
||||||
'key_type': key_type,
|
|
||||||
'nonce': nonce
|
|
||||||
}, function(data){
|
|
||||||
if (data == "1") {
|
if (data == "1") {
|
||||||
loadkeys(chal);
|
loadkeys(chal);
|
||||||
$('#edit-keys').modal('toggle');
|
$('#edit-keys').modal('toggle');
|
||||||
|
@ -132,9 +123,13 @@ $(document).ready(function () {
|
||||||
|
|
||||||
$('#create-keys-submit').click(function (e) {
|
$('#create-keys-submit').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
var chal_data = $('#create-keys-entry-div :input').serializeArray();
|
||||||
|
|
||||||
var chalid = $("#update-keys").attr('chal-id');
|
var chalid = $("#update-keys").attr('chal-id');
|
||||||
var key_data = $('#create-keys').find('input[name=key]').val();
|
chal_data.push({name: 'chal', value: chalid});
|
||||||
|
|
||||||
var key_type = $('#create-keys-select').val();
|
var key_type = $('#create-keys-select').val();
|
||||||
create_key(chalid, key_data, key_type);
|
chal_data.push({name: 'key_type', value: key_type});
|
||||||
|
create_key(chalid, chal_data);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -19,6 +19,7 @@
|
||||||
<script src="{{ request.script_root }}/themes/admin/static/js/vendor/nunjucks.min.js"></script>
|
<script src="{{ request.script_root }}/themes/admin/static/js/vendor/nunjucks.min.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var script_root = "{{ request.script_root }}";
|
var script_root = "{{ request.script_root }}";
|
||||||
|
var csrf_nonce = "{{ nonce }}";
|
||||||
</script>
|
</script>
|
||||||
{% block stylesheets %} {% endblock %}
|
{% block stylesheets %} {% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include "admin/modals/challenges/challenges.html" %}
|
{% include "admin/modals/challenges/challenges.html" %}
|
||||||
|
{% include "admin/modals/challenges/challenges-solves.html" %}
|
||||||
|
|
||||||
{% include "admin/modals/tags/tags.html" %}
|
{% include "admin/modals/tags/tags.html" %}
|
||||||
{% include "admin/modals/files/files.html" %}
|
{% include "admin/modals/files/files.html" %}
|
||||||
|
@ -57,9 +58,9 @@
|
||||||
<td class="d-none d-md-table-cell d-lg-table-cell">{{ challenge.type }}</td>
|
<td class="d-none d-md-table-cell d-lg-table-cell">{{ challenge.type }}</td>
|
||||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center">
|
<td class="d-none d-md-table-cell d-lg-table-cell text-center">
|
||||||
{% if challenge.hidden %}
|
{% if challenge.hidden %}
|
||||||
<button class="btn-sm btn-danger" type="submit" disabled>Hidden</button>
|
<span class="badge badge-danger">hidden</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="btn-sm btn-success" type="submit" disabled>Visible</button>
|
<span class="badge badge-success">visible</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -75,6 +76,13 @@
|
||||||
<i class="btn-fa fas fa-file-alt" aria-hidden="true" chal-id="{{ challenge.id }}"></i>
|
<i class="btn-fa fas fa-file-alt" aria-hidden="true" chal-id="{{ challenge.id }}"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span class="stats-challenge" data-toggle="tooltip"
|
||||||
|
data-placement="top" chal-id="{{ challenge.id }}"
|
||||||
|
title="{{ challenge.name }} solves">
|
||||||
|
<i class="btn-fa fas fa-chart-bar" aria-hidden="true"
|
||||||
|
chal-id="{{ challenge.id }}"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
<span> </span>
|
<span> </span>
|
||||||
|
|
||||||
<span class="edit-keys" data-toggle="tooltip" data-placement="top"
|
<span class="edit-keys" data-toggle="tooltip" data-placement="top"
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<form method="POST" autocomplete="off" class="w-100">
|
<form method="POST" enctype="multipart/form-data" autocomplete="off" class="w-100">
|
||||||
{% for error in errors %}
|
{% for error in errors %}
|
||||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||||
<span class="sr-only">Error:</span>
|
<span class="sr-only">Error:</span>
|
||||||
|
@ -43,7 +43,31 @@
|
||||||
<div role="tabpanel" class="tab-pane active" id="appearance-section">
|
<div role="tabpanel" class="tab-pane active" id="appearance-section">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="ctf_name">CTF Name:</label>
|
<label for="ctf_name">CTF Name:</label>
|
||||||
<input class="form-control" id='ctf_name' name='ctf_name' type='text' placeholder="CTF Name" {% if ctf_name is defined and ctf_name != None %}value="{{ ctf_name }}"{% endif %}>
|
<input class="form-control" id='ctf_name' name='ctf_name' type='text' placeholder="CTF Name"
|
||||||
|
{% if ctf_name is defined and ctf_name != None %}value="{{ ctf_name }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="ctf_logo">CTF Logo:</label>
|
||||||
|
|
||||||
|
{% if ctf_logo %}
|
||||||
|
<div class="d-block py-3">
|
||||||
|
<img id="ctf_logo_preview" class="img-responsive ctf_logo" src="{{ request.script_root }}/files/{{ ctf_logo }}"
|
||||||
|
height="25">
|
||||||
|
<button type="button" class="btn-sm btn-danger float-right" onclick="
|
||||||
|
$('#ctf_logo').attr('type', 'text');
|
||||||
|
$('#ctf_logo').attr('placeholder', 'Logo will be removed on next update');
|
||||||
|
$('#ctf_logo').attr('value', '');
|
||||||
|
$('#ctf_logo').attr('readonly', 'true');
|
||||||
|
$('#ctf_logo_preview').css('visibility', 'hidden');
|
||||||
|
">
|
||||||
|
Remove Logo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<input class="form-control" id='ctf_logo' name='ctf_logo' type='file' placeholder="CTF Logo"
|
||||||
|
{% if ctf_logo is defined and ctf_logo != None %}value="{{ ctf_logo }}"{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<div id="challenge-solves-modal" class="modal fade" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header text-center">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3 id="challenge-solves-title" class="text-center">Solves</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<table class="table table-striped text-center">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Name</b>
|
||||||
|
</td>
|
||||||
|
<td><b>Date</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="challenge-solves-body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,2 +1,3 @@
|
||||||
|
|
||||||
<div id="challenge-preview" class="modal fade" tabindex="-1">
|
<div id="challenge-preview" class="modal fade" tabindex="-1">
|
||||||
</div>
|
</div>
|
|
@ -82,6 +82,20 @@
|
||||||
<input type="text" class="form-control" name="country" id="country"
|
<input type="text" class="form-control" name="country" id="country"
|
||||||
placeholder="Enter country">
|
placeholder="Enter country">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" name="admin" id="admin-checkbox">
|
||||||
|
<label class="form-check-label" for="admin-checkbox">Admin</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" name="verified" id="verified-checkbox">
|
||||||
|
<label class="form-check-label" for="verified-checkbox">Verified</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" name="hidden" id="hidden-checkbox">
|
||||||
|
<label class="form-check-label" for="hidden-checkbox">Hidden</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="results">
|
<div id="results">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,16 +147,18 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Email</b>
|
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Email</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Website</b>
|
<td class="d-none"><b>Website</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Affiliation</b>
|
<td class="d-none"><b>Affiliation</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Country</b>
|
<td class="d-none"><b>Country</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center"><b>Admin</b>
|
<td class="text-center"><b>Admin</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center"><b>Verified</b>
|
<td class="text-center"><b>Verified</b>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="text-center"><b>Hidden</b>
|
||||||
|
</td>
|
||||||
<td class="text-center"><b>Settings</b>
|
<td class="text-center"><b>Settings</b>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -154,7 +170,7 @@
|
||||||
<td class="team-name" value="{{ team.name }}"><a href="{{ request.script_root }}/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
|
<td class="team-name" value="{{ team.name }}"><a href="{{ request.script_root }}/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="team-email d-none d-md-table-cell d-lg-table-cell" value="{{ team.email }}">{{ team.email | truncate(32) }}</td>
|
<td class="team-email d-none d-md-table-cell d-lg-table-cell" value="{{ team.email }}">{{ team.email | truncate(32) }}</td>
|
||||||
<td class="team-website d-none d-md-table-cell d-lg-table-cell text-center">
|
<td class="team-website d-none text-center">
|
||||||
{% if team.website %}
|
{% if team.website %}
|
||||||
<a href="{{ team.website }}" target="_blank">
|
<a href="{{ team.website }}" target="_blank">
|
||||||
<i class="btn-fa fas fa-external-link-alt" data-toggle="tooltip" data-placement="top"
|
<i class="btn-fa fas fa-external-link-alt" data-toggle="tooltip" data-placement="top"
|
||||||
|
@ -162,21 +178,26 @@
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="team-affiliation d-none d-md-table-cell d-lg-table-cell" value="{{ team.affiliation if team.affiliation is not none }}">
|
<td class="team-affiliation d-none" value="{{ team.affiliation if team.affiliation is not none }}">
|
||||||
<span>{% if team.affiliation %}{{ team.affiliation | truncate(20) }}{% endif %}</span>
|
<span>{% if team.affiliation %}{{ team.affiliation | truncate(20) }}{% endif %}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="team-country d-none d-md-table-cell d-lg-table-cell" value="{{ team.country if team.country is not none }}">
|
<td class="team-country d-none" value="{{ team.country if team.country is not none }}">
|
||||||
<span>{% if team.country %}{{ team.country }}{% endif %}</span>
|
<span>{% if team.country %}{{ team.country }}{% endif %}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="team-admin">
|
<td class="team-admin d-none d-md-table-cell d-lg-table-cell text-center" value="{{ team.admin }}">
|
||||||
<div class="center-block checkbox text-center">
|
{% if team.admin %}
|
||||||
<input type="checkbox" {% if team.admin %}checked{% endif %}>
|
<span class="badge badge-primary">admin</span>
|
||||||
</div>
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="team-verified">
|
<td class="team-verified d-none d-md-table-cell d-lg-table-cell text-center" value="{{ team.verified }}">
|
||||||
<div class="center-block checkbox text-center">
|
{% if team.verified %}
|
||||||
<input type="checkbox" {% if team.verified %}checked{% endif %}>
|
<span class="badge badge-success">verified</span>
|
||||||
</div>
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="team-hidden d-none d-md-table-cell d-lg-table-cell text-center" value="{{ team.banned }}">
|
||||||
|
{% if team.banned %}
|
||||||
|
<span class="badge badge-danger">hidden</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center"><span>
|
<td class="text-center"><span>
|
||||||
<span class="edit-team" data-toggle="tooltip" data-placement="top"
|
<span class="edit-team" data-toggle="tooltip" data-placement="top"
|
||||||
|
@ -222,7 +243,7 @@
|
||||||
<script>
|
<script>
|
||||||
var nonce = "{{ nonce }}";
|
var nonce = "{{ nonce }}";
|
||||||
|
|
||||||
function load_update_modal(id, name, email, website, affiliation, country){
|
function load_update_modal(id, name, email, website, affiliation, country, admin, verified, hidden){
|
||||||
var modal_form = $('#update-user-modal form');
|
var modal_form = $('#update-user-modal form');
|
||||||
|
|
||||||
modal_form.find('input[name=name]').val(name);
|
modal_form.find('input[name=name]').val(name);
|
||||||
|
@ -233,6 +254,10 @@ function load_update_modal(id, name, email, website, affiliation, country){
|
||||||
modal_form.find('input[name=country]').val(country);
|
modal_form.find('input[name=country]').val(country);
|
||||||
modal_form.find('input[name=password]').val('');
|
modal_form.find('input[name=password]').val('');
|
||||||
|
|
||||||
|
modal_form.find('input[name=admin]').prop('checked', admin);
|
||||||
|
modal_form.find('input[name=verified]').prop('checked', verified);
|
||||||
|
modal_form.find('input[name=hidden]').prop('checked', hidden);
|
||||||
|
|
||||||
if (id == 'new'){
|
if (id == 'new'){
|
||||||
$('#update-user-modal .modal-action').text('Create Team');
|
$('#update-user-modal .modal-action').text('Create Team');
|
||||||
} else {
|
} else {
|
||||||
|
@ -280,24 +305,6 @@ $(document).ready(function () {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.team-admin input').on('change', function () {
|
|
||||||
var elem = $(this).parent().parent().parent();
|
|
||||||
var id = elem.find('.team-id').text().trim();
|
|
||||||
var admin = $(this).prop('checked');
|
|
||||||
console.log(admin);
|
|
||||||
|
|
||||||
$.post('{{ request.script_root }}/admin/team/' + id, {'admin': admin, 'nonce': nonce});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.team-verified input').on('change', function () {
|
|
||||||
var elem = $(this).parent().parent().parent();
|
|
||||||
var id = elem.find('.team-id').text().trim();
|
|
||||||
var verified = $(this).prop('checked');
|
|
||||||
console.log(verified);
|
|
||||||
|
|
||||||
$.post('{{ request.script_root }}/admin/team/' + id, {'verified': verified, 'nonce': nonce});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#send-user-email').click(function (e) {
|
$('#send-user-email').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var id = $('#email-user input[name="id"]').val();
|
var id = $('#email-user input[name="id"]').val();
|
||||||
|
@ -329,11 +336,15 @@ $(document).ready(function () {
|
||||||
var affiliation = elem.find('.team-affiliation').attr('value') || '';
|
var affiliation = elem.find('.team-affiliation').attr('value') || '';
|
||||||
var country = elem.find('.team-country').attr('value') || '';
|
var country = elem.find('.team-country').attr('value') || '';
|
||||||
|
|
||||||
load_update_modal(id, name, email, website, affiliation, country);
|
var admin = elem.find('.team-admin').attr('value') == 'True' || false;
|
||||||
|
var verified = elem.find('.team-verified').attr('value') == 'True' || false;
|
||||||
|
var hidden = elem.find('.team-hidden').attr('value') == 'True' || false;
|
||||||
|
|
||||||
|
load_update_modal(id, name, email, website, affiliation, country, admin, verified, hidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.create-team').click(function () {
|
$('.create-team').click(function () {
|
||||||
load_update_modal('new', '', '', '', '', '');
|
load_update_modal('new', '', '', '', '', '', false, false, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.delete-team').click(function () {
|
$('.delete-team').click(function () {
|
||||||
|
|
|
@ -2,6 +2,8 @@ var challenges;
|
||||||
var user_solves = [];
|
var user_solves = [];
|
||||||
var templates = {};
|
var templates = {};
|
||||||
|
|
||||||
|
window.challenge = new Object();
|
||||||
|
|
||||||
function loadchal(id) {
|
function loadchal(id) {
|
||||||
var obj = $.grep(challenges['game'], function (e) {
|
var obj = $.grep(challenges['game'], function (e) {
|
||||||
return e.id == id;
|
return e.id == id;
|
||||||
|
@ -20,26 +22,25 @@ function loadchalbyname(chalname) {
|
||||||
|
|
||||||
function updateChalWindow(obj) {
|
function updateChalWindow(obj) {
|
||||||
$.get(script_root + "/chals/" + obj.id, function(challenge_data){
|
$.get(script_root + "/chals/" + obj.id, function(challenge_data){
|
||||||
|
$.getScript(script_root + obj.script, function(){
|
||||||
$.get(script_root + obj.template, function (template_data) {
|
$.get(script_root + obj.template, function (template_data) {
|
||||||
$('#chal-window').empty();
|
$('#chal-window').empty();
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
var nonce = $('#nonce').val();
|
var nonce = $('#nonce').val();
|
||||||
|
|
||||||
var md = window.markdownit({
|
window.challenge.preRender();
|
||||||
html: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
challenge_data['description'] = md.render(challenge_data['description']);
|
challenge_data['description'] = window.challenge.render(challenge_data['description']);
|
||||||
challenge_data['script_root'] = script_root;
|
challenge_data['script_root'] = script_root;
|
||||||
challenge_data['solves'] = solves;
|
challenge_data['solves'] = solves;
|
||||||
|
|
||||||
$('#chal-window').append(template.render(challenge_data));
|
$('#chal-window').append(template.render(challenge_data));
|
||||||
$.getScript(script_root + obj.script,
|
|
||||||
function () {
|
|
||||||
// Handle Solves tab
|
|
||||||
$('.chal-solves').click(function (e) {
|
$('.chal-solves').click(function (e) {
|
||||||
getsolves($('#chal-id').val())
|
getsolves($('#chal-id').val())
|
||||||
});
|
});
|
||||||
|
@ -58,9 +59,36 @@ function updateChalWindow(obj) {
|
||||||
$("#too-fast").slideUp();
|
$("#too-fast").slideUp();
|
||||||
});
|
});
|
||||||
|
|
||||||
// $('pre code').each(function(i, block) {
|
$('#submit-key').click(function (e) {
|
||||||
// hljs.highlightBlock(block);
|
e.preventDefault();
|
||||||
// });
|
$('#submit-key').addClass("disabled-button");
|
||||||
|
$('#submit-key').prop('disabled', true);
|
||||||
|
window.challenge.submit(function (data) {
|
||||||
|
renderSubmissionResponse(data)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#answer-input").keyup(function (event) {
|
||||||
|
if (event.keyCode == 13) {
|
||||||
|
$("#submit-key").click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".input-field").bind({
|
||||||
|
focus: function () {
|
||||||
|
$(this).parent().addClass('input--filled');
|
||||||
|
$label = $(this).siblings(".input-label");
|
||||||
|
},
|
||||||
|
blur: function () {
|
||||||
|
if ($(this).val() === '') {
|
||||||
|
$(this).parent().removeClass('input--filled');
|
||||||
|
$label = $(this).siblings(".input-label");
|
||||||
|
$label.removeClass('input--hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.challenge.postRender();
|
||||||
|
|
||||||
window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
|
window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
|
||||||
$('#chal-window').modal();
|
$('#chal-window').modal();
|
||||||
|
@ -76,13 +104,7 @@ $("#answer-input").keyup(function(event){
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function submitkey(chal, key, nonce, cb) {
|
function renderSubmissionResponse(data, cb){
|
||||||
$('#submit-key').addClass("disabled-button");
|
|
||||||
$('#submit-key').prop('disabled', true);
|
|
||||||
$.post(script_root + "/chal/" + chal, {
|
|
||||||
key: key,
|
|
||||||
nonce: nonce
|
|
||||||
}, function (data) {
|
|
||||||
var result = $.parseJSON(JSON.stringify(data));
|
var result = $.parseJSON(JSON.stringify(data));
|
||||||
|
|
||||||
var result_message = $('#result-message');
|
var result_message = $('#result-message');
|
||||||
|
@ -141,7 +163,6 @@ function submitkey(chal, key, nonce, cb) {
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb(result);
|
cb(result);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function marksolves(cb) {
|
function marksolves(cb) {
|
||||||
|
|
|
@ -28,12 +28,19 @@
|
||||||
<script src="{{ request.script_root }}/themes/{{ ctf_theme() }}/static/js/vendor/nunjucks.min.js"></script>
|
<script src="{{ request.script_root }}/themes/{{ ctf_theme() }}/static/js/vendor/nunjucks.min.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var script_root = "{{ request.script_root }}";
|
var script_root = "{{ request.script_root }}";
|
||||||
|
var csrf_nonce = "{{ nonce }}"
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a href="{{ request.script_root }}/" class="navbar-brand">{{ ctf_name() }}</a>
|
<a href="{{ request.script_root }}/" class="navbar-brand">
|
||||||
|
{% if ctf_logo() %}
|
||||||
|
<img class="img-responsive ctf_logo" src="{{ request.script_root }}/files/{{ ctf_logo() }}" height="25" alt="{{ ctf_name() }}">
|
||||||
|
{% else %}
|
||||||
|
{{ ctf_name() }}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#base-navbars"
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#base-navbars"
|
||||||
aria-controls="base-navbars" aria-expanded="false" aria-label="Toggle navigation">
|
aria-controls="base-navbars" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
|
|
@ -150,6 +150,7 @@ def init_utils(app):
|
||||||
app.jinja_env.globals.update(can_register=can_register)
|
app.jinja_env.globals.update(can_register=can_register)
|
||||||
app.jinja_env.globals.update(can_send_mail=can_send_mail)
|
app.jinja_env.globals.update(can_send_mail=can_send_mail)
|
||||||
app.jinja_env.globals.update(ctf_name=ctf_name)
|
app.jinja_env.globals.update(ctf_name=ctf_name)
|
||||||
|
app.jinja_env.globals.update(ctf_logo=ctf_logo)
|
||||||
app.jinja_env.globals.update(ctf_theme=ctf_theme)
|
app.jinja_env.globals.update(ctf_theme=ctf_theme)
|
||||||
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
|
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
|
||||||
app.jinja_env.globals.update(get_registered_scripts=get_registered_scripts)
|
app.jinja_env.globals.update(get_registered_scripts=get_registered_scripts)
|
||||||
|
@ -211,6 +212,11 @@ def ctf_name():
|
||||||
return name if name else 'CTFd'
|
return name if name else 'CTFd'
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
|
def ctf_logo():
|
||||||
|
return get_config('ctf_logo')
|
||||||
|
|
||||||
|
|
||||||
@cache.memoize()
|
@cache.memoize()
|
||||||
def ctf_theme():
|
def ctf_theme():
|
||||||
theme = get_config('ctf_theme')
|
theme = get_config('ctf_theme')
|
||||||
|
@ -661,7 +667,7 @@ def verify_email(addr):
|
||||||
text = """Please click the following link to confirm your email address for {ctf_name}: {url}/{token}""".format(
|
text = """Please click the following link to confirm your email address for {ctf_name}: {url}/{token}""".format(
|
||||||
ctf_name=get_config('ctf_name'),
|
ctf_name=get_config('ctf_name'),
|
||||||
url=url_for('auth.confirm_user', _external=True),
|
url=url_for('auth.confirm_user', _external=True),
|
||||||
token=base64encode(token, urlencode=True)
|
token=base64encode(token)
|
||||||
)
|
)
|
||||||
sendmail(addr, text)
|
sendmail(addr, text)
|
||||||
|
|
||||||
|
@ -673,7 +679,7 @@ def forgot_password(email, team_name):
|
||||||
|
|
||||||
{0}/{1}
|
{0}/{1}
|
||||||
|
|
||||||
""".format(url_for('auth.reset_password', _external=True), base64encode(token, urlencode=True))
|
""".format(url_for('auth.reset_password', _external=True), base64encode(token))
|
||||||
|
|
||||||
sendmail(email, text)
|
sendmail(email, text)
|
||||||
|
|
||||||
|
@ -700,35 +706,30 @@ def sha512(string):
|
||||||
return hashlib.sha512(string).hexdigest()
|
return hashlib.sha512(string).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def base64encode(s, urlencode=False):
|
def base64encode(s):
|
||||||
if six.PY3 and isinstance(s, six.string_types):
|
if six.PY3 and isinstance(s, six.string_types):
|
||||||
s = s.encode('utf-8')
|
s = s.encode('utf-8')
|
||||||
else:
|
else:
|
||||||
# Python 2 support because the base64 module doesnt like unicode
|
# Python 2 support because the base64 module doesnt like unicode
|
||||||
s = str(s)
|
s = str(s)
|
||||||
|
|
||||||
encoded = base64.urlsafe_b64encode(s)
|
encoded = base64.urlsafe_b64encode(s).rstrip(b'\n=')
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
try:
|
try:
|
||||||
encoded = encoded.decode('utf-8')
|
encoded = encoded.decode('utf-8')
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
if urlencode:
|
|
||||||
encoded = quote(encoded)
|
|
||||||
return encoded
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
def base64decode(s, urldecode=False):
|
def base64decode(s):
|
||||||
if urldecode:
|
|
||||||
s = unquote(s)
|
|
||||||
|
|
||||||
if six.PY3 and isinstance(s, six.string_types):
|
if six.PY3 and isinstance(s, six.string_types):
|
||||||
s = s.encode('utf-8')
|
s = s.encode('utf-8')
|
||||||
else:
|
else:
|
||||||
# Python 2 support because the base64 module doesnt like unicode
|
# Python 2 support because the base64 module doesnt like unicode
|
||||||
s = str(s)
|
s = str(s)
|
||||||
|
|
||||||
decoded = base64.urlsafe_b64decode(s)
|
decoded = base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b'='))
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
try:
|
try:
|
||||||
decoded = decoded.decode('utf-8')
|
decoded = decoded.decode('utf-8')
|
||||||
|
|
|
@ -4,11 +4,11 @@ import functools
|
||||||
|
|
||||||
|
|
||||||
def during_ctf_time_only(f):
|
def during_ctf_time_only(f):
|
||||||
'''
|
"""
|
||||||
Decorator to restrict an endpoint to only be seen during a CTF
|
Decorator to restrict an endpoint to only be seen during a CTF
|
||||||
:param f:
|
:param f:
|
||||||
:return:
|
:return:
|
||||||
'''
|
"""
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def during_ctf_time_only_wrapper(*args, **kwargs):
|
def during_ctf_time_only_wrapper(*args, **kwargs):
|
||||||
if utils.ctftime() or utils.is_admin():
|
if utils.ctftime() or utils.is_admin():
|
||||||
|
@ -28,6 +28,11 @@ def during_ctf_time_only(f):
|
||||||
|
|
||||||
|
|
||||||
def require_verified_emails(f):
|
def require_verified_emails(f):
|
||||||
|
"""
|
||||||
|
Decorator to restrict an endpoint to users with confirmed active email addresses
|
||||||
|
:param f:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def require_verified_emails_wrapper(*args, **kwargs):
|
def require_verified_emails_wrapper(*args, **kwargs):
|
||||||
if utils.get_config('verify_emails'):
|
if utils.get_config('verify_emails'):
|
||||||
|
@ -39,6 +44,11 @@ def require_verified_emails(f):
|
||||||
|
|
||||||
|
|
||||||
def viewable_without_authentication(status_code=None):
|
def viewable_without_authentication(status_code=None):
|
||||||
|
"""
|
||||||
|
Decorator that allows users to view the specified endpoint if viewing challenges without authentication is enabled
|
||||||
|
:param status_code:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
def viewable_without_authentication_decorator(f):
|
def viewable_without_authentication_decorator(f):
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def viewable_without_authentication_wrapper(*args, **kwargs):
|
def viewable_without_authentication_wrapper(*args, **kwargs):
|
||||||
|
@ -55,6 +65,11 @@ def viewable_without_authentication(status_code=None):
|
||||||
|
|
||||||
|
|
||||||
def authed_only(f):
|
def authed_only(f):
|
||||||
|
"""
|
||||||
|
Decorator that requires the user to be authenticated
|
||||||
|
:param f:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def authed_only_wrapper(*args, **kwargs):
|
def authed_only_wrapper(*args, **kwargs):
|
||||||
if session.get('id'):
|
if session.get('id'):
|
||||||
|
@ -66,6 +81,11 @@ def authed_only(f):
|
||||||
|
|
||||||
|
|
||||||
def admins_only(f):
|
def admins_only(f):
|
||||||
|
"""
|
||||||
|
Decorator that requires the user to be authenticated and an admin
|
||||||
|
:param f:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def admins_only_wrapper(*args, **kwargs):
|
def admins_only_wrapper(*args, **kwargs):
|
||||||
if session.get('admin'):
|
if session.get('admin'):
|
||||||
|
|
|
@ -6,4 +6,6 @@ rednose>=1.1.1
|
||||||
pep8>=1.7.0
|
pep8>=1.7.0
|
||||||
freezegun>=0.3.9
|
freezegun>=0.3.9
|
||||||
psycopg2>=2.7.3.1
|
psycopg2>=2.7.3.1
|
||||||
|
psycopg2-binary>=2.7.3.1
|
||||||
codecov>=2.0.9
|
codecov>=2.0.9
|
||||||
|
nose-randomly>=1.2.5
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Flask==0.12.2
|
Flask==1.0.0
|
||||||
Flask-SQLAlchemy==2.3.2
|
Flask-SQLAlchemy==2.3.2
|
||||||
Flask-Session==0.3.1
|
Flask-Session==0.3.1
|
||||||
Flask-Caching==1.3.3
|
Flask-Caching==1.3.3
|
||||||
|
@ -19,3 +19,4 @@ netaddr==0.7.19
|
||||||
redis==2.10.6
|
redis==2.10.6
|
||||||
datafreeze==0.1.0
|
datafreeze==0.1.0
|
||||||
gevent==1.2.2
|
gevent==1.2.2
|
||||||
|
python-dotenv==0.8.2
|
|
@ -16,6 +16,7 @@ def test_admin_post_config_values():
|
||||||
data = {
|
data = {
|
||||||
'nonce': sess.get('nonce'),
|
'nonce': sess.get('nonce'),
|
||||||
'ctf_name': 'CTFd',
|
'ctf_name': 'CTFd',
|
||||||
|
'ctf_logo': '',
|
||||||
'ctf_theme': 'core',
|
'ctf_theme': 'core',
|
||||||
'workshop_mode': 'on',
|
'workshop_mode': 'on',
|
||||||
'paused': 'on',
|
'paused': 'on',
|
||||||
|
@ -59,6 +60,7 @@ def test_admin_post_config_values():
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
'ctf_name': 'CTFd',
|
'ctf_name': 'CTFd',
|
||||||
|
'ctf_logo': None,
|
||||||
'ctf_theme': 'core',
|
'ctf_theme': 'core',
|
||||||
'workshop_mode': True,
|
'workshop_mode': True,
|
||||||
'paused': True,
|
'paused': True,
|
||||||
|
|
|
@ -208,7 +208,7 @@ def test_admins_can_delete_challenges():
|
||||||
|
|
||||||
|
|
||||||
def test_admins_can_delete_challenges_with_extras():
|
def test_admins_can_delete_challenges_with_extras():
|
||||||
""""Test that admins can delete challenges that have a hint"""
|
"""Test that admins can delete challenges that have a hint"""
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
client = login_as_user(app, name="admin", password="password")
|
client = login_as_user(app, name="admin", password="password")
|
||||||
|
@ -274,6 +274,24 @@ def test_admin_chal_detail_returns_proper_data():
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_load_chal_solves():
|
||||||
|
app = create_ctfd()
|
||||||
|
with app.app_context():
|
||||||
|
client = login_as_user(app, name="admin", password="password")
|
||||||
|
|
||||||
|
chal1 = gen_challenge(app.db)
|
||||||
|
flag1 = gen_flag(app.db, chal=chal1.id, flag='flag')
|
||||||
|
chal1_id = chal1.id
|
||||||
|
|
||||||
|
gen_solve(app.db, teamid=1, chalid=chal1_id)
|
||||||
|
|
||||||
|
r = client.get('/admin/chal/1/solves')
|
||||||
|
data = r.get_data(as_text=True)
|
||||||
|
assert json.loads(data)
|
||||||
|
|
||||||
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
def test_admins_can_create_teams():
|
def test_admins_can_create_teams():
|
||||||
'''Test that admins can create new teams'''
|
'''Test that admins can create new teams'''
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
|
|
|
@ -117,8 +117,10 @@ def gen_file(db, chal, location):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def gen_flag(db, chal, flag='flag', key_type='static'):
|
def gen_flag(db, chal, flag='flag', key_type='static', data=None):
|
||||||
key = Keys(chal, flag, key_type)
|
key = Keys(chal, flag, key_type)
|
||||||
|
if data:
|
||||||
|
key.data = data
|
||||||
db.session.add(key)
|
db.session.add(key)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return key
|
return key
|
||||||
|
|
|
@ -63,19 +63,14 @@ def test_base64encode():
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
assert base64encode('abc123') == 'YWJjMTIz'
|
assert base64encode('abc123') == 'YWJjMTIz'
|
||||||
assert base64encode(unicode('abc123')) == 'YWJjMTIz'
|
assert base64encode(unicode('abc123')) == 'YWJjMTIz'
|
||||||
assert base64encode(unicode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'), urlencode=True) == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D'
|
assert base64encode(unicode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4')) == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ'
|
||||||
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8='
|
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8'
|
||||||
assert base64encode('user+user@ctfd.io', urlencode=True) == 'dXNlcit1c2VyQGN0ZmQuaW8%3D'
|
assert base64encode('😆') == '8J-Yhg'
|
||||||
assert base64encode('😆') == '8J-Yhg=='
|
|
||||||
assert base64encode('😆', urlencode=True) == '8J-Yhg%3D%3D'
|
|
||||||
else:
|
else:
|
||||||
assert base64encode('abc123') == 'YWJjMTIz'
|
assert base64encode('abc123') == 'YWJjMTIz'
|
||||||
assert base64encode('abc123') == 'YWJjMTIz'
|
assert base64encode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4') == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ'
|
||||||
assert base64encode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4', urlencode=True) == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D'
|
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8'
|
||||||
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8='
|
assert base64encode('😆') == '8J-Yhg'
|
||||||
assert base64encode('user+user@ctfd.io', urlencode=True) == 'dXNlcit1c2VyQGN0ZmQuaW8%3D'
|
|
||||||
assert base64encode('😆') == '8J-Yhg=='
|
|
||||||
assert base64encode('😆', urlencode=True) == '8J-Yhg%3D%3D'
|
|
||||||
|
|
||||||
|
|
||||||
def test_base64decode():
|
def test_base64decode():
|
||||||
|
@ -83,17 +78,13 @@ def test_base64decode():
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
assert base64decode('YWJjMTIz') == 'abc123'
|
assert base64decode('YWJjMTIz') == 'abc123'
|
||||||
assert base64decode(unicode('YWJjMTIz')) == 'abc123'
|
assert base64decode(unicode('YWJjMTIz')) == 'abc123'
|
||||||
assert base64decode(unicode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D'), urldecode=True) == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
|
assert base64decode(unicode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ')) == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
|
||||||
assert base64decode('8J-Yhg==') == '😆'
|
assert base64decode('8J-Yhg') == '😆'
|
||||||
assert base64decode('8J-Yhg%3D%3D', urldecode=True) == '😆'
|
|
||||||
else:
|
else:
|
||||||
assert base64decode('YWJjMTIz') == 'abc123'
|
assert base64decode('YWJjMTIz') == 'abc123'
|
||||||
assert base64decode('YWJjMTIz') == 'abc123'
|
assert base64decode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ') == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
|
||||||
assert base64decode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D', urldecode=True) == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
|
assert base64decode('dXNlcit1c2VyQGN0ZmQuaW8') == 'user+user@ctfd.io'
|
||||||
assert base64decode('dXNlcit1c2VyQGN0ZmQuaW8=') == 'user+user@ctfd.io'
|
assert base64decode('8J-Yhg') == '😆'
|
||||||
assert base64decode('dXNlcit1c2VyQGN0ZmQuaW8%3D', urldecode=True) == 'user+user@ctfd.io'
|
|
||||||
assert base64decode('8J-Yhg==') == '😆'
|
|
||||||
assert base64decode('8J-Yhg%3D%3D', urldecode=True) == '😆'
|
|
||||||
|
|
||||||
|
|
||||||
def test_override_template():
|
def test_override_template():
|
||||||
|
@ -251,7 +242,7 @@ def test_verify_email(mock_smtp):
|
||||||
# This is currently not actually validated
|
# This is currently not actually validated
|
||||||
msg = ("Please click the following link to confirm"
|
msg = ("Please click the following link to confirm"
|
||||||
" your email address for CTFd:"
|
" your email address for CTFd:"
|
||||||
" http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc%3D")
|
" http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc")
|
||||||
|
|
||||||
ctf_name = get_config('ctf_name')
|
ctf_name = get_config('ctf_name')
|
||||||
email_msg = MIMEText(msg)
|
email_msg = MIMEText(msg)
|
||||||
|
|
|
@ -118,7 +118,7 @@ def test_expired_confirmation_links():
|
||||||
client = login_as_user(app, name="user", password="password")
|
client = login_as_user(app, name="user", password="password")
|
||||||
|
|
||||||
# user@user.com "2012-01-14 03:21:34"
|
# user@user.com "2012-01-14 03:21:34"
|
||||||
confirm_link = 'http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc%3D'
|
confirm_link = 'http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc'
|
||||||
r = client.get(confirm_link)
|
r = client.get(confirm_link)
|
||||||
|
|
||||||
assert "Your confirmation link has expired" in r.get_data(as_text=True)
|
assert "Your confirmation link has expired" in r.get_data(as_text=True)
|
||||||
|
@ -137,7 +137,7 @@ def test_invalid_confirmation_links():
|
||||||
client = login_as_user(app, name="user", password="password")
|
client = login_as_user(app, name="user", password="password")
|
||||||
|
|
||||||
# user@user.com "2012-01-14 03:21:34"
|
# user@user.com "2012-01-14 03:21:34"
|
||||||
confirm_link = 'http://localhost/confirm/a8375iyu<script>alert(1)<script>hn3048wueorighkgnsfg%3D%3D'
|
confirm_link = 'http://localhost/confirm/a8375iyu<script>alert(1)<script>hn3048wueorighkgnsfg'
|
||||||
r = client.get(confirm_link)
|
r = client.get(confirm_link)
|
||||||
|
|
||||||
assert "Your confirmation token is invalid" in r.get_data(as_text=True)
|
assert "Your confirmation token is invalid" in r.get_data(as_text=True)
|
||||||
|
@ -180,7 +180,7 @@ def test_invalid_reset_password_link():
|
||||||
|
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
# user@user.com "2012-01-14 03:21:34"
|
# user@user.com "2012-01-14 03:21:34"
|
||||||
forgot_link = 'http://localhost/reset_password/5678ytfghjiu876tyfg<>hvbnmkoi9u87y6trdfcgvhbnm,lp09iujmk%3D'
|
forgot_link = 'http://localhost/reset_password/5678ytfghjiu876tyfg<>hvbnmkoi9u87y6trdfcgvhbnm,lp09iujmk'
|
||||||
r = client.get(forgot_link)
|
r = client.get(forgot_link)
|
||||||
|
|
||||||
assert "Your reset token is invalid" in r.get_data(as_text=True)
|
assert "Your reset token is invalid" in r.get_data(as_text=True)
|
||||||
|
|
|
@ -132,6 +132,46 @@ def test_submitting_correct_flag():
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_submitting_correct_static_case_insensitive_flag():
|
||||||
|
"""Test that correct static flags are correct if the static flag is marked case_insensitive"""
|
||||||
|
app = create_ctfd()
|
||||||
|
with app.app_context():
|
||||||
|
register_user(app)
|
||||||
|
client = login_as_user(app)
|
||||||
|
chal = gen_challenge(app.db)
|
||||||
|
flag = gen_flag(app.db, chal=chal.id, flag='flag', data="case_insensitive")
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
data = {
|
||||||
|
"key": 'FLAG',
|
||||||
|
"nonce": sess.get('nonce')
|
||||||
|
}
|
||||||
|
r = client.post('/chal/{}'.format(chal.id), data=data)
|
||||||
|
assert r.status_code == 200
|
||||||
|
resp = json.loads(r.data.decode('utf8'))
|
||||||
|
assert resp.get('status') == 1 and resp.get('message') == "Correct"
|
||||||
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_submitting_correct_regex_case_insensitive_flag():
|
||||||
|
"""Test that correct regex flags are correct if the regex flag is marked case_insensitive"""
|
||||||
|
app = create_ctfd()
|
||||||
|
with app.app_context():
|
||||||
|
register_user(app)
|
||||||
|
client = login_as_user(app)
|
||||||
|
chal = gen_challenge(app.db)
|
||||||
|
flag = gen_flag(app.db, chal=chal.id, key_type='regex', flag='flag', data="case_insensitive")
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
data = {
|
||||||
|
"key": 'FLAG',
|
||||||
|
"nonce": sess.get('nonce')
|
||||||
|
}
|
||||||
|
r = client.post('/chal/{}'.format(chal.id), data=data)
|
||||||
|
assert r.status_code == 200
|
||||||
|
resp = json.loads(r.data.decode('utf8'))
|
||||||
|
assert resp.get('status') == 1 and resp.get('message') == "Correct"
|
||||||
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
def test_submitting_incorrect_flag():
|
def test_submitting_incorrect_flag():
|
||||||
"""Test that incorrect flags are incorrect"""
|
"""Test that incorrect flags are incorrect"""
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
|
|
|
@ -567,7 +567,7 @@ def test_user_can_confirm_email(mock_smtp):
|
||||||
assert r.location == "http://localhost/confirm" # We got redirected to /confirm
|
assert r.location == "http://localhost/confirm" # We got redirected to /confirm
|
||||||
|
|
||||||
# Use precalculated confirmation secret
|
# Use precalculated confirmation secret
|
||||||
r = client.get('http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc%3D')
|
r = client.get('http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc')
|
||||||
assert r.location == 'http://localhost/challenges'
|
assert r.location == 'http://localhost/challenges'
|
||||||
|
|
||||||
# The team is now verified
|
# The team is now verified
|
||||||
|
|
Loading…
Reference in New Issue