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
|
||||
python:
|
||||
- 2.7
|
||||
- 3.6
|
||||
- 3.5
|
||||
install:
|
||||
- pip install -r development.txt
|
||||
script:
|
||||
|
|
|
@ -34,23 +34,26 @@ def create_app(config='CTFd.config.Config'):
|
|||
if url.drivername == 'postgres':
|
||||
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)
|
||||
|
||||
try:
|
||||
if not (url.drivername.startswith('sqlite') or database_exists(url)):
|
||||
create_database(url)
|
||||
db.create_all()
|
||||
except OperationalError:
|
||||
db.create_all()
|
||||
except ProgrammingError: ## Database already exists
|
||||
pass
|
||||
else:
|
||||
## Register Flask-Migrate
|
||||
migrate.init_app(app, db)
|
||||
|
||||
## This creates tables instead of db.create_all()
|
||||
## Allows migrations to happen properly
|
||||
migrate_upgrade()
|
||||
|
||||
## Alembic sqlite support is lacking so we should just create_all anyway
|
||||
if url.drivername.startswith('sqlite'):
|
||||
db.create_all()
|
||||
|
||||
app.db = db
|
||||
|
||||
migrate.init_app(app, db)
|
||||
|
||||
cache.init_app(app)
|
||||
app.cache = cache
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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.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.challenges import get_chal_class, CHALLENGE_CLASSES
|
||||
|
||||
|
@ -87,6 +87,66 @@ def admin_delete_tags(tagid):
|
|||
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'])
|
||||
@admins_only
|
||||
def admin_files(chalid):
|
||||
|
@ -125,13 +185,34 @@ def admin_get_values(chalid, prop):
|
|||
chal_keys = Keys.query.filter_by(chal=challenge.id).all()
|
||||
json_data = {'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)
|
||||
elif prop == 'tags':
|
||||
tags = Tags.query.filter_by(chal=chalid).all()
|
||||
json_data = {'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)
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import time
|
|||
from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint
|
||||
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.challenges import get_chal_class
|
||||
|
||||
|
@ -14,6 +14,49 @@ from CTFd import utils
|
|||
|
||||
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'])
|
||||
def challenges_view():
|
||||
|
@ -57,6 +100,14 @@ def chals():
|
|||
for x in chals:
|
||||
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()]
|
||||
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)
|
||||
json['game'].append({
|
||||
'id': x.id,
|
||||
|
@ -66,7 +117,8 @@ def chals():
|
|||
'description': x.description,
|
||||
'category': x.category,
|
||||
'files': files,
|
||||
'tags': tags
|
||||
'tags': tags,
|
||||
'hints': hints
|
||||
})
|
||||
|
||||
db.session.close()
|
||||
|
|
|
@ -32,7 +32,7 @@ class Config(object):
|
|||
|
||||
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__))
|
||||
|
||||
|
||||
'''
|
||||
|
@ -133,4 +133,4 @@ class TestingConfig(Config):
|
|||
PRESERVE_CONTEXT_ON_EXCEPTION = False
|
||||
TESTING = True
|
||||
DEBUG = True
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite://'
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite://'
|
||||
|
|
|
@ -76,6 +76,23 @@ class Challenges(db.Model):
|
|||
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):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
||||
|
@ -244,6 +261,22 @@ class WrongKeys(db.Model):
|
|||
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):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
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) {
|
||||
// $('#chal *').show()
|
||||
// $('#chal > h1').hide()
|
||||
|
@ -169,6 +188,44 @@ function deletetag(tagid){
|
|||
$.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){
|
||||
$.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
|
||||
}
|
||||
|
@ -255,6 +312,7 @@ function loadchals(){
|
|||
$('#challenges button').click(function (e) {
|
||||
loadchal(this.value);
|
||||
loadkeys(this.value);
|
||||
loadhints(this.value);
|
||||
loadtags(this.value);
|
||||
loadfiles(this.value);
|
||||
});
|
||||
|
@ -373,6 +431,25 @@ $('#create-keys-submit').click(function (e) {
|
|||
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(){
|
||||
loadchals();
|
||||
})
|
||||
|
|
|
@ -60,4 +60,23 @@ Handlebars.registerHelper('if_eq', function(a, b, opts) {
|
|||
} else {
|
||||
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}),
|
||||
solves: solves,
|
||||
files: obj.files,
|
||||
hints: obj.hints
|
||||
};
|
||||
|
||||
$('#chal-window').append(template(wrapper));
|
||||
|
@ -226,7 +227,7 @@ function loadchals(refresh) {
|
|||
var chalid = chalinfo.name.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 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 chalscore = $("<span>{0}</span>".format(chalinfo.value));
|
||||
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) {
|
||||
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}}
|
||||
</div>
|
||||
<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">
|
||||
{{#each files}}
|
||||
<div class='col-md-3 file-button-wrapper'>
|
||||
|
|
|
@ -60,4 +60,23 @@ Handlebars.registerHelper('if_eq', function(a, b, opts) {
|
|||
} else {
|
||||
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-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>New Challenge</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
@ -162,6 +163,7 @@
|
|||
<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-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="#delete-chal" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
|
@ -179,6 +181,7 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Delete Challenge</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
@ -202,6 +205,7 @@
|
|||
<div class="modal-content">
|
||||
<input type="hidden" class="chal-id" name="chal-id">
|
||||
<div class="modal-header text-center">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>Create Key</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
@ -275,6 +279,62 @@
|
|||
</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 class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
@ -305,33 +365,6 @@
|
|||
</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">
|
||||
<br>
|
||||
<h1 class="text-center">Challenges</h1>
|
||||
|
|
|
@ -103,6 +103,19 @@
|
|||
|
||||
<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>
|
||||
{% endif %}
|
||||
|
@ -110,6 +123,7 @@
|
|||
|
||||
{% block scripts %}
|
||||
<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 %}
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -10,7 +10,8 @@ config = context.config
|
|||
|
||||
# Interpret the config file for Python logging.
|
||||
# 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')
|
||||
|
||||
# add your model's MetaData object here
|
||||
|
|
|
@ -64,7 +64,11 @@ def upgrade():
|
|||
|
||||
## Drop flags 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")
|
||||
# ### 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():
|
||||
app = create_app()
|
||||
engine = sa.create_engine(app.config.get('SQLALCHEMY_DATABASE_URI'))
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
if not engine.dialect.has_table(engine, 'challenges'):
|
||||
op.create_table('challenges',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=80), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('value', sa.Integer(), nullable=True),
|
||||
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')
|
||||
)
|
||||
op.create_table('challenges',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=80), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('value', sa.Integer(), nullable=True),
|
||||
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',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.Text(), nullable=True),
|
||||
sa.Column('value', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('config',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.Text(), nullable=True),
|
||||
sa.Column('value', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'containers'):
|
||||
op.create_table('containers',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=80), nullable=True),
|
||||
sa.Column('buildfile', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('containers',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=80), nullable=True),
|
||||
sa.Column('buildfile', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'pages'):
|
||||
op.create_table('pages',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('route', sa.String(length=80), nullable=True),
|
||||
sa.Column('html', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('route')
|
||||
)
|
||||
op.create_table('pages',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('route', sa.String(length=80), nullable=True),
|
||||
sa.Column('html', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('route')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'teams'):
|
||||
op.create_table('teams',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', 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('website', 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('bracket', sa.String(length=32), nullable=True),
|
||||
sa.Column('banned', sa.Boolean(), nullable=True),
|
||||
sa.Column('verified', sa.Boolean(), nullable=True),
|
||||
sa.Column('admin', sa.Boolean(), nullable=True),
|
||||
sa.Column('joined', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email'),
|
||||
sa.UniqueConstraint('name')
|
||||
)
|
||||
op.create_table('teams',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', 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('website', 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('bracket', sa.String(length=32), nullable=True),
|
||||
sa.Column('banned', sa.Boolean(), nullable=True),
|
||||
sa.Column('verified', sa.Boolean(), nullable=True),
|
||||
sa.Column('admin', sa.Boolean(), nullable=True),
|
||||
sa.Column('joined', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email'),
|
||||
sa.UniqueConstraint('name')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'awards'):
|
||||
op.create_table('awards',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||
sa.Column('name', sa.String(length=80), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.Column('value', sa.Integer(), nullable=True),
|
||||
sa.Column('category', sa.String(length=80), nullable=True),
|
||||
sa.Column('icon', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('awards',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||
sa.Column('name', sa.String(length=80), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.Column('value', sa.Integer(), nullable=True),
|
||||
sa.Column('category', sa.String(length=80), nullable=True),
|
||||
sa.Column('icon', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'files'):
|
||||
op.create_table('files',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chal', sa.Integer(), nullable=True),
|
||||
sa.Column('location', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('files',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chal', sa.Integer(), nullable=True),
|
||||
sa.Column('location', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'keys'):
|
||||
op.create_table('keys',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chal', sa.Integer(), nullable=True),
|
||||
sa.Column('key_type', sa.Integer(), nullable=True),
|
||||
sa.Column('flag', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('keys',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chal', sa.Integer(), nullable=True),
|
||||
sa.Column('key_type', sa.Integer(), nullable=True),
|
||||
sa.Column('flag', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'solves'):
|
||||
op.create_table('solves',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chalid', sa.Integer(), nullable=True),
|
||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||
sa.Column('ip', sa.Integer(), nullable=True),
|
||||
sa.Column('flag', sa.Text(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('chalid', 'teamid')
|
||||
)
|
||||
op.create_table('solves',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chalid', sa.Integer(), nullable=True),
|
||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||
sa.Column('ip', sa.Integer(), nullable=True),
|
||||
sa.Column('flag', sa.Text(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('chalid', 'teamid')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'tags'):
|
||||
op.create_table('tags',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chal', sa.Integer(), nullable=True),
|
||||
sa.Column('tag', sa.String(length=80), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('tags',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chal', sa.Integer(), nullable=True),
|
||||
sa.Column('tag', sa.String(length=80), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chal'], ['challenges.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'tracking'):
|
||||
op.create_table('tracking',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('ip', sa.BigInteger(), nullable=True),
|
||||
sa.Column('team', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['team'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('tracking',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('ip', sa.BigInteger(), nullable=True),
|
||||
sa.Column('team', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['team'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
if not engine.dialect.has_table(engine, 'wrong_keys'):
|
||||
op.create_table('wrong_keys',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chalid', sa.Integer(), nullable=True),
|
||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.Column('flag', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('wrong_keys',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('chalid', sa.Integer(), nullable=True),
|
||||
sa.Column('teamid', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.Column('flag', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['chalid'], ['challenges.id'], ),
|
||||
sa.ForeignKeyConstraint(['teamid'], ['teams.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
|
|
2
serve.py
2
serve.py
|
@ -1,4 +1,4 @@
|
|||
from CTFd import 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)
|
||||
gen_challenge(app.db)
|
||||
r = client.get('/chals')
|
||||
chals = json.loads(r.data)
|
||||
assert len(chals['game']) == 1
|
||||
chals = json.loads(r.get_data(as_text=True))
|
||||
assert len(chals['game']) == 1
|
||||
|
|
Loading…
Reference in New Issue