Merge branch 'master' of https://github.com/isislab/CTFd into view_challs_after

Conflicts:
	CTFd/admin.py
	CTFd/challenges.py
This commit is contained in:
Sean Meyer
2015-05-20 09:57:04 +08:00
7 changed files with 105 additions and 6 deletions

View File

@@ -74,6 +74,7 @@ def init_admin(app):
ctf_name = set_config("ctf_name", request.form.get('ctf_name', None)) ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None)) mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
do_api_key = set_config("do_api_key", request.form.get('do_api_key', None)) do_api_key = set_config("do_api_key", request.form.get('do_api_key', None))
max_tries = set_config("max_tries", request.form.get('max_tries', None))
db_start = Config.query.filter_by(key='start').first() db_start = Config.query.filter_by(key='start').first()
@@ -100,6 +101,11 @@ def init_admin(app):
if not do_api_key: if not do_api_key:
set_config('do_api_key', None) set_config('do_api_key', None)
max_tries = get_config('max_tries')
if not max_tries:
set_config('max_tries', 0)
max_tries = 0
view_after_ctf = get_config('view_after_ctf') == '1' view_after_ctf = get_config('view_after_ctf') == '1'
if not view_after_ctf: if not view_after_ctf:
set_config('view_after_ctf', 0) set_config('view_after_ctf', 0)
@@ -129,6 +135,7 @@ def init_admin(app):
db.session.close() db.session.close()
return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end, return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end,
max_tries=max_tries,
view_challenges_unregistered=view_challenges_unregistered, view_challenges_unregistered=view_challenges_unregistered,
prevent_registration=prevent_registration, do_api_key=do_api_key, mg_api_key=mg_api_key, prevent_registration=prevent_registration, do_api_key=do_api_key, mg_api_key=mg_api_key,
prevent_name_change=prevent_name_change, prevent_name_change=prevent_name_change,
@@ -165,6 +172,13 @@ def init_admin(app):
pages = Pages.query.all() pages = Pages.query.all()
return render_template('admin/pages.html', routes=pages) return render_template('admin/pages.html', routes=pages)
@app.route('/admin/page/<pageroute>/delete', methods=['POST'])
@admins_only
def delete_page(pageroute):
page = Pages.query.filter_by(route=pageroute).first()
db.session.delete(page)
db.session.commit()
return '1'
@app.route('/admin/hosts', methods=['GET']) @app.route('/admin/hosts', methods=['GET'])
@admins_only @admins_only
@@ -448,7 +462,7 @@ def init_admin(app):
solve_count = db.session.query(db.func.count(Solves.id)).first()[0] solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0] challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0]
most_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves DESC').first() most_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves DESC').first()
least_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves ASC').first() least_solved_chal = Challenges.query.add_columns(db.func.count(Solves.chalid).label('solves')).outerjoin(Solves).group_by(Challenges.id).order_by('solves ASC').first()
db.session.close() db.session.close()

View File

@@ -1,6 +1,6 @@
from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session
from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, can_view_challenges, is_admin from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, can_view_challenges, is_admin, get_config
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys
import time import time
@@ -58,7 +58,17 @@ def init_challenges(app):
db.session.close() db.session.close()
json = {'solves':[]} json = {'solves':[]}
for x in solves: for x in solves:
json['solves'].append({'id':x.id, 'chal':x.chal.name, 'chalid':x.chalid,'team':x.teamid, 'value': x.chal.value, 'category':x.chal.category, 'time':unix_time(x.date)}) json['solves'].append({ 'chal':x.chal.name, 'chalid':x.chalid,'team':x.teamid, 'value': x.chal.value, 'category':x.chal.category, 'time':unix_time(x.date)})
return jsonify(json)
@app.route('/maxattempts')
def attempts():
chals = Challenges.query.add_columns('id').all()
json = {'maxattempts':[]}
for chal, chalid in chals:
fails = WrongKeys.query.filter_by(team=session['id'], chal=chalid).count()
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
json['maxattempts'].append({'chalid':chalid})
return jsonify(json) return jsonify(json)
@app.route('/fails/<teamid>', methods=['GET']) @app.route('/fails/<teamid>', methods=['GET'])
@@ -82,9 +92,12 @@ def init_challenges(app):
if not ctftime(): if not ctftime():
return redirect('/challenges') return redirect('/challenges')
if authed(): if authed():
fails = WrongKeys.query.filter_by(team=session['id'],chal=chalid).count()
logger = logging.getLogger('keys') logger = logging.getLogger('keys')
data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id'])) data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id']))
print "[{0}] {1} submitted {2} with kpm {3}".format(*data) print "[{0}] {1} submitted {2} with kpm {3}".format(*data)
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
return "4" #too many tries on this challenge
if get_kpm(session['id']) > 10: if get_kpm(session['id']) > 10:
wrong = WrongKeys(session['id'], chalid, request.form['key']) wrong = WrongKeys(session['id'], chalid, request.form['key'])
db.session.add(wrong) db.session.add(wrong)

View File

@@ -62,6 +62,10 @@ def init_views(app):
html = request.form['html'] html = request.form['html']
page = Pages('index', html) page = Pages('index', html)
#max attempts per challenge
max_tries = Config("max_tries",0)
## Start time ## Start time
start = Config('start', None) start = Config('start', None)
end = Config('end', None) end = Config('end', None)
@@ -77,6 +81,7 @@ def init_views(app):
db.session.add(ctf_name) db.session.add(ctf_name)
db.session.add(admin) db.session.add(admin)
db.session.add(page) db.session.add(page)
db.session.add(max_tries)
db.session.add(start) db.session.add(start)
db.session.add(end) db.session.add(end)
db.session.add(view_challenges_unregistered) db.session.add(view_challenges_unregistered)

