* 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 users
selenium-screenshot-testing
Kevin Chung 2017-03-28 21:17:56 -04:00 committed by GitHub
parent 9a9b775e57
commit f48a0cdacd
20 changed files with 644 additions and 177 deletions

View File

@ -1,7 +1,7 @@
language: python
python:
- 2.7
- 3.6
- 3.5
install:
- pip install -r development.txt
script:

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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__))
'''

View File

@ -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)

View File

@ -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();
})

View File

@ -61,3 +61,22 @@ Handlebars.registerHelper('if_eq', function(a, b, opts) {
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
});
};
});

View File

@ -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())
});

View File

@ -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));

View File

@ -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'>

View File

@ -61,3 +61,22 @@ Handlebars.registerHelper('if_eq', function(a, b, opts) {
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
});
};
});

View File

@ -28,6 +28,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&#215;</a>
</div>
</div>
</div>
</div>
<div style="text-align:center">
<br>
<h1 class="text-center">Challenges</h1>

View File

@ -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">&times;</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 %}

View File

@ -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

View File

@ -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 ###

View File

@ -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 ###

View File

@ -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 ###

View File

@ -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)

View File

@ -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)
chals = json.loads(r.get_data(as_text=True))
assert len(chals['game']) == 1