mirror of https://github.com/JohnHammond/CTFd.git
Marking 1.0.0 (#196)
* Use <int:xxx> in routes to prevent some errors 500 (#192) * Use first_or_404() to prevent some errors 500 (#193) * Add a populating script for awards. (#191) * Creating upload_file util * Marking 1.0.0 in __init__ and starting database migrations * Upgrading some more HTML * Adding CHANGELOG.mdselenium-screenshot-testing 1.0.0
parent
01cb189b22
commit
935027c55d
|
@ -0,0 +1,19 @@
|
|||
1.0.0 / 2017-01-24
|
||||
==================
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- 1.0.0 release! Things work!
|
||||
- Manage everything from a browser
|
||||
- Run Containers
|
||||
- Themes
|
||||
- Plugins
|
||||
- Database migrations
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Closed out 94 issues before tagging 1.0.0
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Merged 42 pull requests before tagging 1.0.0
|
|
@ -1,13 +1,15 @@
|
|||
import os
|
||||
|
||||
from distutils.version import StrictVersion
|
||||
from flask import Flask
|
||||
from jinja2 import FileSystemLoader
|
||||
from sqlalchemy.engine.url import make_url
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlalchemy_utils import database_exists, create_database
|
||||
|
||||
from utils import get_config, set_config, cache
|
||||
from utils import get_config, set_config, cache, migrate, migrate_upgrade
|
||||
|
||||
__version__ = '1.0.0'
|
||||
|
||||
class ThemeLoader(FileSystemLoader):
|
||||
def get_source(self, environment, template):
|
||||
|
@ -45,14 +47,23 @@ def create_app(config='CTFd.config.Config'):
|
|||
|
||||
app.db = db
|
||||
|
||||
migrate.init_app(app, db)
|
||||
|
||||
cache.init_app(app)
|
||||
app.cache = cache
|
||||
|
||||
version = get_config('ctf_version')
|
||||
|
||||
if not version: ## Upgrading from an unversioned CTFd
|
||||
set_config('ctf_version', __version__)
|
||||
|
||||
if version and (StrictVersion(version) < StrictVersion(__version__)): ## Upgrading from an older version of CTFd
|
||||
migrate_upgrade()
|
||||
set_config('ctf_version', __version__)
|
||||
|
||||
if not get_config('ctf_theme'):
|
||||
set_config('ctf_theme', 'original')
|
||||
|
||||
#Session(app)
|
||||
|
||||
from CTFd.views import views
|
||||
from CTFd.challenges import challenges
|
||||
from CTFd.scoreboard import scoreboard
|
||||
|
|
106
CTFd/admin.py
106
CTFd/admin.py
|
@ -5,11 +5,10 @@ import os
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from passlib.hash import bcrypt_sha256
|
||||
from sqlalchemy.sql import not_
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \
|
||||
set_config, sendmail, rmdir, create_image, delete_image, run_image, container_status, container_ports, \
|
||||
container_stop, container_start, get_themes, cache
|
||||
container_stop, container_start, get_themes, cache, upload_file
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.scoreboard import get_standings
|
||||
|
||||
|
@ -209,7 +208,7 @@ def admin_pages(route):
|
|||
@admin.route('/admin/page/<pageroute>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_page(pageroute):
|
||||
page = Pages.query.filter_by(route=pageroute).first()
|
||||
page = Pages.query.filter_by(route=pageroute).first_or_404()
|
||||
db.session.delete(page)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
@ -226,7 +225,7 @@ def list_container():
|
|||
return render_template('admin/containers.html', containers=containers)
|
||||
|
||||
|
||||
@admin.route('/admin/containers/<container_id>/stop', methods=['POST'])
|
||||
@admin.route('/admin/containers/<int:container_id>/stop', methods=['POST'])
|
||||
@admins_only
|
||||
def stop_container(container_id):
|
||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||
|
@ -236,7 +235,7 @@ def stop_container(container_id):
|
|||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/containers/<container_id>/start', methods=['POST'])
|
||||
@admin.route('/admin/containers/<int:container_id>/start', methods=['POST'])
|
||||
@admins_only
|
||||
def run_container(container_id):
|
||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||
|
@ -252,7 +251,7 @@ def run_container(container_id):
|
|||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/containers/<container_id>/delete', methods=['POST'])
|
||||
@admin.route('/admin/containers/<int:container_id>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_container(container_id):
|
||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||
|
@ -310,19 +309,18 @@ def admin_chals():
|
|||
return render_template('admin/chals.html')
|
||||
|
||||
|
||||
@admin.route('/admin/keys/<chalid>', methods=['POST', 'GET'])
|
||||
@admin.route('/admin/keys/<int:chalid>', methods=['POST', 'GET'])
|
||||
@admins_only
|
||||
def admin_keys(chalid):
|
||||
if request.method == 'GET':
|
||||
chal = Challenges.query.filter_by(id=chalid).first_or_404()
|
||||
|
||||
if request.method == 'GET':
|
||||
json_data = {'keys': []}
|
||||
flags = json.loads(chal.flags)
|
||||
for i, x in enumerate(flags):
|
||||
json_data['keys'].append({'id': i, 'key': x['flag'], 'type': x['type']})
|
||||
return jsonify(json_data)
|
||||
elif request.method == 'POST':
|
||||
chal = Challenges.query.filter_by(id=chalid).first()
|
||||
|
||||
newkeys = request.form.getlist('keys[]')
|
||||
newvals = request.form.getlist('vals[]')
|
||||
flags = []
|
||||
|
@ -338,7 +336,7 @@ def admin_keys(chalid):
|
|||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/tags/<chalid>', methods=['GET', 'POST'])
|
||||
@admin.route('/admin/tags/<int:chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_tags(chalid):
|
||||
if request.method == 'GET':
|
||||
|
@ -358,7 +356,7 @@ def admin_tags(chalid):
|
|||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/tags/<tagid>/delete', methods=['POST'])
|
||||
@admin.route('/admin/tags/<int:tagid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_tags(tagid):
|
||||
if request.method == 'POST':
|
||||
|
@ -369,7 +367,7 @@ def admin_delete_tags(tagid):
|
|||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/files/<chalid>', methods=['GET', 'POST'])
|
||||
@admin.route('/admin/files/<int:chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_files(chalid):
|
||||
if request.method == 'GET':
|
||||
|
@ -391,19 +389,7 @@ def admin_files(chalid):
|
|||
files = request.files.getlist('files[]')
|
||||
|
||||
for f in files:
|
||||
filename = secure_filename(f.filename)
|
||||
|
||||
if len(filename) <= 0:
|
||||
continue
|
||||
|
||||
md5hash = hashlib.md5(os.urandom(64)).hexdigest()
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash)):
|
||||
os.makedirs(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash))
|
||||
|
||||
f.save(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash, filename))
|
||||
db_f = Files(chalid, (md5hash + '/' + filename))
|
||||
db.session.add(db_f)
|
||||
upload_file(file=f, chalid=chalid)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
@ -411,7 +397,7 @@ def admin_files(chalid):
|
|||
|
||||
|
||||
@admin.route('/admin/teams', defaults={'page': '1'})
|
||||
@admin.route('/admin/teams/<page>')
|
||||
@admin.route('/admin/teams/<int:page>')
|
||||
@admins_only
|
||||
def admin_teams(page):
|
||||
page = abs(int(page))
|
||||
|
@ -425,10 +411,10 @@ def admin_teams(page):
|
|||
return render_template('admin/teams.html', teams=teams, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@admin.route('/admin/team/<teamid>', methods=['GET', 'POST'])
|
||||
@admin.route('/admin/team/<int:teamid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_team(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||
|
||||
if request.method == 'GET':
|
||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||
|
@ -497,7 +483,7 @@ def admin_team(teamid):
|
|||
return jsonify({'data': ['success']})
|
||||
|
||||
|
||||
@admin.route('/admin/team/<teamid>/mail', methods=['POST'])
|
||||
@admin.route('/admin/team/<int:teamid>/mail', methods=['POST'])
|
||||
@admins_only
|
||||
def email_user(teamid):
|
||||
message = request.form.get('msg', None)
|
||||
|
@ -508,27 +494,27 @@ def email_user(teamid):
|
|||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/team/<teamid>/ban', methods=['POST'])
|
||||
@admin.route('/admin/team/<int:teamid>/ban', methods=['POST'])
|
||||
@admins_only
|
||||
def ban(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||
user.banned = True
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_scoreboard'))
|
||||
|
||||
|
||||
@admin.route('/admin/team/<teamid>/unban', methods=['POST'])
|
||||
@admin.route('/admin/team/<int:teamid>/unban', methods=['POST'])
|
||||
@admins_only
|
||||
def unban(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||
user.banned = False
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_scoreboard'))
|
||||
|
||||
|
||||
@admin.route('/admin/team/<teamid>/delete', methods=['POST'])
|
||||
@admin.route('/admin/team/<int:teamid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_team(teamid):
|
||||
try:
|
||||
|
@ -572,7 +558,7 @@ def admin_scoreboard():
|
|||
return render_template('admin/scoreboard.html', teams=standings)
|
||||
|
||||
|
||||
@admin.route('/admin/teams/<teamid>/awards', methods=['GET'])
|
||||
@admin.route('/admin/teams/<int:teamid>/awards', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_awards(teamid):
|
||||
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||
|
@ -611,18 +597,14 @@ def create_award():
|
|||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/awards/<award_id>/delete', methods=['POST'])
|
||||
@admin.route('/admin/awards/<int:award_id>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_award(award_id):
|
||||
try:
|
||||
award = Awards.query.filter_by(id=award_id).first()
|
||||
award = Awards.query.filter_by(id=award_id).first_or_404()
|
||||
db.session.delete(award)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/scores')
|
||||
|
@ -671,7 +653,7 @@ def admin_solves(teamid="all"):
|
|||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/solves/<teamid>/<chalid>/solve', methods=['POST'])
|
||||
@admin.route('/admin/solves/<int:teamid>/<int:chalid>/solve', methods=['POST'])
|
||||
@admins_only
|
||||
def create_solve(teamid, chalid):
|
||||
solve = Solves(chalid=chalid, teamid=teamid, ip='127.0.0.1', flag='MARKED_AS_SOLVED_BY_ADMIN')
|
||||
|
@ -681,7 +663,7 @@ def create_solve(teamid, chalid):
|
|||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/solves/<keyid>/delete', methods=['POST'])
|
||||
@admin.route('/admin/solves/<int:keyid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_solve(keyid):
|
||||
solve = Solves.query.filter_by(id=keyid).first_or_404()
|
||||
|
@ -691,7 +673,7 @@ def delete_solve(keyid):
|
|||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/wrong_keys/<keyid>/delete', methods=['POST'])
|
||||
@admin.route('/admin/wrong_keys/<int:keyid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_wrong_key(keyid):
|
||||
wrong_key = WrongKeys.query.filter_by(id=keyid).first_or_404()
|
||||
|
@ -737,9 +719,10 @@ def admin_stats():
|
|||
least_solved=least_solved)
|
||||
|
||||
|
||||
@admin.route('/admin/wrong_keys/<page>', methods=['GET'])
|
||||
@admin.route('/admin/wrong_keys', defaults={'page': '1'}, methods=['GET'])
|
||||
@admin.route('/admin/wrong_keys/<int:page>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_wrong_key(page='1'):
|
||||
def admin_wrong_key(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
|
@ -759,9 +742,10 @@ def admin_wrong_key(page='1'):
|
|||
return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@admin.route('/admin/correct_keys/<page>', methods=['GET'])
|
||||
@admin.route('/admin/correct_keys', defaults={'page': '1'}, methods=['GET'])
|
||||
@admin.route('/admin/correct_keys/<int:page>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_correct_key(page='1'):
|
||||
def admin_correct_key(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
|
@ -781,9 +765,10 @@ def admin_correct_key(page='1'):
|
|||
return render_template('admin/correct_keys.html', solves=solves, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@admin.route('/admin/fails/<teamid>', methods=['GET'])
|
||||
@admin.route('/admin/fails/all', defaults={'teamid': 'all'}, methods=['GET'])
|
||||
@admin.route('/admin/fails/<int:teamid>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_fails(teamid='all'):
|
||||
def admin_fails(teamid):
|
||||
if teamid == "all":
|
||||
fails = WrongKeys.query.join(Teams, WrongKeys.teamid == Teams.id).filter(Teams.banned == False).count()
|
||||
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).count()
|
||||
|
@ -816,19 +801,7 @@ def admin_create_chal():
|
|||
db.session.commit()
|
||||
|
||||
for f in files:
|
||||
filename = secure_filename(f.filename)
|
||||
|
||||
if len(filename) <= 0:
|
||||
continue
|
||||
|
||||
md5hash = hashlib.md5(os.urandom(64)).hexdigest()
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash)):
|
||||
os.makedirs(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash))
|
||||
|
||||
f.save(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash, filename))
|
||||
db_f = Files(chal.id, (md5hash + '/' + filename))
|
||||
db.session.add(db_f)
|
||||
upload_file(file=f, chalid=chal.id)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
@ -838,8 +811,7 @@ def admin_create_chal():
|
|||
@admin.route('/admin/chal/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_chal():
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||
if challenge:
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first_or_404()
|
||||
WrongKeys.query.filter_by(chalid=challenge.id).delete()
|
||||
Solves.query.filter_by(chalid=challenge.id).delete()
|
||||
Keys.query.filter_by(chal=challenge.id).delete()
|
||||
|
@ -858,7 +830,7 @@ def admin_delete_chal():
|
|||
@admin.route('/admin/chal/update', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_update_chal():
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first_or_404()
|
||||
challenge.name = request.form['name']
|
||||
challenge.description = request.form['desc']
|
||||
challenge.value = request.form['value']
|
||||
|
|
|
@ -27,7 +27,7 @@ def confirm_user(data=None):
|
|||
return render_template('confirm.html', errors=['Your confirmation link seems wrong'])
|
||||
except:
|
||||
return render_template('confirm.html', errors=['Your link appears broken, please try again.'])
|
||||
team = Teams.query.filter_by(email=email).first()
|
||||
team = Teams.query.filter_by(email=email).first_or_404()
|
||||
team.verified = True
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
@ -39,7 +39,7 @@ def confirm_user(data=None):
|
|||
if not data and request.method == "GET": # User has been directed to the confirm page because his account is not verified
|
||||
if not authed():
|
||||
return redirect(url_for('auth.login'))
|
||||
team = Teams.query.filter_by(id=session['id']).first()
|
||||
team = Teams.query.filter_by(id=session['id']).first_or_404()
|
||||
if team.verified:
|
||||
return redirect(url_for('views.profile'))
|
||||
else:
|
||||
|
@ -60,7 +60,7 @@ def reset_password(data=None):
|
|||
return render_template('reset_password.html', errors=['Your link has expired'])
|
||||
except:
|
||||
return render_template('reset_password.html', errors=['Your link appears broken, please try again.'])
|
||||
team = Teams.query.filter_by(name=name).first()
|
||||
team = Teams.query.filter_by(name=name).first_or_404()
|
||||
team.password = bcrypt_sha256.encrypt(request.form['password'].strip())
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
|
|
@ -79,7 +79,7 @@ def solves_per_chal():
|
|||
|
||||
|
||||
@challenges.route('/solves')
|
||||
@challenges.route('/solves/<teamid>')
|
||||
@challenges.route('/solves/<int:teamid>')
|
||||
def solves(teamid=None):
|
||||
solves = None
|
||||
awards = None
|
||||
|
@ -131,7 +131,7 @@ def attempts():
|
|||
return jsonify(json)
|
||||
|
||||
|
||||
@challenges.route('/fails/<teamid>', methods=['GET'])
|
||||
@challenges.route('/fails/<int:teamid>', methods=['GET'])
|
||||
def fails(teamid):
|
||||
fails = WrongKeys.query.filter_by(teamid=teamid).count()
|
||||
solves = Solves.query.filter_by(teamid=teamid).count()
|
||||
|
@ -140,7 +140,7 @@ def fails(teamid):
|
|||
return jsonify(json)
|
||||
|
||||
|
||||
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
|
||||
@challenges.route('/chal/<int:chalid>/solves', methods=['GET'])
|
||||
def who_solved(chalid):
|
||||
if not user_can_view_challenges():
|
||||
return redirect(url_for('auth.login', next=request.path))
|
||||
|
@ -151,7 +151,7 @@ def who_solved(chalid):
|
|||
return jsonify(json)
|
||||
|
||||
|
||||
@challenges.route('/chal/<chalid>', methods=['POST'])
|
||||
@challenges.route('/chal/<int:chalid>', methods=['POST'])
|
||||
def chal(chalid):
|
||||
if ctf_ended() and not view_after_ctf():
|
||||
return redirect(url_for('challenges.challenges_view'))
|
||||
|
@ -178,7 +178,7 @@ def chal(chalid):
|
|||
|
||||
# Challange not solved yet
|
||||
if not solves:
|
||||
chal = Challenges.query.filter_by(id=chalid).first()
|
||||
chal = Challenges.query.filter_by(id=chalid).first_or_404()
|
||||
key = unicode(request.form['key'].strip().lower())
|
||||
keys = json.loads(chal.flags)
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ def scores():
|
|||
return jsonify(json)
|
||||
|
||||
|
||||
@scoreboard.route('/top/<count>')
|
||||
@scoreboard.route('/top/<int:count>')
|
||||
def topteams(count):
|
||||
if get_config('view_scoreboard_if_authed') and not authed():
|
||||
return redirect(url_for('auth.login', next=request.path))
|
||||
|
|
|
@ -85,7 +85,8 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="exampleInputFile">Upload challenge files</label>
|
||||
<label>Upload challenge files</label>
|
||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
</div>
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
|
@ -220,6 +221,7 @@
|
|||
<div id="current-files"></div>
|
||||
<input type="hidden" name="method" value="upload">
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
||||
<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" type="submit">Update</button>
|
||||
|
|
|
@ -429,6 +429,18 @@
|
|||
});
|
||||
|
||||
$(function () {
|
||||
|
||||
var hash = window.location.hash;
|
||||
if (hash) {
|
||||
hash = hash.replace("<>[]'\"", "");
|
||||
$('ul.nav a[href="' + hash + '"]').tab('show');
|
||||
}
|
||||
|
||||
$('.nav-pills a').click(function (e) {
|
||||
$(this).tab('show');
|
||||
window.location.hash = this.hash;
|
||||
});
|
||||
|
||||
var start = $('#start').val();
|
||||
var end = $('#end').val();
|
||||
console.log(start);
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label for="buildfile-editor" class="control-label">Build File</label>
|
||||
<textarea id="buildfile-editor" class="form-control" name="buildfile" rows="10"></textarea>
|
||||
<textarea id="buildfile-editor" class="form-control" name="buildfile" rows="10" placeholder="Enter container build file"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="container-files">File input</label>
|
||||
<label for="container-files">Associated Files
|
||||
<i class="fa fa-question-circle" title="These files are uploaded alongside your buildfile"></i>
|
||||
</label>
|
||||
<input type="file" name="files[]" id="container-files" multiple>
|
||||
<p class="help-block">These files are uploaded alongside your buildfile</p>
|
||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
||||
</div>
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
</div>
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<div class="row-fluid">
|
||||
<div class="col-md-12">
|
||||
<h3>Route: </h3>
|
||||
<p class="help-block">This is the URL route that your page will be at (e.g. /page)</p>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input class="form-control radius" id="route" type="text" name="route" value="{% if page is defined %}{{ page.route }}{% endif %}" placeholder="Route">
|
||||
</div>
|
||||
|
@ -32,6 +33,7 @@
|
|||
<div class="row-fluid">
|
||||
<div class="col-md-12">
|
||||
<h3>Content: </h3>
|
||||
<p class="help-block">This is the HTML content of your page</p>
|
||||
<textarea id="admin-pages-editor" name="html">{% if page is defined %}{{ page.html }}{% endif %}</textarea><br>
|
||||
<button class="btn btn-theme btn-outlined create-challenge pull-right">
|
||||
{% if page is defined %}
|
||||
|
|
|
@ -19,13 +19,16 @@ import urllib
|
|||
|
||||
from flask import current_app as app, request, redirect, url_for, session, render_template, abort
|
||||
from flask_caching import Cache
|
||||
from flask_migrate import Migrate, upgrade as migrate_upgrade
|
||||
from itsdangerous import Signer
|
||||
import six
|
||||
from six.moves.urllib.parse import urlparse, urljoin
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Containers, ip2long, long2ip
|
||||
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Files, Containers, ip2long, long2ip
|
||||
|
||||
cache = Cache()
|
||||
migrate = Migrate()
|
||||
|
||||
|
||||
def init_logs(app):
|
||||
|
@ -297,6 +300,24 @@ def get_themes():
|
|||
if os.path.isdir(os.path.join(dir, name)) and name != 'admin']
|
||||
|
||||
|
||||
def upload_file(file, chalid):
|
||||
filename = secure_filename(file.filename)
|
||||
|
||||
if len(filename) <= 0:
|
||||
return False
|
||||
|
||||
md5hash = hashlib.md5(os.urandom(64)).hexdigest()
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash)):
|
||||
os.makedirs(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash))
|
||||
|
||||
file.save(os.path.join(os.path.normpath(app.root_path), 'uploads', md5hash, filename))
|
||||
db_f = Files(chalid, (md5hash + '/' + filename))
|
||||
db.session.add(db_f)
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def get_config(key):
|
||||
config = Config.query.filter_by(key=key).first()
|
||||
|
|
|
@ -105,15 +105,12 @@ def static_html(template):
|
|||
try:
|
||||
return render_template('%s.html' % template)
|
||||
except TemplateNotFound:
|
||||
page = Pages.query.filter_by(route=template).first()
|
||||
if page:
|
||||
page = Pages.query.filter_by(route=template).first_or_404()
|
||||
return render_template('page.html', content=page.html)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@views.route('/teams', defaults={'page': '1'})
|
||||
@views.route('/teams/<page>')
|
||||
@views.route('/teams/<int:page>')
|
||||
def teams(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
|
@ -130,7 +127,7 @@ def teams(page):
|
|||
return render_template('teams.html', teams=teams, team_pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@views.route('/team/<teamid>', methods=['GET', 'POST'])
|
||||
@views.route('/team/<int:teamid>', methods=['GET', 'POST'])
|
||||
def team(teamid):
|
||||
if get_config('view_scoreboard_if_authed') and not authed():
|
||||
return redirect(url_for('auth.login', next=request.path))
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_script import Manager
|
||||
from flask_migrate import Migrate, MigrateCommand
|
||||
from CTFd import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
manager = Manager(app)
|
||||
manager.add_command('db', MigrateCommand)
|
||||
|
||||
if __name__ == '__main__':
|
||||
manager.run()
|
|
@ -0,0 +1 @@
|
|||
Generic single-database configuration.
|
|
@ -0,0 +1,45 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
|
@ -0,0 +1,87 @@
|
|||
from __future__ import with_statement
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
from logging.config import fileConfig
|
||||
import logging
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
from flask import current_app
|
||||
config.set_main_option('sqlalchemy.url',
|
||||
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
|
||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(url=url)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.readthedocs.org/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
engine = engine_from_config(config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool)
|
||||
|
||||
connection = engine.connect()
|
||||
context.configure(connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions['migrate'].configure_args)
|
||||
|
||||
try:
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
|
@ -0,0 +1,24 @@
|
|||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
|
@ -0,0 +1,148 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: cb3cfcc47e2f
|
||||
Revises:
|
||||
Create Date: 2017-01-17 15:39:42.804290
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'cb3cfcc47e2f'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
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('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('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('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('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('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('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('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('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('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('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('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 ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('wrong_keys')
|
||||
op.drop_table('tracking')
|
||||
op.drop_table('tags')
|
||||
op.drop_table('solves')
|
||||
op.drop_table('keys')
|
||||
op.drop_table('files')
|
||||
op.drop_table('awards')
|
||||
op.drop_table('teams')
|
||||
op.drop_table('pages')
|
||||
op.drop_table('containers')
|
||||
op.drop_table('config')
|
||||
op.drop_table('challenges')
|
||||
# ### end Alembic commands ###
|
17
populate.py
17
populate.py
|
@ -6,12 +6,13 @@ import hashlib
|
|||
import random
|
||||
|
||||
from CTFd import create_app
|
||||
from CTFd.models import Teams, Solves, Challenges, WrongKeys, Keys, Files
|
||||
from CTFd.models import Teams, Solves, Challenges, WrongKeys, Keys, Files, Awards
|
||||
|
||||
app = create_app()
|
||||
|
||||
USER_AMOUNT = 50
|
||||
CHAL_AMOUNT = 20
|
||||
AWARDS_AMOUNT = 5
|
||||
|
||||
categories = [
|
||||
'Exploitation',
|
||||
|
@ -270,6 +271,20 @@ if __name__ == '__main__':
|
|||
|
||||
db.session.commit()
|
||||
|
||||
# Generating Awards
|
||||
print("GENERATING AWARDS")
|
||||
for x in range(USER_AMOUNT):
|
||||
base_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=-10000)
|
||||
for _ in range(random.randint(0, AWARDS_AMOUNT)):
|
||||
award = Awards(x + 1, gen_word(), random.randint(-10, 10))
|
||||
new_base = random_date(base_time, base_time + datetime.timedelta(minutes=random.randint(30, 60)))
|
||||
award.date = new_base
|
||||
base_time = new_base
|
||||
|
||||
db.session.add(award)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Generating Wrong Keys
|
||||
print("GENERATING WRONG KEYS")
|
||||
for x in range(USER_AMOUNT):
|
||||
|
|
|
@ -2,6 +2,7 @@ Flask
|
|||
Flask-SQLAlchemy
|
||||
Flask-Session
|
||||
Flask-Caching
|
||||
Flask-Migrate
|
||||
SQLAlchemy
|
||||
sqlalchemy-utils
|
||||
passlib
|
||||
|
|
|
@ -173,7 +173,7 @@ def test_user_get_profile():
|
|||
|
||||
|
||||
def test_user_get_logout():
|
||||
"""Can a registered user can load /logout"""
|
||||
"""Can a registered user load /logout"""
|
||||
app = create_ctfd()
|
||||
with app.app_context():
|
||||
register_user(app)
|
||||
|
@ -185,7 +185,7 @@ def test_user_get_logout():
|
|||
|
||||
|
||||
def test_user_get_reset_password():
|
||||
"""Can an unregistered user can load /reset_password"""
|
||||
"""Can an unregistered user load /reset_password"""
|
||||
app = create_ctfd()
|
||||
with app.app_context():
|
||||
register_user(app)
|
||||
|
|
Loading…
Reference in New Issue