View File

@@ -105,6 +105,12 @@ function submitkey(chal, key, nonce) {
$('#submit-key').css('background-color', '#e18728') $('#submit-key').css('background-color', '#e18728')
$('#submit-key').prop('disabled', true) $('#submit-key').prop('disabled', true)
} }
else if (data == 4){ // too many incorrect solves
$('#submit-key').text('Too many attempts.')
$('#submit-key').css('background-color', 'red')
$('#submit-key').prop('disabled', true)
}
marktoomanyattempts()
marksolves() marksolves()
updatesolves() updatesolves()
setTimeout(function(){ setTimeout(function(){
@@ -129,6 +135,20 @@ function marksolves() {
}); });
} }
function marktoomanyattempts() {
$.get('/maxattempts', function (data) {
maxattempts = $.parseJSON(JSON.stringify(data));
for (var i = maxattempts['maxattempts'].length - 1; i >= 0; i--) {
id = maxattempts['maxattempts'][i].chalid
$('#challenges button[value="' + id + '"]').addClass('secondary')
$('#challenges button[value="' + id + '"]').css('background-color', '#FF9999')
};
if (window.location.hash.length > 0){
loadchalbyname(window.location.hash.substring(1))
}
});
}
function updatesolves(){ function updatesolves(){
$.get('/chals/solves', function (data) { $.get('/chals/solves', function (data) {
solves = $.parseJSON(JSON.stringify(data)); solves = $.parseJSON(JSON.stringify(data));
@@ -177,6 +197,7 @@ function loadchals() {
$('#' + challenges['game'][i].category.replace(/ /g,"-")).append($('<button value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>')); $('#' + challenges['game'][i].category.replace(/ /g,"-")).append($('<button value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>'));
}; };
updatesolves() updatesolves()
marktoomanyattempts()
marksolves() marksolves()
$('#challenges button').click(function (e) { $('#challenges button').click(function (e) {

View File

@@ -12,6 +12,11 @@
<input 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 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>
<div class="row">
<label for="max_tries">Maximum Attempts Per Challenge (0 to disable):</label>
<input id='max_tries' name='max_tries' type='text' placeholder="0" {% if max_tries is defined and max_tries != None %}value="{{ max_tries }}"{% endif %}>
</div>
<div class="row"> <div class="row">
<label for="start">Mailgun API Key:</label> <label for="start">Mailgun API Key:</label>
<input id='mg_api_key' name='mg_api_key' type='text' placeholder="Mailgun API Key" {% if mg_api_key is defined and mg_api_key != None %}value="{{ mg_api_key }}"{% endif %}> <input id='mg_api_key' name='mg_api_key' type='text' placeholder="Mailgun API Key" {% if mg_api_key is defined and mg_api_key != None %}value="{{ mg_api_key }}"{% endif %}>

View File

@@ -3,16 +3,31 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<br> <br>
<div id="confirm" class="reveal-modal" data-reveal>
<h2 class="text-center">Delete Page</h2>
<form method="POST">
<input type="hidden" name="route">
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
<div class="small-6 small-centered text-center columns">
<p>Are you sure you want to delete <strong id="confirm-route-name"></strong>?</p>
<button type="button" class="button alert radius" onclick="$('#confirm').foundation('reveal', 'close');">No</button>
<button type="button" id="delete-route" class="button success radius">Yes</button>
</div>
</form>
<a class="close-reveal-modal">&#215;</a>
</div>
<table id="pages"> <table id="pages">
<thead> <thead>
<tr> <tr>
<td><b>Route</b></td> <td><b>Route</b></td>
<td class="text-center" style="width: 150px;"><b>Settings</b></td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for route in routes %} {% for route in routes %}
<tr> <tr name="{{ route.route }}">
<td><a href="/admin/pages/{{ route.route }}">{{ route.route }}</a></td> <td class="route-name"><a href="/admin/pages/{{ route.route }}">{{ route.route }}</a></td>
<td class="text-center"><i class="fa fa-times"></i></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -25,4 +40,30 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script>
$('#delete-route').click(function(e){
e.preventDefault();
var route = $('#confirm input[name="route"]').val()
$.post($('#confirm form').attr('action'), $('#confirm form').serialize(), function(data){
var data = $.parseJSON(JSON.stringify(data))
if (data == "1"){
location.reload()
}
})
});
function load_confirm_modal(route){
var modal = $('#confirm')
modal.find('input[name=route]').val(route)
modal.find('#confirm-route-name').text(route)
$('#confirm form').attr('action', '/admin/page/'+route+'/delete');
$('#confirm').foundation('reveal', 'open');
}
$('.fa-times').click(function(){
var elem = $(this).parent().parent();
var name = elem.find('.route-name').text().trim();
load_confirm_modal(name)
});
</script>
{% endblock %} {% endblock %}

View File

@@ -13,7 +13,7 @@
<h3>Most solved: <b>{{ most_solved[0].chal.name }}</b> with {{ most_solved[1] }}</b> solves</h3> <h3>Most solved: <b>{{ most_solved[0].chal.name }}</b> with {{ most_solved[1] }}</b> solves</h3>
{% endif %} {% endif %}
{% if least_solved %} {% if least_solved %}
<h3>Least solved: <b>{{ least_solved[0].chal.name }}</b> with {{ least_solved[1] }}</b> solves</h3> <h3>Least solved: <b>{{ least_solved[0].name }}</b> with {{ least_solved[1] }}</b> solves</h3>
{% endif %} {% endif %}
</div> </div>