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
|
import os
|
||||||
|
|
||||||
|
from distutils.version import StrictVersion
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from jinja2 import FileSystemLoader
|
from jinja2 import FileSystemLoader
|
||||||
from sqlalchemy.engine.url import make_url
|
from sqlalchemy.engine.url import make_url
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError
|
||||||
from sqlalchemy_utils import database_exists, create_database
|
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):
|
class ThemeLoader(FileSystemLoader):
|
||||||
def get_source(self, environment, template):
|
def get_source(self, environment, template):
|
||||||
|
@ -45,14 +47,23 @@ def create_app(config='CTFd.config.Config'):
|
||||||
|
|
||||||
app.db = db
|
app.db = db
|
||||||
|
|
||||||
|
migrate.init_app(app, db)
|
||||||
|
|
||||||
cache.init_app(app)
|
cache.init_app(app)
|
||||||
app.cache = cache
|
app.cache = cache
|
||||||
|
|
||||||
|
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'):
|
if not get_config('ctf_theme'):
|
||||||
set_config('ctf_theme', 'original')
|
set_config('ctf_theme', 'original')
|
||||||
|
|
||||||
#Session(app)
|
|
||||||
|
|
||||||
from CTFd.views import views
|
from CTFd.views import views
|
||||||
from CTFd.challenges import challenges
|
from CTFd.challenges import challenges
|
||||||
from CTFd.scoreboard import scoreboard
|
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 flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from passlib.hash import bcrypt_sha256
|
from passlib.hash import bcrypt_sha256
|
||||||
from sqlalchemy.sql import not_
|
from sqlalchemy.sql import not_
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
|
|
||||||
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \
|
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, \
|
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.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
from CTFd.scoreboard import get_standings
|
from CTFd.scoreboard import get_standings
|
||||||
|
|
||||||
|
@ -209,7 +208,7 @@ def admin_pages(route):
|
||||||
@admin.route('/admin/page/<pageroute>/delete', methods=['POST'])
|
@admin.route('/admin/page/<pageroute>/delete', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def delete_page(pageroute):
|
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.delete(page)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
@ -226,7 +225,7 @@ def list_container():
|
||||||
return render_template('admin/containers.html', containers=containers)
|
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
|
@admins_only
|
||||||
def stop_container(container_id):
|
def stop_container(container_id):
|
||||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||||
|
@ -236,7 +235,7 @@ def stop_container(container_id):
|
||||||
return '0'
|
return '0'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/containers/<container_id>/start', methods=['POST'])
|
@admin.route('/admin/containers/<int:container_id>/start', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def run_container(container_id):
|
def run_container(container_id):
|
||||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||||
|
@ -252,7 +251,7 @@ def run_container(container_id):
|
||||||
return '0'
|
return '0'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/containers/<container_id>/delete', methods=['POST'])
|
@admin.route('/admin/containers/<int:container_id>/delete', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def delete_container(container_id):
|
def delete_container(container_id):
|
||||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||||
|
@ -310,19 +309,18 @@ def admin_chals():
|
||||||
return render_template('admin/chals.html')
|
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
|
@admins_only
|
||||||
def admin_keys(chalid):
|
def admin_keys(chalid):
|
||||||
if request.method == 'GET':
|
|
||||||
chal = Challenges.query.filter_by(id=chalid).first_or_404()
|
chal = Challenges.query.filter_by(id=chalid).first_or_404()
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
json_data = {'keys': []}
|
json_data = {'keys': []}
|
||||||
flags = json.loads(chal.flags)
|
flags = json.loads(chal.flags)
|
||||||
for i, x in enumerate(flags):
|
for i, x in enumerate(flags):
|
||||||
json_data['keys'].append({'id': i, 'key': x['flag'], 'type': x['type']})
|
json_data['keys'].append({'id': i, 'key': x['flag'], 'type': x['type']})
|
||||||
return jsonify(json_data)
|
return jsonify(json_data)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
chal = Challenges.query.filter_by(id=chalid).first()
|
|
||||||
|
|
||||||
newkeys = request.form.getlist('keys[]')
|
newkeys = request.form.getlist('keys[]')
|
||||||
newvals = request.form.getlist('vals[]')
|
newvals = request.form.getlist('vals[]')
|
||||||
flags = []
|
flags = []
|
||||||
|
@ -338,7 +336,7 @@ def admin_keys(chalid):
|
||||||
return '1'
|
return '1'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/tags/<chalid>', methods=['GET', 'POST'])
|
@admin.route('/admin/tags/<int:chalid>', methods=['GET', 'POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_tags(chalid):
|
def admin_tags(chalid):
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
|
@ -358,7 +356,7 @@ def admin_tags(chalid):
|
||||||
return '1'
|
return '1'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/tags/<tagid>/delete', methods=['POST'])
|
@admin.route('/admin/tags/<int:tagid>/delete', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_delete_tags(tagid):
|
def admin_delete_tags(tagid):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
@ -369,7 +367,7 @@ def admin_delete_tags(tagid):
|
||||||
return '1'
|
return '1'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/files/<chalid>', methods=['GET', 'POST'])
|
@admin.route('/admin/files/<int:chalid>', methods=['GET', 'POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_files(chalid):
|
def admin_files(chalid):
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
|
@ -391,19 +389,7 @@ def admin_files(chalid):
|
||||||
files = request.files.getlist('files[]')
|
files = request.files.getlist('files[]')
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
filename = secure_filename(f.filename)
|
upload_file(file=f, chalid=chalid)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
@ -411,7 +397,7 @@ def admin_files(chalid):
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/teams', defaults={'page': '1'})
|
@admin.route('/admin/teams', defaults={'page': '1'})
|
||||||
@admin.route('/admin/teams/<page>')
|
@admin.route('/admin/teams/<int:page>')
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_teams(page):
|
def admin_teams(page):
|
||||||
page = abs(int(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)
|
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
|
@admins_only
|
||||||
def admin_team(teamid):
|
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':
|
if request.method == 'GET':
|
||||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||||
|
@ -497,7 +483,7 @@ def admin_team(teamid):
|
||||||
return jsonify({'data': ['success']})
|
return jsonify({'data': ['success']})
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/team/<teamid>/mail', methods=['POST'])
|
@admin.route('/admin/team/<int:teamid>/mail', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def email_user(teamid):
|
def email_user(teamid):
|
||||||
message = request.form.get('msg', None)
|
message = request.form.get('msg', None)
|
||||||
|
@ -508,27 +494,27 @@ def email_user(teamid):
|
||||||
return '0'
|
return '0'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/team/<teamid>/ban', methods=['POST'])
|
@admin.route('/admin/team/<int:teamid>/ban', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def ban(teamid):
|
def ban(teamid):
|
||||||
user = Teams.query.filter_by(id=teamid).first()
|
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||||
user.banned = True
|
user.banned = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return redirect(url_for('admin.admin_scoreboard'))
|
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
|
@admins_only
|
||||||
def unban(teamid):
|
def unban(teamid):
|
||||||
user = Teams.query.filter_by(id=teamid).first()
|
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||||
user.banned = False
|
user.banned = False
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return redirect(url_for('admin.admin_scoreboard'))
|
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
|
@admins_only
|
||||||
def delete_team(teamid):
|
def delete_team(teamid):
|
||||||
try:
|
try:
|
||||||
|
@ -572,7 +558,7 @@ def admin_scoreboard():
|
||||||
return render_template('admin/scoreboard.html', teams=standings)
|
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
|
@admins_only
|
||||||
def admin_awards(teamid):
|
def admin_awards(teamid):
|
||||||
awards = Awards.query.filter_by(teamid=teamid).all()
|
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||||
|
@ -611,18 +597,14 @@ def create_award():
|
||||||
return '0'
|
return '0'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/awards/<award_id>/delete', methods=['POST'])
|
@admin.route('/admin/awards/<int:award_id>/delete', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def delete_award(award_id):
|
def delete_award(award_id):
|
||||||
try:
|
award = Awards.query.filter_by(id=award_id).first_or_404()
|
||||||
award = Awards.query.filter_by(id=award_id).first()
|
|
||||||
db.session.delete(award)
|
db.session.delete(award)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return '1'
|
return '1'
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return '0'
|
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/scores')
|
@admin.route('/admin/scores')
|
||||||
|
@ -671,7 +653,7 @@ def admin_solves(teamid="all"):
|
||||||
return jsonify(json_data)
|
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
|
@admins_only
|
||||||
def create_solve(teamid, chalid):
|
def create_solve(teamid, chalid):
|
||||||
solve = Solves(chalid=chalid, teamid=teamid, ip='127.0.0.1', flag='MARKED_AS_SOLVED_BY_ADMIN')
|
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'
|
return '1'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/solves/<keyid>/delete', methods=['POST'])
|
@admin.route('/admin/solves/<int:keyid>/delete', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def delete_solve(keyid):
|
def delete_solve(keyid):
|
||||||
solve = Solves.query.filter_by(id=keyid).first_or_404()
|
solve = Solves.query.filter_by(id=keyid).first_or_404()
|
||||||
|
@ -691,7 +673,7 @@ def delete_solve(keyid):
|
||||||
return '1'
|
return '1'
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/wrong_keys/<keyid>/delete', methods=['POST'])
|
@admin.route('/admin/wrong_keys/<int:keyid>/delete', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def delete_wrong_key(keyid):
|
def delete_wrong_key(keyid):
|
||||||
wrong_key = WrongKeys.query.filter_by(id=keyid).first_or_404()
|
wrong_key = WrongKeys.query.filter_by(id=keyid).first_or_404()
|
||||||
|
@ -737,9 +719,10 @@ def admin_stats():
|
||||||
least_solved=least_solved)
|
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
|
@admins_only
|
||||||
def admin_wrong_key(page='1'):
|
def admin_wrong_key(page):
|
||||||
page = abs(int(page))
|
page = abs(int(page))
|
||||||
results_per_page = 50
|
results_per_page = 50
|
||||||
page_start = results_per_page * (page - 1)
|
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)
|
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
|
@admins_only
|
||||||
def admin_correct_key(page='1'):
|
def admin_correct_key(page):
|
||||||
page = abs(int(page))
|
page = abs(int(page))
|
||||||
results_per_page = 50
|
results_per_page = 50
|
||||||
page_start = results_per_page * (page - 1)
|
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)
|
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
|
@admins_only
|
||||||
def admin_fails(teamid='all'):
|
def admin_fails(teamid):
|
||||||
if teamid == "all":
|
if teamid == "all":
|
||||||
fails = WrongKeys.query.join(Teams, WrongKeys.teamid == Teams.id).filter(Teams.banned == False).count()
|
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()
|
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()
|
db.session.commit()
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
filename = secure_filename(f.filename)
|
upload_file(file=f, chalid=chal.id)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
@ -838,8 +811,7 @@ def admin_create_chal():
|
||||||
@admin.route('/admin/chal/delete', methods=['POST'])
|
@admin.route('/admin/chal/delete', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_delete_chal():
|
def admin_delete_chal():
|
||||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
challenge = Challenges.query.filter_by(id=request.form['id']).first_or_404()
|
||||||
if challenge:
|
|
||||||
WrongKeys.query.filter_by(chalid=challenge.id).delete()
|
WrongKeys.query.filter_by(chalid=challenge.id).delete()
|
||||||
Solves.query.filter_by(chalid=challenge.id).delete()
|
Solves.query.filter_by(chalid=challenge.id).delete()
|
||||||
Keys.query.filter_by(chal=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'])
|
@admin.route('/admin/chal/update', methods=['POST'])
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_update_chal():
|
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.name = request.form['name']
|
||||||
challenge.description = request.form['desc']
|
challenge.description = request.form['desc']
|
||||||
challenge.value = request.form['value']
|
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'])
|
return render_template('confirm.html', errors=['Your confirmation link seems wrong'])
|
||||||
except:
|
except:
|
||||||
return render_template('confirm.html', errors=['Your link appears broken, please try again.'])
|
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
|
team.verified = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
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 data and request.method == "GET": # User has been directed to the confirm page because his account is not verified
|
||||||
if not authed():
|
if not authed():
|
||||||
return redirect(url_for('auth.login'))
|
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:
|
if team.verified:
|
||||||
return redirect(url_for('views.profile'))
|
return redirect(url_for('views.profile'))
|
||||||
else:
|
else:
|
||||||
|
@ -60,7 +60,7 @@ def reset_password(data=None):
|
||||||
return render_template('reset_password.html', errors=['Your link has expired'])
|
return render_template('reset_password.html', errors=['Your link has expired'])
|
||||||
except:
|
except:
|
||||||
return render_template('reset_password.html', errors=['Your link appears broken, please try again.'])
|
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())
|
team.password = bcrypt_sha256.encrypt(request.form['password'].strip())
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
|
|
@ -79,7 +79,7 @@ def solves_per_chal():
|
||||||
|
|
||||||
|
|
||||||
@challenges.route('/solves')
|
@challenges.route('/solves')
|
||||||
@challenges.route('/solves/<teamid>')
|
@challenges.route('/solves/<int:teamid>')
|
||||||
def solves(teamid=None):
|
def solves(teamid=None):
|
||||||
solves = None
|
solves = None
|
||||||
awards = None
|
awards = None
|
||||||
|
@ -131,7 +131,7 @@ def attempts():
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
|
|
||||||
|
|
||||||
@challenges.route('/fails/<teamid>', methods=['GET'])
|
@challenges.route('/fails/<int:teamid>', methods=['GET'])
|
||||||
def fails(teamid):
|
def fails(teamid):
|
||||||
fails = WrongKeys.query.filter_by(teamid=teamid).count()
|
fails = WrongKeys.query.filter_by(teamid=teamid).count()
|
||||||
solves = Solves.query.filter_by(teamid=teamid).count()
|
solves = Solves.query.filter_by(teamid=teamid).count()
|
||||||
|
@ -140,7 +140,7 @@ def fails(teamid):
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
|
|
||||||
|
|
||||||
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
|
@challenges.route('/chal/<int:chalid>/solves', methods=['GET'])
|
||||||
def who_solved(chalid):
|
def who_solved(chalid):
|
||||||
if not user_can_view_challenges():
|
if not user_can_view_challenges():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
|
@ -151,7 +151,7 @@ def who_solved(chalid):
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
|
|
||||||
|
|
||||||
@challenges.route('/chal/<chalid>', methods=['POST'])
|
@challenges.route('/chal/<int:chalid>', methods=['POST'])
|
||||||
def chal(chalid):
|
def chal(chalid):
|
||||||
if ctf_ended() and not view_after_ctf():
|
if ctf_ended() and not view_after_ctf():
|
||||||
return redirect(url_for('challenges.challenges_view'))
|
return redirect(url_for('challenges.challenges_view'))
|
||||||
|
@ -178,7 +178,7 @@ def chal(chalid):
|
||||||
|
|
||||||
# Challange not solved yet
|
# Challange not solved yet
|
||||||
if not solves:
|
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())
|
key = unicode(request.form['key'].strip().lower())
|
||||||
keys = json.loads(chal.flags)
|
keys = json.loads(chal.flags)
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ def scores():
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
|
|
||||||
|
|
||||||
@scoreboard.route('/top/<count>')
|
@scoreboard.route('/top/<int:count>')
|
||||||
def topteams(count):
|
def topteams(count):
|
||||||
if get_config('view_scoreboard_if_authed') and not authed():
|
if get_config('view_scoreboard_if_authed') and not authed():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
|
|
|
@ -85,7 +85,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<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">
|
<input type="file" name="files[]" multiple="multiple">
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||||
|
@ -220,6 +221,7 @@
|
||||||
<div id="current-files"></div>
|
<div id="current-files"></div>
|
||||||
<input type="hidden" name="method" value="upload">
|
<input type="hidden" name="method" value="upload">
|
||||||
<input type="file" name="files[]" multiple="multiple">
|
<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">
|
<div class="row" style="text-align:center;margin-top:20px">
|
||||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||||
<button class="btn btn-theme btn-outlined" type="submit">Update</button>
|
<button class="btn btn-theme btn-outlined" type="submit">Update</button>
|
||||||
|
|
|
@ -429,6 +429,18 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function () {
|
$(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 start = $('#start').val();
|
||||||
var end = $('#end').val();
|
var end = $('#end').val();
|
||||||
console.log(start);
|
console.log(start);
|
||||||
|
|
|
@ -17,12 +17,14 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="buildfile-editor" class="control-label">Build File</label>
|
<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>
|
||||||
<div class="form-group">
|
<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>
|
<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>
|
</div>
|
||||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h3>Route: </h3>
|
<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 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">
|
<input class="form-control radius" id="route" type="text" name="route" value="{% if page is defined %}{{ page.route }}{% endif %}" placeholder="Route">
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h3>Content: </h3>
|
<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>
|
<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">
|
<button class="btn btn-theme btn-outlined create-challenge pull-right">
|
||||||
{% if page is defined %}
|
{% 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 import current_app as app, request, redirect, url_for, session, render_template, abort
|
||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
|
from flask_migrate import Migrate, upgrade as migrate_upgrade
|
||||||
from itsdangerous import Signer
|
from itsdangerous import Signer
|
||||||
import six
|
import six
|
||||||
from six.moves.urllib.parse import urlparse, urljoin
|
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()
|
cache = Cache()
|
||||||
|
migrate = Migrate()
|
||||||
|
|
||||||
|
|
||||||
def init_logs(app):
|
def init_logs(app):
|
||||||
|
@ -297,6 +300,24 @@ def get_themes():
|
||||||
if os.path.isdir(os.path.join(dir, name)) and name != 'admin']
|
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()
|
@cache.memoize()
|
||||||
def get_config(key):
|
def get_config(key):
|
||||||
config = Config.query.filter_by(key=key).first()
|
config = Config.query.filter_by(key=key).first()
|
||||||
|
|
|
@ -105,15 +105,12 @@ def static_html(template):
|
||||||
try:
|
try:
|
||||||
return render_template('%s.html' % template)
|
return render_template('%s.html' % template)
|
||||||
except TemplateNotFound:
|
except TemplateNotFound:
|
||||||
page = Pages.query.filter_by(route=template).first()
|
page = Pages.query.filter_by(route=template).first_or_404()
|
||||||
if page:
|
|
||||||
return render_template('page.html', content=page.html)
|
return render_template('page.html', content=page.html)
|
||||||
else:
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
|
|
||||||
@views.route('/teams', defaults={'page': '1'})
|
@views.route('/teams', defaults={'page': '1'})
|
||||||
@views.route('/teams/<page>')
|
@views.route('/teams/<int:page>')
|
||||||
def teams(page):
|
def teams(page):
|
||||||
page = abs(int(page))
|
page = abs(int(page))
|
||||||
results_per_page = 50
|
results_per_page = 50
|
||||||
|
@ -130,7 +127,7 @@ def teams(page):
|
||||||
return render_template('teams.html', teams=teams, team_pages=pages, curr_page=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):
|
def team(teamid):
|
||||||
if get_config('view_scoreboard_if_authed') and not authed():
|
if get_config('view_scoreboard_if_authed') and not authed():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
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
|
import random
|
||||||
|
|
||||||
from CTFd import create_app
|
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()
|
app = create_app()
|
||||||
|
|
||||||
USER_AMOUNT = 50
|
USER_AMOUNT = 50
|
||||||
CHAL_AMOUNT = 20
|
CHAL_AMOUNT = 20
|
||||||
|
AWARDS_AMOUNT = 5
|
||||||
|
|
||||||
categories = [
|
categories = [
|
||||||
'Exploitation',
|
'Exploitation',
|
||||||
|
@ -270,6 +271,20 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
db.session.commit()
|
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
|
# Generating Wrong Keys
|
||||||
print("GENERATING WRONG KEYS")
|
print("GENERATING WRONG KEYS")
|
||||||
for x in range(USER_AMOUNT):
|
for x in range(USER_AMOUNT):
|
||||||
|
|
|
@ -2,6 +2,7 @@ Flask
|
||||||
Flask-SQLAlchemy
|
Flask-SQLAlchemy
|
||||||
Flask-Session
|
Flask-Session
|
||||||
Flask-Caching
|
Flask-Caching
|
||||||
|
Flask-Migrate
|
||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
sqlalchemy-utils
|
sqlalchemy-utils
|
||||||
passlib
|
passlib
|
||||||
|
|
|
@ -173,7 +173,7 @@ def test_user_get_profile():
|
||||||
|
|
||||||
|
|
||||||
def test_user_get_logout():
|
def test_user_get_logout():
|
||||||
"""Can a registered user can load /logout"""
|
"""Can a registered user load /logout"""
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
register_user(app)
|
register_user(app)
|
||||||
|
@ -185,7 +185,7 @@ def test_user_get_logout():
|
||||||
|
|
||||||
|
|
||||||
def test_user_get_reset_password():
|
def test_user_get_reset_password():
|
||||||
"""Can an unregistered user can load /reset_password"""
|
"""Can an unregistered user load /reset_password"""
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
register_user(app)
|
register_user(app)
|
||||||
|
|
Loading…
Reference in New Issue