mirror of https://github.com/JohnHammond/CTFd.git
Hints (#232)
* Switching to Flask-Migrate to create tables/database. Adding Hints & Unlocks. * Adding db.create_all call for sqlite db's (sqlite is not properly handled with alembic yet) * Python 3 testing works properly with 3.5 * Adding admin side of hints * Hints are viewable for usersselenium-screenshot-testing
parent
9a9b775e57
commit
f48a0cdacd
|
@ -1,7 +1,7 @@
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- 2.7
|
- 2.7
|
||||||
- 3.6
|
- 3.5
|
||||||
install:
|
install:
|
||||||
- pip install -r development.txt
|
- pip install -r development.txt
|
||||||
script:
|
script:
|
||||||
|
|
|
@ -34,23 +34,26 @@ def create_app(config='CTFd.config.Config'):
|
||||||
if url.drivername == 'postgres':
|
if url.drivername == 'postgres':
|
||||||
url.drivername = 'postgresql'
|
url.drivername = 'postgresql'
|
||||||
|
|
||||||
|
## Creates database if the database database does not exist
|
||||||
|
if not database_exists(url):
|
||||||
|
create_database(url)
|
||||||
|
|
||||||
|
## Register database
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
try:
|
## Register Flask-Migrate
|
||||||
if not (url.drivername.startswith('sqlite') or database_exists(url)):
|
migrate.init_app(app, db)
|
||||||
create_database(url)
|
|
||||||
db.create_all()
|
## This creates tables instead of db.create_all()
|
||||||
except OperationalError:
|
## Allows migrations to happen properly
|
||||||
db.create_all()
|
migrate_upgrade()
|
||||||
except ProgrammingError: ## Database already exists
|
|
||||||
pass
|
## Alembic sqlite support is lacking so we should just create_all anyway
|
||||||
else:
|
if url.drivername.startswith('sqlite'):
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
app.db = db
|
app.db = db
|
||||||
|
|
||||||
migrate.init_app(app, db)
|
|
||||||
|
|
||||||
cache.init_app(app)
|
cache.init_app(app)
|
||||||
app.cache = cache
|
app.cache = cache
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
from CTFd.utils import admins_only, is_admin, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, Hints, Unlocks, DatabaseError
|
||||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||||
from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES
|
from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES
|
||||||
|
|
||||||
|
@ -87,6 +87,66 @@ def admin_delete_tags(tagid):
|
||||||
return '1'
|
return '1'
|
||||||
|
|
||||||
|
|
||||||
|
@admin_challenges.route('/admin/hints', defaults={'hintid': None}, methods=['POST', 'GET'])
|
||||||
|
@admin_challenges.route('/admin/hints/<int:hintid>', methods=['GET', 'POST', 'DELETE'])
|
||||||
|
@admins_only
|
||||||
|
def admin_hints(hintid):
|
||||||
|
if hintid:
|
||||||
|
hint = Hints.query.filter_by(id=hintid).first_or_404()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
hint.hint = request.form.get('hint')
|
||||||
|
hint.chal = int(request.form.get('chal'))
|
||||||
|
hint.cost = int(request.form.get('cost'))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
elif request.method == 'DELETE':
|
||||||
|
db.session.delete(hint)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
json_data = {
|
||||||
|
'hint': hint.hint,
|
||||||
|
'type': hint.type,
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost,
|
||||||
|
'id': hint.id
|
||||||
|
}
|
||||||
|
db.session.close()
|
||||||
|
return jsonify(json_data)
|
||||||
|
else:
|
||||||
|
if request.method == 'GET':
|
||||||
|
hints = Hints.query.all()
|
||||||
|
json_data = []
|
||||||
|
for hint in hints:
|
||||||
|
json_data.append({
|
||||||
|
'hint': hint.hint,
|
||||||
|
'type': hint.type,
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost,
|
||||||
|
'id': hint.id
|
||||||
|
})
|
||||||
|
return jsonify({'results': json_data})
|
||||||
|
elif request.method == 'POST':
|
||||||
|
hint = request.form.get('hint')
|
||||||
|
chalid = int(request.form.get('chal'))
|
||||||
|
cost = int(request.form.get('cost'))
|
||||||
|
hint_type = request.form.get('type', 0)
|
||||||
|
hint = Hints(chal=chalid, hint=hint, cost=cost)
|
||||||
|
db.session.add(hint)
|
||||||
|
db.session.commit()
|
||||||
|
json_data = {
|
||||||
|
'hint': hint.hint,
|
||||||
|
'type': hint.type,
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost,
|
||||||
|
'id': hint.id
|
||||||
|
}
|
||||||
|
db.session.close()
|
||||||
|
return jsonify(json_data)
|
||||||
|
|
||||||
|
|
||||||
@admin_challenges.route('/admin/files/<int:chalid>', methods=['GET', 'POST'])
|
@admin_challenges.route('/admin/files/<int:chalid>', methods=['GET', 'POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_files(chalid):
|
def admin_files(chalid):
|
||||||
|
@ -125,13 +185,34 @@ def admin_get_values(chalid, prop):
|
||||||
chal_keys = Keys.query.filter_by(chal=challenge.id).all()
|
chal_keys = Keys.query.filter_by(chal=challenge.id).all()
|
||||||
json_data = {'keys': []}
|
json_data = {'keys': []}
|
||||||
for x in chal_keys:
|
for x in chal_keys:
|
||||||
json_data['keys'].append({'id': x.id, 'key': x.flag, 'type': x.key_type, 'type_name': get_key_class(x.key_type).name})
|
json_data['keys'].append({
|
||||||
|
'id': x.id,
|
||||||
|
'key': x.flag,
|
||||||
|
'type': x.key_type,
|
||||||
|
'type_name': get_key_class(x.key_type).name
|
||||||
|
})
|
||||||
return jsonify(json_data)
|
return jsonify(json_data)
|
||||||
elif prop == 'tags':
|
elif prop == 'tags':
|
||||||
tags = Tags.query.filter_by(chal=chalid).all()
|
tags = Tags.query.filter_by(chal=chalid).all()
|
||||||
json_data = {'tags': []}
|
json_data = {'tags': []}
|
||||||
for x in tags:
|
for x in tags:
|
||||||
json_data['tags'].append({'id': x.id, 'chal': x.chal, 'tag': x.tag})
|
json_data['tags'].append({
|
||||||
|
'id': x.id,
|
||||||
|
'chal': x.chal,
|
||||||
|
'tag': x.tag
|
||||||
|
})
|
||||||
|
return jsonify(json_data)
|
||||||
|
elif prop == 'hints':
|
||||||
|
hints = Hints.query.filter_by(chal=chalid)
|
||||||
|
json_data = {'hints': []}
|
||||||
|
for hint in hints:
|
||||||
|
json_data['hints'].append({
|
||||||
|
'hint': hint.hint,
|
||||||
|
'type': hint.type,
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost,
|
||||||
|
'id': hint.id
|
||||||
|
})
|
||||||
return jsonify(json_data)
|
return jsonify(json_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import time
|
||||||
from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint
|
from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint
|
||||||
from sqlalchemy.sql import or_
|
from sqlalchemy.sql import or_
|
||||||
|
|
||||||
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards
|
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards, Hints, Unlocks
|
||||||
from CTFd.plugins.keys import get_key_class
|
from CTFd.plugins.keys import get_key_class
|
||||||
from CTFd.plugins.challenges import get_chal_class
|
from CTFd.plugins.challenges import get_chal_class
|
||||||
|
|
||||||
|
@ -14,6 +14,49 @@ from CTFd import utils
|
||||||
|
|
||||||
challenges = Blueprint('challenges', __name__)
|
challenges = Blueprint('challenges', __name__)
|
||||||
|
|
||||||
|
@challenges.route('/hints/<int:hintid>', methods=['GET', 'POST'])
|
||||||
|
def hints_view(hintid):
|
||||||
|
hint = Hints.query.filter_by(id=hintid).first_or_404()
|
||||||
|
chal = Challenges.query.filter_by(id=hint.chal).first()
|
||||||
|
unlock = Unlocks.query.filter_by(model='hints', itemid=hintid, teamid=session['id']).first()
|
||||||
|
if request.method == 'GET':
|
||||||
|
if unlock:
|
||||||
|
return jsonify({
|
||||||
|
'hint': hint.hint,
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost
|
||||||
|
})
|
||||||
|
elif request.method == 'POST':
|
||||||
|
if not unlock:
|
||||||
|
team = Teams.query.filter_by(id=session['id']).first()
|
||||||
|
if team.score() < hint.cost:
|
||||||
|
return jsonify({'errors': 'Not enough points'})
|
||||||
|
unlock = Unlocks(model='hints', teamid=session['id'], itemid=hint.id)
|
||||||
|
award = Awards(teamid=session['id'], name='Hint for {}'.format(chal.name), value=(-hint.cost))
|
||||||
|
db.session.add(unlock)
|
||||||
|
db.session.add(award)
|
||||||
|
db.session.commit()
|
||||||
|
json_data = {
|
||||||
|
'hint': hint.hint,
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost
|
||||||
|
}
|
||||||
|
db.session.close()
|
||||||
|
return jsonify(json_data)
|
||||||
|
else:
|
||||||
|
json_data = {
|
||||||
|
'hint': hint.hint,
|
||||||
|
'chal': hint.chal,
|
||||||
|
'cost': hint.cost
|
||||||
|
}
|
||||||
|
db.session.close()
|
||||||
|
return jsonify(json_data)
|
||||||
|
|
||||||
|
|
||||||
@challenges.route('/challenges', methods=['GET'])
|
@challenges.route('/challenges', methods=['GET'])
|
||||||
def challenges_view():
|
def challenges_view():
|
||||||
|
@ -57,6 +100,14 @@ def chals():
|
||||||
for x in chals:
|
for x in chals:
|
||||||
tags = [tag.tag for tag in Tags.query.add_columns('tag').filter_by(chal=x.id).all()]
|
tags = [tag.tag for tag in Tags.query.add_columns('tag').filter_by(chal=x.id).all()]
|
||||||
files = [str(f.location) for f in Files.query.filter_by(chal=x.id).all()]
|
files = [str(f.location) for f in Files.query.filter_by(chal=x.id).all()]
|
||||||
|
unlocked_hints = set([u.itemid for u in Unlocks.query.filter_by(model='hints', teamid=session['id'])])
|
||||||
|
hints = []
|
||||||
|
for hint in Hints.query.filter_by(chal=x.id).all():
|
||||||
|
if hint.id in unlocked_hints:
|
||||||
|
hints.append({'id':hint.id, 'cost':hint.cost, 'hint':hint.hint})
|
||||||
|
else:
|
||||||
|
hints.append({'id':hint.id, 'cost':hint.cost})
|
||||||
|
# hints = [{'id':hint.id, 'cost':hint.cost} for hint in Hints.query.filter_by(chal=x.id).all()]
|
||||||
chal_type = get_chal_class(x.type)
|
chal_type = get_chal_class(x.type)
|
||||||
json['game'].append({
|
json['game'].append({
|
||||||
'id': x.id,
|
'id': x.id,
|
||||||
|
@ -66,7 +117,8 @@ def chals():
|
||||||
'description': x.description,
|
'description': x.description,
|
||||||
'category': x.category,
|
'category': x.category,
|
||||||
'files': files,
|
'files': files,
|
||||||
'tags': tags
|
'tags': tags,
|
||||||
|
'hints': hints
|
||||||
})
|
})
|
||||||
|
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Config(object):
|
||||||
|
|
||||||
http://flask-sqlalchemy.pocoo.org/2.1/config/#configuration-keys
|
http://flask-sqlalchemy.pocoo.org/2.1/config/#configuration-keys
|
||||||
'''
|
'''
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///ctfd.db'
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///{}/ctfd.db'.format(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -76,6 +76,23 @@ class Challenges(db.Model):
|
||||||
return '<chal %r>' % self.name
|
return '<chal %r>' % self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Hints(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
type = db.Column(db.Integer, default=0)
|
||||||
|
chal = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||||
|
hint = db.Column(db.Text)
|
||||||
|
cost = db.Column(db.Integer, default=0)
|
||||||
|
|
||||||
|
def __init__(self, chal, hint, cost=0, type=0):
|
||||||
|
self.chal = chal
|
||||||
|
self.hint = hint
|
||||||
|
self.cost = cost
|
||||||
|
self.type = type
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<hint %r>' % self.hint
|
||||||
|
|
||||||
|
|
||||||
class Awards(db.Model):
|
class Awards(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
||||||
|
@ -244,6 +261,22 @@ class WrongKeys(db.Model):
|
||||||
return '<wrong %r>' % self.flag
|
return '<wrong %r>' % self.flag
|
||||||
|
|
||||||
|
|
||||||
|
class Unlocks(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
||||||
|
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||||
|
itemid = db.Column(db.Integer)
|
||||||
|
model = db.Column(db.String(32))
|
||||||
|
|
||||||
|
def __init__(self, model, teamid, itemid):
|
||||||
|
self.model = model
|
||||||
|
self.teamid = teamid
|
||||||
|
self.itemid = itemid
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<unlock %r>' % self.teamid
|
||||||
|
|
||||||
|
|
||||||
class Tracking(db.Model):
|
class Tracking(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
ip = db.Column(db.BigInteger)
|
ip = db.Column(db.BigInteger)
|
||||||
|
|
|
@ -38,6 +38,25 @@ function load_edit_key_modal(key_id, key_type_name) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function load_hint_modal(method, hintid){
|
||||||
|
$('#hint-modal-hint').val('');
|
||||||
|
$('#hint-modal-cost').val('');
|
||||||
|
if (method == 'create'){
|
||||||
|
$('#hint-modal-submit').attr('action', '/admin/hints');
|
||||||
|
$('#hint-modal-title').text('Create Hint');
|
||||||
|
$("#hint-modal").modal();
|
||||||
|
} else if (method == 'update'){
|
||||||
|
$.get(script_root + '/admin/hints/' + hintid, function(data){
|
||||||
|
$('#hint-modal-submit').attr('action', '/admin/hints/' + hintid);
|
||||||
|
$('#hint-modal-hint').val(data.hint);
|
||||||
|
$('#hint-modal-cost').val(data.cost);
|
||||||
|
$('#hint-modal-title').text('Update Hint');
|
||||||
|
$("#hint-modal-button").text('Update Hint');
|
||||||
|
$("#hint-modal").modal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loadchal(id, update) {
|
function loadchal(id, update) {
|
||||||
// $('#chal *').show()
|
// $('#chal *').show()
|
||||||
// $('#chal > h1').hide()
|
// $('#chal > h1').hide()
|
||||||
|
@ -169,6 +188,44 @@ function deletetag(tagid){
|
||||||
$.post(script_root + '/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
|
$.post(script_root + '/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function edithint(hintid){
|
||||||
|
$.get(script_root + '/admin/hints/' + hintid, function(data){
|
||||||
|
console.log(data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function deletehint(hintid){
|
||||||
|
$.delete(script_root + '/admin/hints/' + hintid, function(data, textStatus, jqXHR){
|
||||||
|
if (jqXHR.status == 204){
|
||||||
|
var chalid = $('.chal-id').val();
|
||||||
|
loadhints(chalid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadhints(chal){
|
||||||
|
$.get(script_root + '/admin/chal/{0}/hints'.format(chal), function(data){
|
||||||
|
var table = $('#hintsboard > tbody');
|
||||||
|
table.empty();
|
||||||
|
for (var i = 0; i < data.hints.length; i++) {
|
||||||
|
var hint = data.hints[i]
|
||||||
|
var hint_row = "<tr>" +
|
||||||
|
"<td class='hint-entry'>{0}</td>".format(hint.hint) +
|
||||||
|
"<td class='hint-cost'>{0}</td>".format(hint.cost) +
|
||||||
|
"<td class='hint-settings'><span>" +
|
||||||
|
"<i role='button' class='fa fa-pencil-square-o' onclick=javascript:load_hint_modal('update',{0})></i>".format(hint.id)+
|
||||||
|
"<i role='button' class='fa fa-times' onclick=javascript:deletehint({0})></i>".format(hint.id)+
|
||||||
|
"</span></td>" +
|
||||||
|
"</tr>";
|
||||||
|
table.append(hint_row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function deletechal(chalid){
|
function deletechal(chalid){
|
||||||
$.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
|
$.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
|
||||||
}
|
}
|
||||||
|
@ -255,6 +312,7 @@ function loadchals(){
|
||||||
$('#challenges button').click(function (e) {
|
$('#challenges button').click(function (e) {
|
||||||
loadchal(this.value);
|
loadchal(this.value);
|
||||||
loadkeys(this.value);
|
loadkeys(this.value);
|
||||||
|
loadhints(this.value);
|
||||||
loadtags(this.value);
|
loadtags(this.value);
|
||||||
loadfiles(this.value);
|
loadfiles(this.value);
|
||||||
});
|
});
|
||||||
|
@ -373,6 +431,25 @@ $('#create-keys-submit').click(function (e) {
|
||||||
create_key(chalid, key_data, key_type);
|
create_key(chalid, key_data, key_type);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$('#create-hint').click(function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
load_hint_modal('create');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#hint-modal-submit').submit(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var params = {}
|
||||||
|
$(this).serializeArray().map(function(x){
|
||||||
|
params[x.name] = x.value;
|
||||||
|
});
|
||||||
|
$.post(script_root + $(this).attr('action'), params, function(data){
|
||||||
|
loadhints(params['chal']);
|
||||||
|
});
|
||||||
|
$("#hint-modal").modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
$(function(){
|
$(function(){
|
||||||
loadchals();
|
loadchals();
|
||||||
})
|
})
|
||||||
|
|
|
@ -61,3 +61,22 @@ Handlebars.registerHelper('if_eq', function(a, b, opts) {
|
||||||
return opts.inverse(this);
|
return opts.inverse(this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// http://stepansuvorov.com/blog/2014/04/jquery-put-and-delete/
|
||||||
|
jQuery.each(["put", "delete"], function(i, method) {
|
||||||
|
jQuery[method] = function(url, data, callback, type) {
|
||||||
|
if (jQuery.isFunction(data)) {
|
||||||
|
type = type || callback;
|
||||||
|
callback = data;
|
||||||
|
data = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jQuery.ajax({
|
||||||
|
url: url,
|
||||||
|
type: method,
|
||||||
|
dataType: type,
|
||||||
|
data: data,
|
||||||
|
success: callback
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
|
@ -42,6 +42,7 @@ function updateChalWindow(obj) {
|
||||||
desc: marked(obj.description, {'gfm':true, 'breaks':true}),
|
desc: marked(obj.description, {'gfm':true, 'breaks':true}),
|
||||||
solves: solves,
|
solves: solves,
|
||||||
files: obj.files,
|
files: obj.files,
|
||||||
|
hints: obj.hints
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#chal-window').append(template(wrapper));
|
$('#chal-window').append(template(wrapper));
|
||||||
|
@ -226,7 +227,7 @@ function loadchals(refresh) {
|
||||||
var chalid = chalinfo.name.replace(/ /g,"-").hashCode();
|
var chalid = chalinfo.name.replace(/ /g,"-").hashCode();
|
||||||
var catid = chalinfo.category.replace(/ /g,"-").hashCode();
|
var catid = chalinfo.category.replace(/ /g,"-").hashCode();
|
||||||
var chalwrap = $("<div id='{0}' class='challenge-wrapper col-md-2'></div>".format(chalid));
|
var chalwrap = $("<div id='{0}' class='challenge-wrapper col-md-2'></div>".format(chalid));
|
||||||
var chalbutton = $("<button class='challenge-button trigger theme-background hide-text' value='{0}' data-toggle='modal' data-target='#chal-window'></div>".format(chalinfo.id));
|
var chalbutton = $("<button class='challenge-button trigger theme-background hide-text' value='{0}'></div>".format(chalinfo.id));
|
||||||
var chalheader = $("<h5>{0}</h5>".format(chalinfo.name));
|
var chalheader = $("<h5>{0}</h5>".format(chalinfo.name));
|
||||||
var chalscore = $("<span>{0}</span>".format(chalinfo.value));
|
var chalscore = $("<span>{0}</span>".format(chalinfo.value));
|
||||||
chalbutton.append(chalheader);
|
chalbutton.append(chalheader);
|
||||||
|
@ -254,6 +255,19 @@ function loadchals(refresh) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadhint(hintid){
|
||||||
|
if (confirm("Are you sure you want to open this hint?")){
|
||||||
|
$.post(script_root + "/hints/" + hintid, {'nonce': $('#nonce').val()}, function(data){
|
||||||
|
if (data.errors){
|
||||||
|
alert(data.errors);
|
||||||
|
} else {
|
||||||
|
$('#hint-modal-body').html(marked(data.hint, {'gfm':true, 'breaks':true}));
|
||||||
|
$('#hint-modal').modal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$('#submit-key').click(function (e) {
|
$('#submit-key').click(function (e) {
|
||||||
submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val())
|
submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val())
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
(function($, window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MultiModal = function(element) {
|
||||||
|
this.$element = $(element);
|
||||||
|
this.modalCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
MultiModal.BASE_ZINDEX = 1040;
|
||||||
|
|
||||||
|
MultiModal.prototype.show = function(target) {
|
||||||
|
var that = this;
|
||||||
|
var $target = $(target);
|
||||||
|
var modalIndex = that.modalCount++;
|
||||||
|
|
||||||
|
$target.css('z-index', MultiModal.BASE_ZINDEX + (modalIndex * 20) + 10);
|
||||||
|
|
||||||
|
window.setTimeout(function() {
|
||||||
|
if(modalIndex > 0)
|
||||||
|
$('.modal-backdrop').not(':first').addClass('hidden');
|
||||||
|
|
||||||
|
that.adjustBackdrop();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MultiModal.prototype.hidden = function(target) {
|
||||||
|
this.modalCount--;
|
||||||
|
|
||||||
|
if(this.modalCount) {
|
||||||
|
this.adjustBackdrop();
|
||||||
|
$('body').addClass('modal-open');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MultiModal.prototype.adjustBackdrop = function() {
|
||||||
|
var modalIndex = this.modalCount - 1;
|
||||||
|
$('.modal-backdrop:first').css('z-index', MultiModal.BASE_ZINDEX + (modalIndex * 20));
|
||||||
|
};
|
||||||
|
|
||||||
|
function Plugin(method, target) {
|
||||||
|
return this.each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var data = $this.data('multi-modal-plugin');
|
||||||
|
|
||||||
|
if(!data)
|
||||||
|
$this.data('multi-modal-plugin', (data = new MultiModal(this)));
|
||||||
|
|
||||||
|
if(method)
|
||||||
|
data[method](target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.multiModal = Plugin;
|
||||||
|
$.fn.multiModal.Constructor = MultiModal;
|
||||||
|
|
||||||
|
$(document).on('show.bs.modal', function(e) {
|
||||||
|
$(document).multiModal('show', e.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('hidden.bs.modal', function(e) {
|
||||||
|
$(document).multiModal('hidden', e.target);
|
||||||
|
});
|
||||||
|
}(jQuery, window));
|
|
@ -20,6 +20,29 @@
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<span class="chal-desc">{{{ desc }}}</span>
|
<span class="chal-desc">{{{ desc }}}</span>
|
||||||
|
<div class="chal-hints file-row row">
|
||||||
|
{{#each hints}}
|
||||||
|
<div class='col-md-12 file-button-wrapper'>
|
||||||
|
<a onclick="javascript:loadhint({{this.id}})">
|
||||||
|
{{#if this.hint }}
|
||||||
|
<label class='challenge-wrapper file-wrapper hide-text'>
|
||||||
|
View Hint
|
||||||
|
</label>
|
||||||
|
{{else}}
|
||||||
|
{{#if this.cost}}
|
||||||
|
<label class='challenge-wrapper file-wrapper hide-text'>
|
||||||
|
Unlock Hint for {{this.cost}} points
|
||||||
|
</label>
|
||||||
|
{{else}}
|
||||||
|
<label class='challenge-wrapper file-wrapper hide-text'>
|
||||||
|
View Hint
|
||||||
|
</label>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
<div class="chal-files file-row row">
|
<div class="chal-files file-row row">
|
||||||
{{#each files}}
|
{{#each files}}
|
||||||
<div class='col-md-3 file-button-wrapper'>
|
<div class='col-md-3 file-button-wrapper'>
|
||||||
|
|
|
@ -61,3 +61,22 @@ Handlebars.registerHelper('if_eq', function(a, b, opts) {
|
||||||
return opts.inverse(this);
|
return opts.inverse(this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// http://stepansuvorov.com/blog/2014/04/jquery-put-and-delete/
|
||||||
|
jQuery.each(["put", "delete"], function(i, method) {
|
||||||
|
jQuery[method] = function(url, data, callback, type) {
|
||||||
|
if (jQuery.isFunction(data)) {
|
||||||
|
type = type || callback;
|
||||||
|
callback = data;
|
||||||
|
data = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jQuery.ajax({
|
||||||
|
url: url,
|
||||||
|
type: method,
|
||||||
|
dataType: type,
|
||||||
|
data: data,
|
||||||
|
success: callback
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
|
@ -28,6 +28,7 @@
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
<h3>New Challenge</h3>
|
<h3>New Challenge</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
@ -162,6 +163,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<a href="#" data-toggle="modal" data-target="#update-tags" class="btn btn-primary">Tags</a>
|
<a href="#" data-toggle="modal" data-target="#update-tags" class="btn btn-primary">Tags</a>
|
||||||
<a href="#" data-toggle="modal" data-target="#update-files" class="btn btn-primary">Files</a>
|
<a href="#" data-toggle="modal" data-target="#update-files" class="btn btn-primary">Files</a>
|
||||||
|
<a href="#" data-toggle="modal" data-target="#update-hints" class="btn btn-primary">Hints</a>
|
||||||
<a href="#" data-toggle="modal" data-target="#update-keys" class="btn btn-primary">Keys</a>
|
<a href="#" data-toggle="modal" data-target="#update-keys" class="btn btn-primary">Keys</a>
|
||||||
<a href="#" data-toggle="modal" data-target="#delete-chal" class="btn btn-danger">Delete</a>
|
<a href="#" data-toggle="modal" data-target="#delete-chal" class="btn btn-danger">Delete</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -179,6 +181,7 @@
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header text-center">
|
<div class="modal-header text-center">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
<h3>Delete Challenge</h3>
|
<h3>Delete Challenge</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
@ -202,6 +205,7 @@
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<input type="hidden" class="chal-id" name="chal-id">
|
<input type="hidden" class="chal-id" name="chal-id">
|
||||||
<div class="modal-header text-center">
|
<div class="modal-header text-center">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
<h3>Create Key</h3>
|
<h3>Create Key</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
@ -275,6 +279,62 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="hint-modal" class="modal fade" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header text-center">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
<h3 id="hint-modal-title"></h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="hint-modal-submit" method='POST'>
|
||||||
|
<input type="hidden" class="chal-id" name="chal">
|
||||||
|
<div class="form-group">
|
||||||
|
<textarea id="hint-modal-hint" type="text" class="form-control" name="hint" placeholder="Hint"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input id="hint-modal-cost" type="number" class="form-control" name="cost" placeholder="Cost">
|
||||||
|
</div>
|
||||||
|
<div class="row" style="text-align:center;margin-top:20px">
|
||||||
|
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||||
|
<button class="btn btn-theme btn-outlined" id="hint-modal-button">Add Hint</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="update-hints" class="modal fade" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header text-center">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
<h3>Hints</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row" style="text-align:center">
|
||||||
|
<a href="#" id="create-hint" class="btn btn-primary" style="margin-bottom:15px;">New Hint</a>
|
||||||
|
</div>
|
||||||
|
<div class='current-hints'>
|
||||||
|
<table id="hintsboard" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><b>Hint</b></td>
|
||||||
|
<td class="text-center"><b>Cost</b></td>
|
||||||
|
<td class="text-center"><b>Settings</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-center">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="update-tags" class="modal fade" tabindex="-1">
|
<div id="update-tags" class="modal fade" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -305,33 +365,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="email-user" class="modal fade" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header text-center">
|
|
||||||
<h3>Tags</h3>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<input type="text" class="tag-insert" maxlength="80" placeholder="Type tag and press Enter">
|
|
||||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
|
||||||
<input id="tags-chal" name='chal' type='hidden'>
|
|
||||||
|
|
||||||
<div id="current-tags">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<div id="chal-tags">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<a href="#" id="submit-tags" class="button">Update</a>
|
|
||||||
<a class="close-reveal-modal">×</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="text-align:center">
|
<div style="text-align:center">
|
||||||
<br>
|
<br>
|
||||||
<h1 class="text-center">Challenges</h1>
|
<h1 class="text-center">Challenges</h1>
|
||||||
|
|
|
@ -103,6 +103,19 @@
|
||||||
|
|
||||||
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
|
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
|
||||||
|
|
||||||
|
<div class="modal fade" id="hint-modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header text-center">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
<h3>Hint</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="hint-modal-body">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="chal-window" tabindex="-1" role="dialog">
|
<div class="modal fade" id="chal-window" tabindex="-1" role="dialog">
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -110,6 +123,7 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
||||||
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/multi-modal.js"></script>
|
||||||
{% if not errors %}<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/chalboard.js"></script>{% endif %}
|
{% if not errors %}<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/chalboard.js"></script>{% endif %}
|
||||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -10,7 +10,8 @@ config = context.config
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
# Interpret the config file for Python logging.
|
||||||
# This line sets up loggers basically.
|
# This line sets up loggers basically.
|
||||||
fileConfig(config.config_file_name)
|
## http://stackoverflow.com/questions/42427487/using-alembic-config-main-redirects-log-output
|
||||||
|
# fileConfig(config.config_file_name)
|
||||||
logger = logging.getLogger('alembic.env')
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
# add your model's MetaData object here
|
# add your model's MetaData object here
|
||||||
|
|
|
@ -64,7 +64,11 @@ def upgrade():
|
||||||
|
|
||||||
## Drop flags from challenges
|
## Drop flags from challenges
|
||||||
print("Dropping flags column from challenges")
|
print("Dropping flags column from challenges")
|
||||||
op.drop_column('challenges', 'flags')
|
try:
|
||||||
|
op.drop_column('challenges', 'flags')
|
||||||
|
except sa.exc.OperationalError:
|
||||||
|
print("Failed to drop flags. Likely due to SQLite")
|
||||||
|
|
||||||
|
|
||||||
print("Finished")
|
print("Finished")
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
"""Adding Hints and Unlocks tables
|
||||||
|
|
||||||
|
Revision ID: c7225db614c1
|
||||||
|
Revises: d6514ec92738
|
||||||
|
Create Date: 2017-03-23 01:31:43.940187
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'c7225db614c1'
|
||||||
|
down_revision = 'd6514ec92738'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('hints',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('type', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('chal', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('hint', sa.Text(), nullable=True),
|
||||||
|
sa.Column('cost', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('unlocks',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('itemid', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('model', sa.String(length=32), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('unlocks')
|
||||||
|
op.drop_table('hints')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -18,142 +18,127 @@ depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
app = create_app()
|
op.create_table('challenges',
|
||||||
engine = sa.create_engine(app.config.get('SQLALCHEMY_DATABASE_URI'))
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
sa.Column('name', sa.String(length=80), nullable=True),
|
||||||
if not engine.dialect.has_table(engine, 'challenges'):
|
sa.Column('description', sa.Text(), nullable=True),
|
||||||
op.create_table('challenges',
|
sa.Column('value', sa.Integer(), nullable=True),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('category', sa.String(length=80), nullable=True),
|
||||||
sa.Column('name', sa.String(length=80), nullable=True),
|
sa.Column('flags', sa.Text(), nullable=True),
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
sa.Column('hidden', sa.Boolean(), nullable=True),
|
||||||
sa.Column('value', sa.Integer(), nullable=True),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.Column('category', sa.String(length=80), nullable=True),
|
)
|
||||||
sa.Column('flags', sa.Text(), nullable=True),
|
|
||||||
sa.Column('hidden', sa.Boolean(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'config'):
|
op.create_table('config',
|
||||||
op.create_table('config',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('key', sa.Text(), nullable=True),
|
||||||
sa.Column('key', sa.Text(), nullable=True),
|
sa.Column('value', sa.Text(), nullable=True),
|
||||||
sa.Column('value', sa.Text(), nullable=True),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'containers'):
|
op.create_table('containers',
|
||||||
op.create_table('containers',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('name', sa.String(length=80), nullable=True),
|
||||||
sa.Column('name', sa.String(length=80), nullable=True),
|
sa.Column('buildfile', sa.Text(), nullable=True),
|
||||||
sa.Column('buildfile', sa.Text(), nullable=True),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'pages'):
|
op.create_table('pages',
|
||||||
op.create_table('pages',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('route', sa.String(length=80), nullable=True),
|
||||||
sa.Column('route', sa.String(length=80), nullable=True),
|
sa.Column('html', sa.Text(), nullable=True),
|
||||||
sa.Column('html', sa.Text(), nullable=True),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.UniqueConstraint('route')
|
||||||
sa.UniqueConstraint('route')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'teams'):
|
op.create_table('teams',
|
||||||
op.create_table('teams',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('name', sa.String(length=128), nullable=True),
|
||||||
sa.Column('name', sa.String(length=128), nullable=True),
|
sa.Column('email', sa.String(length=128), nullable=True),
|
||||||
sa.Column('email', sa.String(length=128), nullable=True),
|
sa.Column('password', sa.String(length=128), nullable=True),
|
||||||
sa.Column('password', sa.String(length=128), nullable=True),
|
sa.Column('website', sa.String(length=128), nullable=True),
|
||||||
sa.Column('website', sa.String(length=128), nullable=True),
|
sa.Column('affiliation', sa.String(length=128), nullable=True),
|
||||||
sa.Column('affiliation', sa.String(length=128), nullable=True),
|
sa.Column('country', sa.String(length=32), nullable=True),
|
||||||
sa.Column('country', sa.String(length=32), nullable=True),
|
sa.Column('bracket', sa.String(length=32), nullable=True),
|
||||||
sa.Column('bracket', sa.String(length=32), nullable=True),
|
sa.Column('banned', sa.Boolean(), nullable=True),
|
||||||
sa.Column('banned', sa.Boolean(), nullable=True),
|
sa.Column('verified', sa.Boolean(), nullable=True),
|
||||||
sa.Column('verified', sa.Boolean(), nullable=True),
|
sa.Column('admin', sa.Boolean(), nullable=True),
|
||||||
sa.Column('admin', sa.Boolean(), nullable=True),
|
sa.Column('joined', sa.DateTime(), nullable=True),
|
||||||
sa.Column('joined', sa.DateTime(), nullable=True),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.UniqueConstraint('email'),
|
||||||
sa.UniqueConstraint('email'),
|
sa.UniqueConstraint('name')
|
||||||
sa.UniqueConstraint('name')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'awards'):
|
op.create_table('awards',
|
||||||
op.create_table('awards',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
sa.Column('name', sa.String(length=80), nullable=True),
|
||||||
sa.Column('name', sa.String(length=80), nullable=True),
|
sa.Column('description', sa.Text(), nullable=True),
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
sa.Column('date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('date', sa.DateTime(), nullable=True),
|
sa.Column('value', sa.Integer(), nullable=True),
|
||||||
sa.Column('value', sa.Integer(), nullable=True),
|
sa.Column('category', sa.String(length=80), nullable=True),
|
||||||
sa.Column('category', sa.String(length=80), nullable=True),
|
sa.Column('icon', sa.Text(), nullable=True),
|
||||||
sa.Column('icon', sa.Text(), nullable=True),
|
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'files'):
|
op.create_table('files',
|
||||||
op.create_table('files',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('chal', sa.Integer(), nullable=True),
|
||||||
sa.Column('chal', sa.Integer(), nullable=True),
|
sa.Column('location', sa.Text(), nullable=True),
|
||||||
sa.Column('location', sa.Text(), nullable=True),
|
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'keys'):
|
op.create_table('keys',
|
||||||
op.create_table('keys',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('chal', sa.Integer(), nullable=True),
|
||||||
sa.Column('chal', sa.Integer(), nullable=True),
|
sa.Column('key_type', sa.Integer(), nullable=True),
|
||||||
sa.Column('key_type', sa.Integer(), nullable=True),
|
sa.Column('flag', sa.Text(), nullable=True),
|
||||||
sa.Column('flag', sa.Text(), nullable=True),
|
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'solves'):
|
op.create_table('solves',
|
||||||
op.create_table('solves',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('chalid', sa.Integer(), nullable=True),
|
||||||
sa.Column('chalid', sa.Integer(), nullable=True),
|
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
sa.Column('ip', sa.Integer(), nullable=True),
|
||||||
sa.Column('ip', sa.Integer(), nullable=True),
|
sa.Column('flag', sa.Text(), nullable=True),
|
||||||
sa.Column('flag', sa.Text(), nullable=True),
|
sa.Column('date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('date', sa.DateTime(), nullable=True),
|
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
||||||
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.UniqueConstraint('chalid', 'teamid')
|
||||||
sa.UniqueConstraint('chalid', 'teamid')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'tags'):
|
op.create_table('tags',
|
||||||
op.create_table('tags',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('chal', sa.Integer(), nullable=True),
|
||||||
sa.Column('chal', sa.Integer(), nullable=True),
|
sa.Column('tag', sa.String(length=80), nullable=True),
|
||||||
sa.Column('tag', sa.String(length=80), nullable=True),
|
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'tracking'):
|
op.create_table('tracking',
|
||||||
op.create_table('tracking',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('ip', sa.BigInteger(), nullable=True),
|
||||||
sa.Column('ip', sa.BigInteger(), nullable=True),
|
sa.Column('team', sa.Integer(), nullable=True),
|
||||||
sa.Column('team', sa.Integer(), nullable=True),
|
sa.Column('date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('date', sa.DateTime(), nullable=True),
|
sa.ForeignKeyConstraint(['team'], ['teams.id'], ),
|
||||||
sa.ForeignKeyConstraint(['team'], ['teams.id'], ),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not engine.dialect.has_table(engine, 'wrong_keys'):
|
op.create_table('wrong_keys',
|
||||||
op.create_table('wrong_keys',
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('chalid', sa.Integer(), nullable=True),
|
||||||
sa.Column('chalid', sa.Integer(), nullable=True),
|
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
sa.Column('date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('date', sa.DateTime(), nullable=True),
|
sa.Column('flag', sa.Text(), nullable=True),
|
||||||
sa.Column('flag', sa.Text(), nullable=True),
|
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
||||||
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
sa.PrimaryKeyConstraint('id')
|
||||||
sa.PrimaryKeyConstraint('id')
|
)
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
|
2
serve.py
2
serve.py
|
@ -1,4 +1,4 @@
|
||||||
from CTFd import create_app
|
from CTFd import create_app
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
app.run(debug=True, threaded=True, host="0.0.0.0", port=4000)
|
app.run(debug=True, threaded=True, host="127.0.0.1", port=4000)
|
||||||
|
|
|
@ -203,5 +203,5 @@ def test_viewing_challenges():
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
gen_challenge(app.db)
|
gen_challenge(app.db)
|
||||||
r = client.get('/chals')
|
r = client.get('/chals')
|
||||||
chals = json.loads(r.data)
|
chals = json.loads(r.get_data(as_text=True))
|
||||||
assert len(chals['game']) == 1
|
assert len(chals['game']) == 1
|
Loading…
Reference in New Issue