mirror of https://github.com/JohnHammond/CTFd.git
Testing branch (#211)
* Extracting key checking logic to make it more extensible * Add missing keys __init__ file * Adding logging access and errors to Dockerfile * Use template inheritance for page.html (#198) * Fix exception on cofirmation screen (#202) When a user attempts to confirm an e-mail address, an exception is thrown because the db session is closed prior to logging. The line db.session.close() has to move after the logging, otherwise the team parameters from the orm object are discarded and an exception is thrown. Closing the session after logging, fixes the issue. * Adding custom key types for challenges * Separating out admin.py, adding challenge types * Don't let truncate affect edit modal * File uploads no longer refresh page (#207) Closes (#180) * Fixing missing import * Fixing mistake in flag JSON response * Removing compare_digest to support Python 2.7.6 * Fixing inconsistencies in standard challenge modal * Passing submission input over to template js * Handling cases where data can't be found in the DOM better * Don't refresh modal if it's just a refresh operation * Fixing solving challenges while scoreboard is public Induce a redirect to make user login * Adding missing js file and fixing migration * Fixing some visual glitches and streamlining challenge creationselenium-screenshot-testing
parent
2e3df14764
commit
fdb2c34d88
|
@ -60,6 +60,5 @@ target/
|
|||
.idea/
|
||||
CTFd/static/uploads
|
||||
CTFd/uploads
|
||||
CTFd/plugins
|
||||
.data/
|
||||
.ctfd_secret_key
|
||||
|
|
|
@ -4,7 +4,7 @@ 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.exc import OperationalError, ProgrammingError
|
||||
from sqlalchemy_utils import database_exists, create_database
|
||||
|
||||
from utils import get_config, set_config, cache, migrate, migrate_upgrade
|
||||
|
@ -68,7 +68,7 @@ def create_app(config='CTFd.config.Config'):
|
|||
from CTFd.challenges import challenges
|
||||
from CTFd.scoreboard import scoreboard
|
||||
from CTFd.auth import auth
|
||||
from CTFd.admin import admin
|
||||
from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_containers, admin_keys, admin_teams
|
||||
from CTFd.utils import init_utils, init_errors, init_logs
|
||||
|
||||
init_utils(app)
|
||||
|
@ -79,7 +79,15 @@ def create_app(config='CTFd.config.Config'):
|
|||
app.register_blueprint(challenges)
|
||||
app.register_blueprint(scoreboard)
|
||||
app.register_blueprint(auth)
|
||||
|
||||
app.register_blueprint(admin)
|
||||
app.register_blueprint(admin_statistics)
|
||||
app.register_blueprint(admin_challenges)
|
||||
app.register_blueprint(admin_teams)
|
||||
app.register_blueprint(admin_scoreboard)
|
||||
app.register_blueprint(admin_keys)
|
||||
app.register_blueprint(admin_containers)
|
||||
app.register_blueprint(admin_pages)
|
||||
|
||||
from CTFd.plugins import init_plugins
|
||||
|
||||
|
|
842
CTFd/admin.py
842
CTFd/admin.py
|
@ -1,842 +0,0 @@
|
|||
import hashlib
|
||||
import json
|
||||
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 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, 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
|
||||
|
||||
admin = Blueprint('admin', __name__)
|
||||
|
||||
|
||||
@admin.route('/admin', methods=['GET'])
|
||||
def admin_view():
|
||||
if is_admin():
|
||||
return redirect(url_for('admin.admin_graphs'))
|
||||
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
|
||||
@admin.route('/admin/graphs')
|
||||
@admins_only
|
||||
def admin_graphs():
|
||||
return render_template('admin/graphs.html')
|
||||
|
||||
|
||||
@admin.route('/admin/config', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_config():
|
||||
if request.method == "POST":
|
||||
start = None
|
||||
end = None
|
||||
if request.form.get('start'):
|
||||
start = int(request.form['start'])
|
||||
if request.form.get('end'):
|
||||
end = int(request.form['end'])
|
||||
|
||||
try:
|
||||
view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
|
||||
view_scoreboard_if_authed = bool(request.form.get('view_scoreboard_if_authed', None))
|
||||
prevent_registration = bool(request.form.get('prevent_registration', None))
|
||||
prevent_name_change = bool(request.form.get('prevent_name_change', None))
|
||||
view_after_ctf = bool(request.form.get('view_after_ctf', None))
|
||||
verify_emails = bool(request.form.get('verify_emails', None))
|
||||
mail_tls = bool(request.form.get('mail_tls', None))
|
||||
mail_ssl = bool(request.form.get('mail_ssl', None))
|
||||
except (ValueError, TypeError):
|
||||
view_challenges_unregistered = None
|
||||
view_scoreboard_if_authed = None
|
||||
prevent_registration = None
|
||||
prevent_name_change = None
|
||||
view_after_ctf = None
|
||||
verify_emails = None
|
||||
mail_tls = None
|
||||
mail_ssl = None
|
||||
finally:
|
||||
view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
|
||||
view_scoreboard_if_authed = set_config('view_scoreboard_if_authed', view_scoreboard_if_authed)
|
||||
prevent_registration = set_config('prevent_registration', prevent_registration)
|
||||
prevent_name_change = set_config('prevent_name_change', prevent_name_change)
|
||||
view_after_ctf = set_config('view_after_ctf', view_after_ctf)
|
||||
verify_emails = set_config('verify_emails', verify_emails)
|
||||
mail_tls = set_config('mail_tls', mail_tls)
|
||||
mail_ssl = set_config('mail_ssl', mail_ssl)
|
||||
|
||||
mail_server = set_config("mail_server", request.form.get('mail_server', None))
|
||||
mail_port = set_config("mail_port", request.form.get('mail_port', None))
|
||||
|
||||
mail_username = set_config("mail_username", request.form.get('mail_username', None))
|
||||
mail_password = set_config("mail_password", request.form.get('mail_password', None))
|
||||
|
||||
ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
|
||||
ctf_theme = set_config("ctf_theme", request.form.get('ctf_theme', None))
|
||||
|
||||
mailfrom_addr = set_config("mailfrom_addr", request.form.get('mailfrom_addr', None))
|
||||
mg_base_url = set_config("mg_base_url", request.form.get('mg_base_url', None))
|
||||
mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
|
||||
|
||||
max_tries = set_config("max_tries", request.form.get('max_tries', None))
|
||||
|
||||
db_start = Config.query.filter_by(key='start').first()
|
||||
db_start.value = start
|
||||
|
||||
db_end = Config.query.filter_by(key='end').first()
|
||||
db_end.value = end
|
||||
|
||||
db.session.add(db_start)
|
||||
db.session.add(db_end)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
return redirect(url_for('admin.admin_config'))
|
||||
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
ctf_name = get_config('ctf_name')
|
||||
ctf_theme = get_config('ctf_theme')
|
||||
max_tries = get_config('max_tries')
|
||||
|
||||
mail_server = get_config('mail_server')
|
||||
mail_port = get_config('mail_port')
|
||||
mail_username = get_config('mail_username')
|
||||
mail_password = get_config('mail_password')
|
||||
|
||||
mailfrom_addr = get_config('mailfrom_addr')
|
||||
mg_api_key = get_config('mg_api_key')
|
||||
mg_base_url = get_config('mg_base_url')
|
||||
if not max_tries:
|
||||
set_config('max_tries', 0)
|
||||
max_tries = 0
|
||||
|
||||
view_after_ctf = get_config('view_after_ctf')
|
||||
start = get_config('start')
|
||||
end = get_config('end')
|
||||
|
||||
mail_tls = get_config('mail_tls')
|
||||
mail_ssl = get_config('mail_ssl')
|
||||
|
||||
view_challenges_unregistered = get_config('view_challenges_unregistered')
|
||||
view_scoreboard_if_authed = get_config('view_scoreboard_if_authed')
|
||||
prevent_registration = get_config('prevent_registration')
|
||||
prevent_name_change = get_config('prevent_name_change')
|
||||
verify_emails = get_config('verify_emails')
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
themes = get_themes()
|
||||
themes.remove(ctf_theme)
|
||||
|
||||
return render_template('admin/config.html',
|
||||
ctf_name=ctf_name,
|
||||
ctf_theme_config=ctf_theme,
|
||||
start=start,
|
||||
end=end,
|
||||
max_tries=max_tries,
|
||||
mail_server=mail_server,
|
||||
mail_port=mail_port,
|
||||
mail_username=mail_username,
|
||||
mail_password=mail_password,
|
||||
mail_tls=mail_tls,
|
||||
mail_ssl=mail_ssl,
|
||||
view_challenges_unregistered=view_challenges_unregistered,
|
||||
view_scoreboard_if_authed=view_scoreboard_if_authed,
|
||||
prevent_registration=prevent_registration,
|
||||
mailfrom_addr=mailfrom_addr,
|
||||
mg_base_url=mg_base_url,
|
||||
mg_api_key=mg_api_key,
|
||||
prevent_name_change=prevent_name_change,
|
||||
verify_emails=verify_emails,
|
||||
view_after_ctf=view_after_ctf,
|
||||
themes=themes)
|
||||
|
||||
|
||||
@admin.route('/admin/css', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_css():
|
||||
if request.method == 'POST':
|
||||
css = request.form['css']
|
||||
css = set_config('css', css)
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
return '1'
|
||||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST'])
|
||||
@admin.route('/admin/pages/<route>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_pages(route):
|
||||
if request.method == 'GET' and request.args.get('mode') == 'create':
|
||||
return render_template('admin/editor.html')
|
||||
if route and request.method == 'GET':
|
||||
page = Pages.query.filter_by(route=route).first()
|
||||
return render_template('admin/editor.html', page=page)
|
||||
if route and request.method == 'POST':
|
||||
page = Pages.query.filter_by(route=route).first()
|
||||
errors = []
|
||||
html = request.form['html']
|
||||
route = request.form['route']
|
||||
if not route:
|
||||
errors.append('Missing URL route')
|
||||
if errors:
|
||||
page = Pages(html, '')
|
||||
return render_template('/admin/editor.html', page=page)
|
||||
if page:
|
||||
page.route = route
|
||||
page.html = html
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_pages'))
|
||||
page = Pages(route, html)
|
||||
db.session.add(page)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_pages'))
|
||||
pages = Pages.query.all()
|
||||
return render_template('admin/pages.html', routes=pages, css=get_config('css'))
|
||||
|
||||
|
||||
@admin.route('/admin/page/<pageroute>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_page(pageroute):
|
||||
page = Pages.query.filter_by(route=pageroute).first_or_404()
|
||||
db.session.delete(page)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/containers', methods=['GET'])
|
||||
@admins_only
|
||||
def list_container():
|
||||
containers = Containers.query.all()
|
||||
for c in containers:
|
||||
c.status = container_status(c.name)
|
||||
c.ports = ', '.join(container_ports(c.name, verbose=True))
|
||||
return render_template('admin/containers.html', containers=containers)
|
||||
|
||||
|
||||
@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()
|
||||
if container_stop(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
|
||||
|
||||
@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()
|
||||
if container_status(container.name) == 'missing':
|
||||
if run_image(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
else:
|
||||
if container_start(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
|
||||
|
||||
@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()
|
||||
if delete_image(container.name):
|
||||
db.session.delete(container)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/containers/new', methods=['POST'])
|
||||
@admins_only
|
||||
def new_container():
|
||||
name = request.form.get('name')
|
||||
if not set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
|
||||
return redirect(url_for('admin.list_container'))
|
||||
buildfile = request.form.get('buildfile')
|
||||
files = request.files.getlist('files[]')
|
||||
create_image(name=name, buildfile=buildfile, files=files)
|
||||
run_image(name)
|
||||
return redirect(url_for('admin.list_container'))
|
||||
|
||||
|
||||
@admin.route('/admin/chals', methods=['POST', 'GET'])
|
||||
@admins_only
|
||||
def admin_chals():
|
||||
if request.method == 'POST':
|
||||
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category', 'hidden').order_by(Challenges.value).all()
|
||||
|
||||
teams_with_points = db.session.query(Solves.teamid).join(Teams).filter(
|
||||
Teams.banned == False).group_by(Solves.teamid).count()
|
||||
|
||||
json_data = {'game': []}
|
||||
for x in chals:
|
||||
solve_count = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(
|
||||
Solves.chalid == x[1], Teams.banned == False).count()
|
||||
if teams_with_points > 0:
|
||||
percentage = (float(solve_count) / float(teams_with_points))
|
||||
else:
|
||||
percentage = 0.0
|
||||
|
||||
json_data['game'].append({
|
||||
'id': x.id,
|
||||
'name': x.name,
|
||||
'value': x.value,
|
||||
'description': x.description,
|
||||
'category': x.category,
|
||||
'hidden': x.hidden,
|
||||
'percentage_solved': percentage
|
||||
})
|
||||
|
||||
db.session.close()
|
||||
return jsonify(json_data)
|
||||
else:
|
||||
return render_template('admin/chals.html')
|
||||
|
||||
|
||||
@admin.route('/admin/keys/<int:chalid>', methods=['POST', 'GET'])
|
||||
@admins_only
|
||||
def admin_keys(chalid):
|
||||
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':
|
||||
newkeys = request.form.getlist('keys[]')
|
||||
newvals = request.form.getlist('vals[]')
|
||||
flags = []
|
||||
for flag, val in zip(newkeys, newvals):
|
||||
flag_dict = {'flag': flag, 'type': int(val)}
|
||||
flags.append(flag_dict)
|
||||
json_data = json.dumps(flags)
|
||||
|
||||
chal.flags = json_data
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/tags/<int:chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_tags(chalid):
|
||||
if request.method == 'GET':
|
||||
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})
|
||||
return jsonify(json_data)
|
||||
|
||||
elif request.method == 'POST':
|
||||
newtags = request.form.getlist('tags[]')
|
||||
for x in newtags:
|
||||
tag = Tags(chalid, x)
|
||||
db.session.add(tag)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/tags/<int:tagid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_tags(tagid):
|
||||
if request.method == 'POST':
|
||||
tag = Tags.query.filter_by(id=tagid).first_or_404()
|
||||
db.session.delete(tag)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/files/<int:chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_files(chalid):
|
||||
if request.method == 'GET':
|
||||
files = Files.query.filter_by(chal=chalid).all()
|
||||
json_data = {'files': []}
|
||||
for x in files:
|
||||
json_data['files'].append({'id': x.id, 'file': x.location})
|
||||
return jsonify(json_data)
|
||||
if request.method == 'POST':
|
||||
if request.form['method'] == "delete":
|
||||
f = Files.query.filter_by(id=request.form['file']).first_or_404()
|
||||
if os.path.exists(os.path.join(app.root_path, 'uploads', f.location)): # Some kind of os.path.isfile issue on Windows...
|
||||
os.unlink(os.path.join(app.root_path, 'uploads', f.location))
|
||||
db.session.delete(f)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
elif request.form['method'] == "upload":
|
||||
files = request.files.getlist('files[]')
|
||||
|
||||
for f in files:
|
||||
upload_file(file=f, chalid=chalid)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_chals'))
|
||||
|
||||
|
||||
@admin.route('/admin/teams', defaults={'page': '1'})
|
||||
@admin.route('/admin/teams/<int:page>')
|
||||
@admins_only
|
||||
def admin_teams(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
|
||||
teams = Teams.query.order_by(Teams.id.asc()).slice(page_start, page_end).all()
|
||||
count = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
||||
return render_template('admin/teams.html', teams=teams, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@admin.route('/admin/team/<int:teamid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_team(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||
|
||||
if request.method == 'GET':
|
||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||
solve_ids = [s.chalid for s in solves]
|
||||
missing = Challenges.query.filter(not_(Challenges.id.in_(solve_ids))).all()
|
||||
last_seen = db.func.max(Tracking.date).label('last_seen')
|
||||
addrs = db.session.query(Tracking.ip, last_seen) \
|
||||
.filter_by(team=teamid) \
|
||||
.group_by(Tracking.ip) \
|
||||
.order_by(last_seen.desc()).all()
|
||||
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.asc()).all()
|
||||
awards = Awards.query.filter_by(teamid=teamid).order_by(Awards.date.asc()).all()
|
||||
score = user.score()
|
||||
place = user.place()
|
||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
||||
place=place, wrong_keys=wrong_keys, awards=awards)
|
||||
elif request.method == 'POST':
|
||||
admin_user = request.form.get('admin', None)
|
||||
if admin_user:
|
||||
admin_user = True if admin_user == 'true' else False
|
||||
user.admin = admin_user
|
||||
# Set user.banned to hide admins from scoreboard
|
||||
user.banned = admin_user
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return jsonify({'data': ['success']})
|
||||
|
||||
verified = request.form.get('verified', None)
|
||||
if verified:
|
||||
verified = True if verified == 'true' else False
|
||||
user.verified = verified
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return jsonify({'data': ['success']})
|
||||
|
||||
name = request.form.get('name', None)
|
||||
password = request.form.get('password', None)
|
||||
email = request.form.get('email', None)
|
||||
website = request.form.get('website', None)
|
||||
affiliation = request.form.get('affiliation', None)
|
||||
country = request.form.get('country', None)
|
||||
|
||||
errors = []
|
||||
|
||||
name_used = Teams.query.filter(Teams.name == name).first()
|
||||
if name_used and int(name_used.id) != int(teamid):
|
||||
errors.append('That name is taken')
|
||||
|
||||
email_used = Teams.query.filter(Teams.email == email).first()
|
||||
if email_used and int(email_used.id) != int(teamid):
|
||||
errors.append('That email is taken')
|
||||
|
||||
if errors:
|
||||
db.session.close()
|
||||
return jsonify({'data': errors})
|
||||
else:
|
||||
user.name = name
|
||||
user.email = email
|
||||
if password:
|
||||
user.password = bcrypt_sha256.encrypt(password)
|
||||
user.website = website
|
||||
user.affiliation = affiliation
|
||||
user.country = country
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return jsonify({'data': ['success']})
|
||||
|
||||
|
||||
@admin.route('/admin/team/<int:teamid>/mail', methods=['POST'])
|
||||
@admins_only
|
||||
def email_user(teamid):
|
||||
message = request.form.get('msg', None)
|
||||
team = Teams.query.filter(Teams.id == teamid).first()
|
||||
if message and team:
|
||||
if sendmail(team.email, message):
|
||||
return '1'
|
||||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/team/<int:teamid>/ban', methods=['POST'])
|
||||
@admins_only
|
||||
def ban(teamid):
|
||||
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/<int:teamid>/unban', methods=['POST'])
|
||||
@admins_only
|
||||
def unban(teamid):
|
||||
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/<int:teamid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_team(teamid):
|
||||
try:
|
||||
WrongKeys.query.filter_by(teamid=teamid).delete()
|
||||
Solves.query.filter_by(teamid=teamid).delete()
|
||||
Tracking.query.filter_by(team=teamid).delete()
|
||||
Teams.query.filter_by(id=teamid).delete()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
except DatabaseError:
|
||||
return '0'
|
||||
else:
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/graphs/<graph_type>')
|
||||
@admins_only
|
||||
def admin_graph(graph_type):
|
||||
if graph_type == 'categories':
|
||||
categories = db.session.query(Challenges.category, db.func.count(Challenges.category)).group_by(Challenges.category).all()
|
||||
json_data = {'categories': []}
|
||||
for category, count in categories:
|
||||
json_data['categories'].append({'category': category, 'count': count})
|
||||
return jsonify(json_data)
|
||||
elif graph_type == "solves":
|
||||
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \
|
||||
.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \
|
||||
.group_by(Solves.chalid).subquery()
|
||||
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \
|
||||
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
||||
json_data = {}
|
||||
for chal, count, name in solves:
|
||||
json_data[name] = count
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/scoreboard')
|
||||
@admins_only
|
||||
def admin_scoreboard():
|
||||
standings = get_standings(admin=True)
|
||||
return render_template('admin/scoreboard.html', teams=standings)
|
||||
|
||||
|
||||
@admin.route('/admin/teams/<int:teamid>/awards', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_awards(teamid):
|
||||
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||
|
||||
awards_list = []
|
||||
for award in awards:
|
||||
awards_list.append({
|
||||
'id': award.id,
|
||||
'name': award.name,
|
||||
'description': award.description,
|
||||
'date': award.date,
|
||||
'value': award.value,
|
||||
'category': award.category,
|
||||
'icon': award.icon
|
||||
})
|
||||
json_data = {'awards': awards_list}
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/awards/add', methods=['POST'])
|
||||
@admins_only
|
||||
def create_award():
|
||||
try:
|
||||
teamid = request.form['teamid']
|
||||
name = request.form.get('name', 'Award')
|
||||
value = request.form.get('value', 0)
|
||||
award = Awards(teamid, name, value)
|
||||
award.description = request.form.get('description')
|
||||
award.category = request.form.get('category')
|
||||
db.session.add(award)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return '0'
|
||||
|
||||
|
||||
@admin.route('/admin/awards/<int:award_id>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_award(award_id):
|
||||
award = Awards.query.filter_by(id=award_id).first_or_404()
|
||||
db.session.delete(award)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/scores')
|
||||
@admins_only
|
||||
def admin_scores():
|
||||
score = db.func.sum(Challenges.value).label('score')
|
||||
quickest = db.func.max(Solves.date).label('quickest')
|
||||
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == False).group_by(Solves.teamid).order_by(score.desc(), quickest)
|
||||
db.session.close()
|
||||
json_data = {'teams': []}
|
||||
for i, x in enumerate(teams):
|
||||
json_data['teams'].append({'place': i + 1, 'id': x.teamid, 'name': x.name, 'score': int(x.score)})
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/solves/<teamid>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_solves(teamid="all"):
|
||||
if teamid == "all":
|
||||
solves = Solves.query.all()
|
||||
else:
|
||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||
db.session.close()
|
||||
json_data = {'solves': []}
|
||||
for x in solves:
|
||||
json_data['solves'].append({
|
||||
'id': x.id,
|
||||
'chal': x.chal.name,
|
||||
'chalid': x.chalid,
|
||||
'team': x.teamid,
|
||||
'value': x.chal.value,
|
||||
'category': x.chal.category,
|
||||
'time': unix_time(x.date)
|
||||
})
|
||||
for award in awards:
|
||||
json_data['solves'].append({
|
||||
'chal': award.name,
|
||||
'chalid': None,
|
||||
'team': award.teamid,
|
||||
'value': award.value,
|
||||
'category': award.category,
|
||||
'time': unix_time(award.date)
|
||||
})
|
||||
json_data['solves'].sort(key=lambda k: k['time'])
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@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')
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@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()
|
||||
db.session.delete(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@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()
|
||||
db.session.delete(wrong_key)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/statistics', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_stats():
|
||||
teams_registered = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
|
||||
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
|
||||
challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0]
|
||||
|
||||
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \
|
||||
.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \
|
||||
.group_by(Solves.chalid).subquery()
|
||||
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \
|
||||
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
||||
solve_data = {}
|
||||
for chal, count, name in solves:
|
||||
solve_data[name] = count
|
||||
|
||||
most_solved = None
|
||||
least_solved = None
|
||||
if len(solve_data):
|
||||
most_solved = max(solve_data, key=solve_data.get)
|
||||
least_solved = min(solve_data, key=solve_data.get)
|
||||
|
||||
db.session.expunge_all()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return render_template('admin/statistics.html', team_count=teams_registered,
|
||||
wrong_count=wrong_count,
|
||||
solve_count=solve_count,
|
||||
challenge_count=challenge_count,
|
||||
solve_data=solve_data,
|
||||
most_solved=most_solved,
|
||||
least_solved=least_solved)
|
||||
|
||||
|
||||
@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):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
|
||||
wrong_keys = WrongKeys.query.add_columns(WrongKeys.id, WrongKeys.chalid, WrongKeys.flag, WrongKeys.teamid, WrongKeys.date,
|
||||
Challenges.name.label('chal_name'), Teams.name.label('team_name')) \
|
||||
.join(Challenges) \
|
||||
.join(Teams) \
|
||||
.order_by(WrongKeys.date.desc()) \
|
||||
.slice(page_start, page_end) \
|
||||
.all()
|
||||
|
||||
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
|
||||
pages = int(wrong_count / results_per_page) + (wrong_count % results_per_page > 0)
|
||||
|
||||
return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@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):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
|
||||
solves = Solves.query.add_columns(Solves.id, Solves.chalid, Solves.teamid, Solves.date, Solves.flag,
|
||||
Challenges.name.label('chal_name'), Teams.name.label('team_name')) \
|
||||
.join(Challenges) \
|
||||
.join(Teams) \
|
||||
.order_by(Solves.date.desc()) \
|
||||
.slice(page_start, page_end) \
|
||||
.all()
|
||||
|
||||
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
|
||||
pages = int(solve_count / results_per_page) + (solve_count % results_per_page > 0)
|
||||
|
||||
return render_template('admin/correct_keys.html', solves=solves, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@admin.route('/admin/fails/all', defaults={'teamid': 'all'}, methods=['GET'])
|
||||
@admin.route('/admin/fails/<int:teamid>', methods=['GET'])
|
||||
@admins_only
|
||||
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()
|
||||
db.session.close()
|
||||
json_data = {'fails': str(fails), 'solves': str(solves)}
|
||||
return jsonify(json_data)
|
||||
else:
|
||||
fails = WrongKeys.query.filter_by(teamid=teamid).count()
|
||||
solves = Solves.query.filter_by(teamid=teamid).count()
|
||||
db.session.close()
|
||||
json_data = {'fails': str(fails), 'solves': str(solves)}
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/chal/new', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_create_chal():
|
||||
files = request.files.getlist('files[]')
|
||||
|
||||
# TODO: Expand to support multiple flags
|
||||
flags = [{'flag': request.form['key'], 'type':int(request.form['key_type[0]'])}]
|
||||
|
||||
# Create challenge
|
||||
chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category'], flags)
|
||||
if 'hidden' in request.form:
|
||||
chal.hidden = True
|
||||
else:
|
||||
chal.hidden = False
|
||||
db.session.add(chal)
|
||||
db.session.commit()
|
||||
|
||||
for f in files:
|
||||
upload_file(file=f, chalid=chal.id)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_chals'))
|
||||
|
||||
|
||||
@admin.route('/admin/chal/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_chal():
|
||||
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()
|
||||
files = Files.query.filter_by(chal=challenge.id).all()
|
||||
Files.query.filter_by(chal=challenge.id).delete()
|
||||
for file in files:
|
||||
folder = os.path.dirname(os.path.join(os.path.normpath(app.root_path), 'uploads', file.location))
|
||||
rmdir(folder)
|
||||
Tags.query.filter_by(chal=challenge.id).delete()
|
||||
Challenges.query.filter_by(id=challenge.id).delete()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/chal/update', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_update_chal():
|
||||
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']
|
||||
challenge.category = request.form['category']
|
||||
challenge.hidden = 'hidden' in request.form
|
||||
db.session.add(challenge)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_chals'))
|
|
@ -0,0 +1,163 @@
|
|||
import hashlib
|
||||
import json
|
||||
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 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, 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
|
||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||
|
||||
from CTFd.admin.statistics import admin_statistics
|
||||
from CTFd.admin.challenges import admin_challenges
|
||||
from CTFd.admin.scoreboard import admin_scoreboard
|
||||
from CTFd.admin.pages import admin_pages
|
||||
from CTFd.admin.containers import admin_containers
|
||||
from CTFd.admin.keys import admin_keys
|
||||
from CTFd.admin.teams import admin_teams
|
||||
|
||||
|
||||
admin = Blueprint('admin', __name__)
|
||||
|
||||
|
||||
@admin.route('/admin', methods=['GET'])
|
||||
def admin_view():
|
||||
if is_admin():
|
||||
return redirect(url_for('admin_statistics.admin_graphs'))
|
||||
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
|
||||
@admin.route('/admin/config', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_config():
|
||||
if request.method == "POST":
|
||||
start = None
|
||||
end = None
|
||||
if request.form.get('start'):
|
||||
start = int(request.form['start'])
|
||||
if request.form.get('end'):
|
||||
end = int(request.form['end'])
|
||||
|
||||
try:
|
||||
view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
|
||||
view_scoreboard_if_authed = bool(request.form.get('view_scoreboard_if_authed', None))
|
||||
prevent_registration = bool(request.form.get('prevent_registration', None))
|
||||
prevent_name_change = bool(request.form.get('prevent_name_change', None))
|
||||
view_after_ctf = bool(request.form.get('view_after_ctf', None))
|
||||
verify_emails = bool(request.form.get('verify_emails', None))
|
||||
mail_tls = bool(request.form.get('mail_tls', None))
|
||||
mail_ssl = bool(request.form.get('mail_ssl', None))
|
||||
except (ValueError, TypeError):
|
||||
view_challenges_unregistered = None
|
||||
view_scoreboard_if_authed = None
|
||||
prevent_registration = None
|
||||
prevent_name_change = None
|
||||
view_after_ctf = None
|
||||
verify_emails = None
|
||||
mail_tls = None
|
||||
mail_ssl = None
|
||||
finally:
|
||||
view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
|
||||
view_scoreboard_if_authed = set_config('view_scoreboard_if_authed', view_scoreboard_if_authed)
|
||||
prevent_registration = set_config('prevent_registration', prevent_registration)
|
||||
prevent_name_change = set_config('prevent_name_change', prevent_name_change)
|
||||
view_after_ctf = set_config('view_after_ctf', view_after_ctf)
|
||||
verify_emails = set_config('verify_emails', verify_emails)
|
||||
mail_tls = set_config('mail_tls', mail_tls)
|
||||
mail_ssl = set_config('mail_ssl', mail_ssl)
|
||||
|
||||
mail_server = set_config("mail_server", request.form.get('mail_server', None))
|
||||
mail_port = set_config("mail_port", request.form.get('mail_port', None))
|
||||
|
||||
mail_username = set_config("mail_username", request.form.get('mail_username', None))
|
||||
mail_password = set_config("mail_password", request.form.get('mail_password', None))
|
||||
|
||||
ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
|
||||
ctf_theme = set_config("ctf_theme", request.form.get('ctf_theme', None))
|
||||
|
||||
mailfrom_addr = set_config("mailfrom_addr", request.form.get('mailfrom_addr', None))
|
||||
mg_base_url = set_config("mg_base_url", request.form.get('mg_base_url', None))
|
||||
mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
|
||||
|
||||
max_tries = set_config("max_tries", request.form.get('max_tries', None))
|
||||
|
||||
db_start = Config.query.filter_by(key='start').first()
|
||||
db_start.value = start
|
||||
|
||||
db_end = Config.query.filter_by(key='end').first()
|
||||
db_end.value = end
|
||||
|
||||
db.session.add(db_start)
|
||||
db.session.add(db_end)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
return redirect(url_for('admin.admin_config'))
|
||||
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
ctf_name = get_config('ctf_name')
|
||||
ctf_theme = get_config('ctf_theme')
|
||||
max_tries = get_config('max_tries')
|
||||
|
||||
mail_server = get_config('mail_server')
|
||||
mail_port = get_config('mail_port')
|
||||
mail_username = get_config('mail_username')
|
||||
mail_password = get_config('mail_password')
|
||||
|
||||
mailfrom_addr = get_config('mailfrom_addr')
|
||||
mg_api_key = get_config('mg_api_key')
|
||||
mg_base_url = get_config('mg_base_url')
|
||||
if not max_tries:
|
||||
set_config('max_tries', 0)
|
||||
max_tries = 0
|
||||
|
||||
view_after_ctf = get_config('view_after_ctf')
|
||||
start = get_config('start')
|
||||
end = get_config('end')
|
||||
|
||||
mail_tls = get_config('mail_tls')
|
||||
mail_ssl = get_config('mail_ssl')
|
||||
|
||||
view_challenges_unregistered = get_config('view_challenges_unregistered')
|
||||
view_scoreboard_if_authed = get_config('view_scoreboard_if_authed')
|
||||
prevent_registration = get_config('prevent_registration')
|
||||
prevent_name_change = get_config('prevent_name_change')
|
||||
verify_emails = get_config('verify_emails')
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
themes = get_themes()
|
||||
themes.remove(ctf_theme)
|
||||
|
||||
return render_template('admin/config.html',
|
||||
ctf_name=ctf_name,
|
||||
ctf_theme_config=ctf_theme,
|
||||
start=start,
|
||||
end=end,
|
||||
max_tries=max_tries,
|
||||
mail_server=mail_server,
|
||||
mail_port=mail_port,
|
||||
mail_username=mail_username,
|
||||
mail_password=mail_password,
|
||||
mail_tls=mail_tls,
|
||||
mail_ssl=mail_ssl,
|
||||
view_challenges_unregistered=view_challenges_unregistered,
|
||||
view_scoreboard_if_authed=view_scoreboard_if_authed,
|
||||
prevent_registration=prevent_registration,
|
||||
mailfrom_addr=mailfrom_addr,
|
||||
mg_base_url=mg_base_url,
|
||||
mg_api_key=mg_api_key,
|
||||
prevent_name_change=prevent_name_change,
|
||||
verify_emails=verify_emails,
|
||||
view_after_ctf=view_after_ctf,
|
||||
themes=themes)
|
|
@ -0,0 +1,198 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
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, upload_file
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||
from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES
|
||||
import os
|
||||
|
||||
admin_challenges = Blueprint('admin_challenges', __name__)
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/chal_types', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_chal_types():
|
||||
data = {}
|
||||
for class_id in CHALLENGE_CLASSES:
|
||||
data[class_id] = CHALLENGE_CLASSES.get(class_id).name
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/chals', methods=['POST', 'GET'])
|
||||
@admins_only
|
||||
def admin_chals():
|
||||
if request.method == 'POST':
|
||||
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category', 'hidden').order_by(Challenges.value).all()
|
||||
|
||||
teams_with_points = db.session.query(Solves.teamid).join(Teams).filter(
|
||||
Teams.banned == False).group_by(Solves.teamid).count()
|
||||
|
||||
json_data = {'game': []}
|
||||
for x in chals:
|
||||
solve_count = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(
|
||||
Solves.chalid == x[1], Teams.banned == False).count()
|
||||
if teams_with_points > 0:
|
||||
percentage = (float(solve_count) / float(teams_with_points))
|
||||
else:
|
||||
percentage = 0.0
|
||||
|
||||
json_data['game'].append({
|
||||
'id': x.id,
|
||||
'name': x.name,
|
||||
'value': x.value,
|
||||
'description': x.description,
|
||||
'category': x.category,
|
||||
'hidden': x.hidden,
|
||||
'percentage_solved': percentage
|
||||
})
|
||||
|
||||
db.session.close()
|
||||
return jsonify(json_data)
|
||||
else:
|
||||
return render_template('admin/chals.html')
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/tags/<int:chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_tags(chalid):
|
||||
if request.method == 'GET':
|
||||
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})
|
||||
return jsonify(json_data)
|
||||
|
||||
elif request.method == 'POST':
|
||||
newtags = request.form.getlist('tags[]')
|
||||
for x in newtags:
|
||||
tag = Tags(chalid, x)
|
||||
db.session.add(tag)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/tags/<int:tagid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_tags(tagid):
|
||||
if request.method == 'POST':
|
||||
tag = Tags.query.filter_by(id=tagid).first_or_404()
|
||||
db.session.delete(tag)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/files/<int:chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_files(chalid):
|
||||
if request.method == 'GET':
|
||||
files = Files.query.filter_by(chal=chalid).all()
|
||||
json_data = {'files': []}
|
||||
for x in files:
|
||||
json_data['files'].append({'id': x.id, 'file': x.location})
|
||||
return jsonify(json_data)
|
||||
if request.method == 'POST':
|
||||
if request.form['method'] == "delete":
|
||||
f = Files.query.filter_by(id=request.form['file']).first_or_404()
|
||||
if os.path.exists(os.path.join(app.root_path, 'uploads', f.location)): # Some kind of os.path.isfile issue on Windows...
|
||||
os.unlink(os.path.join(app.root_path, 'uploads', f.location))
|
||||
db.session.delete(f)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
elif request.form['method'] == "upload":
|
||||
files = request.files.getlist('files[]')
|
||||
|
||||
for f in files:
|
||||
upload_file(file=f, chalid=chalid)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/chal/<int:chalid>/<prop>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_get_values(chalid, prop):
|
||||
challenge = Challenges.query.filter_by(id=chalid).first_or_404()
|
||||
if prop == 'keys':
|
||||
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})
|
||||
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})
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/chal/new', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_create_chal():
|
||||
if request.method == 'POST':
|
||||
files = request.files.getlist('files[]')
|
||||
|
||||
# Create challenge
|
||||
chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category'], int(request.form['chaltype']))
|
||||
if 'hidden' in request.form:
|
||||
chal.hidden = True
|
||||
else:
|
||||
chal.hidden = False
|
||||
db.session.add(chal)
|
||||
db.session.flush()
|
||||
|
||||
flag = Keys(chal.id, request.form['key'], int(request.form['key_type[0]']))
|
||||
if request.form.get('keydata'):
|
||||
flag.data = request.form.get('keydata')
|
||||
db.session.add(flag)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
for f in files:
|
||||
upload_file(file=f, chalid=chal.id)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin_challenges.admin_chals'))
|
||||
else:
|
||||
return render_template('admin/chals/create.html')
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/chal/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_chal():
|
||||
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()
|
||||
files = Files.query.filter_by(chal=challenge.id).all()
|
||||
Files.query.filter_by(chal=challenge.id).delete()
|
||||
for file in files:
|
||||
folder = os.path.dirname(os.path.join(os.path.normpath(app.root_path), 'uploads', file.location))
|
||||
rmdir(folder)
|
||||
Tags.query.filter_by(chal=challenge.id).delete()
|
||||
Challenges.query.filter_by(id=challenge.id).delete()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_challenges.route('/admin/chal/update', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_update_chal():
|
||||
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']
|
||||
challenge.category = request.form['category']
|
||||
challenge.hidden = 'hidden' in request.form
|
||||
db.session.add(challenge)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin_challenges.admin_chals'))
|
|
@ -0,0 +1,66 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
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, upload_file
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
|
||||
admin_containers = Blueprint('admin_containers', __name__)
|
||||
|
||||
@admin_containers.route('/admin/containers', methods=['GET'])
|
||||
@admins_only
|
||||
def list_container():
|
||||
containers = Containers.query.all()
|
||||
for c in containers:
|
||||
c.status = container_status(c.name)
|
||||
c.ports = ', '.join(container_ports(c.name, verbose=True))
|
||||
return render_template('admin/containers.html', containers=containers)
|
||||
|
||||
|
||||
@admin_containers.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()
|
||||
if container_stop(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
|
||||
|
||||
@admin_containers.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()
|
||||
if container_status(container.name) == 'missing':
|
||||
if run_image(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
else:
|
||||
if container_start(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
|
||||
|
||||
@admin_containers.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()
|
||||
if delete_image(container.name):
|
||||
db.session.delete(container)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_containers.route('/admin/containers/new', methods=['POST'])
|
||||
@admins_only
|
||||
def new_container():
|
||||
name = request.form.get('name')
|
||||
if not set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
|
||||
return redirect(url_for('admin_containers.list_container'))
|
||||
buildfile = request.form.get('buildfile')
|
||||
files = request.files.getlist('files[]')
|
||||
create_image(name=name, buildfile=buildfile, files=files)
|
||||
run_image(name)
|
||||
return redirect(url_for('admin_containers.list_container'))
|
|
@ -0,0 +1,67 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
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, upload_file
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||
|
||||
admin_keys = Blueprint('admin_keys', __name__)
|
||||
|
||||
@admin_keys.route('/admin/key_types', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_key_types():
|
||||
data = {}
|
||||
for class_id in KEY_CLASSES:
|
||||
data[class_id] = KEY_CLASSES.get(class_id).name
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
@admin_keys.route('/admin/keys', defaults={'keyid': None}, methods=['POST', 'GET'])
|
||||
@admin_keys.route('/admin/keys/<int:keyid>', methods=['POST', 'GET'])
|
||||
@admins_only
|
||||
def admin_keys_view(keyid):
|
||||
print repr(keyid)
|
||||
if request.method == 'GET':
|
||||
if keyid:
|
||||
saved_key = Keys.query.filter_by(id=keyid).first_or_404()
|
||||
|
||||
json_data = {
|
||||
'id': saved_key.id,
|
||||
'key': saved_key.flag,
|
||||
'data': saved_key.data,
|
||||
'chal': saved_key.chal,
|
||||
'type': saved_key.key_type,
|
||||
'type_name': get_key_class(saved_key.key_type).name
|
||||
}
|
||||
|
||||
return jsonify(json_data)
|
||||
elif request.method == 'POST':
|
||||
chal = request.form.get('chal')
|
||||
flag = request.form.get('key')
|
||||
data = request.form.get('keydata')
|
||||
key_type = int(request.form.get('key_type'))
|
||||
if not keyid:
|
||||
k = Keys(chal, flag, key_type)
|
||||
k.data = data
|
||||
db.session.add(k)
|
||||
else:
|
||||
k = Keys.query.filter_by(id=keyid).first()
|
||||
k.chal = chal
|
||||
k.flag = flag
|
||||
k.data = data
|
||||
k.key_type = key_type
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
|
||||
@admin_keys.route('/admin/keys/<int:keyid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_keys(keyid):
|
||||
if request.method == 'POST':
|
||||
key = Keys.query.filter_by(id=keyid).first_or_404()
|
||||
db.session.delete(key)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
|
@ -0,0 +1,62 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
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, upload_file
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
|
||||
admin_pages = Blueprint('admin_pages', __name__)
|
||||
|
||||
@admin_pages.route('/admin/css', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_css():
|
||||
if request.method == 'POST':
|
||||
css = request.form['css']
|
||||
css = set_config('css', css)
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
return '1'
|
||||
return '0'
|
||||
|
||||
|
||||
@admin_pages.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST'])
|
||||
@admin_pages.route('/admin/pages/<route>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_pages_view(route):
|
||||
if request.method == 'GET' and request.args.get('mode') == 'create':
|
||||
return render_template('admin/editor.html')
|
||||
if route and request.method == 'GET':
|
||||
page = Pages.query.filter_by(route=route).first()
|
||||
return render_template('admin/editor.html', page=page)
|
||||
if route and request.method == 'POST':
|
||||
page = Pages.query.filter_by(route=route).first()
|
||||
errors = []
|
||||
html = request.form['html']
|
||||
route = request.form['route']
|
||||
if not route:
|
||||
errors.append('Missing URL route')
|
||||
if errors:
|
||||
page = Pages(html, '')
|
||||
return render_template('/admin/editor.html', page=page)
|
||||
if page:
|
||||
page.route = route
|
||||
page.html = html
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin_pages.admin_pages_view'))
|
||||
page = Pages(route, html)
|
||||
db.session.add(page)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin_pages.admin_pages_view'))
|
||||
pages = Pages.query.all()
|
||||
return render_template('admin/pages.html', routes=pages, css=get_config('css'))
|
||||
|
||||
|
||||
@admin_pages.route('/admin/page/<pageroute>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_page(pageroute):
|
||||
page = Pages.query.filter_by(route=pageroute).first_or_404()
|
||||
db.session.delete(page)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
|
@ -0,0 +1,27 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
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, 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
|
||||
|
||||
admin_scoreboard = Blueprint('admin_scoreboard', __name__)
|
||||
|
||||
@admin_scoreboard.route('/admin/scoreboard')
|
||||
@admins_only
|
||||
def admin_scoreboard_view():
|
||||
standings = get_standings(admin=True)
|
||||
return render_template('admin/scoreboard.html', teams=standings)
|
||||
|
||||
|
||||
@admin_scoreboard.route('/admin/scores')
|
||||
@admins_only
|
||||
def admin_scores():
|
||||
score = db.func.sum(Challenges.value).label('score')
|
||||
quickest = db.func.max(Solves.date).label('quickest')
|
||||
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == False).group_by(Solves.teamid).order_by(score.desc(), quickest)
|
||||
db.session.close()
|
||||
json_data = {'teams': []}
|
||||
for i, x in enumerate(teams):
|
||||
json_data['teams'].append({'place': i + 1, 'id': x.teamid, 'name': x.name, 'score': int(x.score)})
|
||||
return jsonify(json_data)
|
|
@ -0,0 +1,112 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
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, upload_file
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
|
||||
admin_statistics = Blueprint('admin_statistics', __name__)
|
||||
|
||||
@admin_statistics.route('/admin/graphs')
|
||||
@admins_only
|
||||
def admin_graphs():
|
||||
return render_template('admin/graphs.html')
|
||||
|
||||
@admin_statistics.route('/admin/graphs/<graph_type>')
|
||||
@admins_only
|
||||
def admin_graph(graph_type):
|
||||
if graph_type == 'categories':
|
||||
categories = db.session.query(Challenges.category, db.func.count(Challenges.category)).group_by(Challenges.category).all()
|
||||
json_data = {'categories': []}
|
||||
for category, count in categories:
|
||||
json_data['categories'].append({'category': category, 'count': count})
|
||||
return jsonify(json_data)
|
||||
elif graph_type == "solves":
|
||||
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \
|
||||
.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \
|
||||
.group_by(Solves.chalid).subquery()
|
||||
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \
|
||||
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
||||
json_data = {}
|
||||
for chal, count, name in solves:
|
||||
json_data[name] = count
|
||||
return jsonify(json_data)
|
||||
|
||||
@admin_statistics.route('/admin/statistics', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_stats():
|
||||
teams_registered = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
|
||||
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
|
||||
challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0]
|
||||
|
||||
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \
|
||||
.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \
|
||||
.group_by(Solves.chalid).subquery()
|
||||
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \
|
||||
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
||||
solve_data = {}
|
||||
for chal, count, name in solves:
|
||||
solve_data[name] = count
|
||||
|
||||
most_solved = None
|
||||
least_solved = None
|
||||
if len(solve_data):
|
||||
most_solved = max(solve_data, key=solve_data.get)
|
||||
least_solved = min(solve_data, key=solve_data.get)
|
||||
|
||||
db.session.expunge_all()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return render_template('admin/statistics.html', team_count=teams_registered,
|
||||
wrong_count=wrong_count,
|
||||
solve_count=solve_count,
|
||||
challenge_count=challenge_count,
|
||||
solve_data=solve_data,
|
||||
most_solved=most_solved,
|
||||
least_solved=least_solved)
|
||||
|
||||
@admin_statistics.route('/admin/wrong_keys', defaults={'page': '1'}, methods=['GET'])
|
||||
@admin_statistics.route('/admin/wrong_keys/<int:page>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_wrong_key(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
|
||||
wrong_keys = WrongKeys.query.add_columns(WrongKeys.id, WrongKeys.chalid, WrongKeys.flag, WrongKeys.teamid, WrongKeys.date,
|
||||
Challenges.name.label('chal_name'), Teams.name.label('team_name')) \
|
||||
.join(Challenges) \
|
||||
.join(Teams) \
|
||||
.order_by(WrongKeys.date.desc()) \
|
||||
.slice(page_start, page_end) \
|
||||
.all()
|
||||
|
||||
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
|
||||
pages = int(wrong_count / results_per_page) + (wrong_count % results_per_page > 0)
|
||||
|
||||
return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@admin_statistics.route('/admin/correct_keys', defaults={'page': '1'}, methods=['GET'])
|
||||
@admin_statistics.route('/admin/correct_keys/<int:page>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_correct_key(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
|
||||
solves = Solves.query.add_columns(Solves.id, Solves.chalid, Solves.teamid, Solves.date, Solves.flag,
|
||||
Challenges.name.label('chal_name'), Teams.name.label('team_name')) \
|
||||
.join(Challenges) \
|
||||
.join(Teams) \
|
||||
.order_by(Solves.date.desc()) \
|
||||
.slice(page_start, page_end) \
|
||||
.all()
|
||||
|
||||
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
|
||||
pages = int(solve_count / results_per_page) + (solve_count % results_per_page > 0)
|
||||
|
||||
return render_template('admin/correct_keys.html', solves=solves, pages=pages, curr_page=page)
|
|
@ -0,0 +1,270 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
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, upload_file
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from passlib.hash import bcrypt_sha256
|
||||
from sqlalchemy.sql import not_
|
||||
|
||||
admin_teams = Blueprint('admin_teams', __name__)
|
||||
|
||||
@admin_teams.route('/admin/teams', defaults={'page': '1'})
|
||||
@admin_teams.route('/admin/teams/<int:page>')
|
||||
@admins_only
|
||||
def admin_teams_view(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
|
||||
teams = Teams.query.order_by(Teams.id.asc()).slice(page_start, page_end).all()
|
||||
count = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
||||
return render_template('admin/teams.html', teams=teams, pages=pages, curr_page=page)
|
||||
|
||||
|
||||
@admin_teams.route('/admin/team/<int:teamid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_team(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||
|
||||
if request.method == 'GET':
|
||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||
solve_ids = [s.chalid for s in solves]
|
||||
missing = Challenges.query.filter(not_(Challenges.id.in_(solve_ids))).all()
|
||||
last_seen = db.func.max(Tracking.date).label('last_seen')
|
||||
addrs = db.session.query(Tracking.ip, last_seen) \
|
||||
.filter_by(team=teamid) \
|
||||
.group_by(Tracking.ip) \
|
||||
.order_by(last_seen.desc()).all()
|
||||
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.asc()).all()
|
||||
awards = Awards.query.filter_by(teamid=teamid).order_by(Awards.date.asc()).all()
|
||||
score = user.score()
|
||||
place = user.place()
|
||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
||||
place=place, wrong_keys=wrong_keys, awards=awards)
|
||||
elif request.method == 'POST':
|
||||
admin_user = request.form.get('admin', None)
|
||||
if admin_user:
|
||||
admin_user = True if admin_user == 'true' else False
|
||||
user.admin = admin_user
|
||||
# Set user.banned to hide admins from scoreboard
|
||||
user.banned = admin_user
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return jsonify({'data': ['success']})
|
||||
|
||||
verified = request.form.get('verified', None)
|
||||
if verified:
|
||||
verified = True if verified == 'true' else False
|
||||
user.verified = verified
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return jsonify({'data': ['success']})
|
||||
|
||||
name = request.form.get('name', None)
|
||||
password = request.form.get('password', None)
|
||||
email = request.form.get('email', None)
|
||||
website = request.form.get('website', None)
|
||||
affiliation = request.form.get('affiliation', None)
|
||||
country = request.form.get('country', None)
|
||||
|
||||
errors = []
|
||||
|
||||
name_used = Teams.query.filter(Teams.name == name).first()
|
||||
if name_used and int(name_used.id) != int(teamid):
|
||||
errors.append('That name is taken')
|
||||
|
||||
email_used = Teams.query.filter(Teams.email == email).first()
|
||||
if email_used and int(email_used.id) != int(teamid):
|
||||
errors.append('That email is taken')
|
||||
|
||||
if errors:
|
||||
db.session.close()
|
||||
return jsonify({'data': errors})
|
||||
else:
|
||||
user.name = name
|
||||
user.email = email
|
||||
if password:
|
||||
user.password = bcrypt_sha256.encrypt(password)
|
||||
user.website = website
|
||||
user.affiliation = affiliation
|
||||
user.country = country
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return jsonify({'data': ['success']})
|
||||
|
||||
|
||||
@admin_teams.route('/admin/team/<int:teamid>/mail', methods=['POST'])
|
||||
@admins_only
|
||||
def email_user(teamid):
|
||||
message = request.form.get('msg', None)
|
||||
team = Teams.query.filter(Teams.id == teamid).first()
|
||||
if message and team:
|
||||
if sendmail(team.email, message):
|
||||
return '1'
|
||||
return '0'
|
||||
|
||||
|
||||
@admin_teams.route('/admin/team/<int:teamid>/ban', methods=['POST'])
|
||||
@admins_only
|
||||
def ban(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||
user.banned = True
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin_scoreboard.admin_scoreboard_view'))
|
||||
|
||||
|
||||
@admin_teams.route('/admin/team/<int:teamid>/unban', methods=['POST'])
|
||||
@admins_only
|
||||
def unban(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||
user.banned = False
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin_scoreboard.admin_scoreboard_view'))
|
||||
|
||||
|
||||
@admin_teams.route('/admin/team/<int:teamid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_team(teamid):
|
||||
try:
|
||||
WrongKeys.query.filter_by(teamid=teamid).delete()
|
||||
Solves.query.filter_by(teamid=teamid).delete()
|
||||
Tracking.query.filter_by(team=teamid).delete()
|
||||
Teams.query.filter_by(id=teamid).delete()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
except DatabaseError:
|
||||
return '0'
|
||||
else:
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_teams.route('/admin/solves/<teamid>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_solves(teamid="all"):
|
||||
if teamid == "all":
|
||||
solves = Solves.query.all()
|
||||
else:
|
||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||
db.session.close()
|
||||
json_data = {'solves': []}
|
||||
for x in solves:
|
||||
json_data['solves'].append({
|
||||
'id': x.id,
|
||||
'chal': x.chal.name,
|
||||
'chalid': x.chalid,
|
||||
'team': x.teamid,
|
||||
'value': x.chal.value,
|
||||
'category': x.chal.category,
|
||||
'time': unix_time(x.date)
|
||||
})
|
||||
for award in awards:
|
||||
json_data['solves'].append({
|
||||
'chal': award.name,
|
||||
'chalid': None,
|
||||
'team': award.teamid,
|
||||
'value': award.value,
|
||||
'category': award.category or "Award",
|
||||
'time': unix_time(award.date)
|
||||
})
|
||||
json_data['solves'].sort(key=lambda k: k['time'])
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin_teams.route('/admin/fails/all', defaults={'teamid': 'all'}, methods=['GET'])
|
||||
@admin_teams.route('/admin/fails/<int:teamid>', methods=['GET'])
|
||||
@admins_only
|
||||
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()
|
||||
db.session.close()
|
||||
json_data = {'fails': str(fails), 'solves': str(solves)}
|
||||
return jsonify(json_data)
|
||||
else:
|
||||
fails = WrongKeys.query.filter_by(teamid=teamid).count()
|
||||
solves = Solves.query.filter_by(teamid=teamid).count()
|
||||
db.session.close()
|
||||
json_data = {'fails': str(fails), 'solves': str(solves)}
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin_teams.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')
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_teams.route('/admin/solves/<int:keyid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_solve(keyid):
|
||||
solve = Solves.query.filter_by(id=keyid).first_or_404()
|
||||
db.session.delete(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_teams.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()
|
||||
db.session.delete(wrong_key)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
@admin_teams.route('/admin/awards/<int:award_id>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_award(award_id):
|
||||
award = Awards.query.filter_by(id=award_id).first_or_404()
|
||||
db.session.delete(award)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
@admin_teams.route('/admin/teams/<int:teamid>/awards', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_awards(teamid):
|
||||
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||
|
||||
awards_list = []
|
||||
for award in awards:
|
||||
awards_list.append({
|
||||
'id': award.id,
|
||||
'name': award.name,
|
||||
'description': award.description,
|
||||
'date': award.date,
|
||||
'value': award.value,
|
||||
'category': award.category,
|
||||
'icon': award.icon
|
||||
})
|
||||
json_data = {'awards': awards_list}
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin_teams.route('/admin/awards/add', methods=['POST'])
|
||||
@admins_only
|
||||
def create_award():
|
||||
try:
|
||||
teamid = request.form['teamid']
|
||||
name = request.form.get('name', 'Award')
|
||||
value = request.form.get('value', 0)
|
||||
award = Awards(teamid, name, value)
|
||||
award.description = request.form.get('description')
|
||||
award.category = request.form.get('category')
|
||||
db.session.add(award)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return '0'
|
|
@ -30,9 +30,9 @@ def confirm_user(data=None):
|
|||
team = Teams.query.filter_by(email=email).first_or_404()
|
||||
team.verified = True
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
logger = logging.getLogger('regs')
|
||||
logger.warn("[{0}] {1} confirmed {2}".format(time.strftime("%m/%d/%Y %X"), team.name.encode('utf-8'), team.email.encode('utf-8')))
|
||||
db.session.close()
|
||||
if authed():
|
||||
return redirect(url_for('challenges.challenges_view'))
|
||||
return redirect(url_for('auth.login'))
|
||||
|
|
|
@ -7,7 +7,9 @@ from flask import render_template, request, redirect, jsonify, url_for, session,
|
|||
from sqlalchemy.sql import or_
|
||||
|
||||
from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, user_can_view_challenges, is_admin, get_config, get_ip, is_verified, ctf_started, ctf_ended, ctf_name
|
||||
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Tags, Teams, Awards
|
||||
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards
|
||||
from CTFd.plugins.keys import get_key_class
|
||||
from CTFd.plugins.challenges import get_chal_class
|
||||
|
||||
challenges = Blueprint('challenges', __name__)
|
||||
|
||||
|
@ -49,13 +51,22 @@ def chals():
|
|||
else:
|
||||
return redirect(url_for('views.static_html'))
|
||||
if user_can_view_challenges() and (ctf_started() or is_admin()):
|
||||
chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
|
||||
|
||||
chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).order_by(Challenges.value).all()
|
||||
json = {'game': []}
|
||||
for x in chals:
|
||||
tags = [tag.tag for tag in Tags.query.add_columns('tag').filter_by(chal=x[1]).all()]
|
||||
tags = [tag.tag for tag in Tags.query.add_columns('tag').filter_by(chal=x.id).all()]
|
||||
files = [str(f.location) for f in Files.query.filter_by(chal=x.id).all()]
|
||||
json['game'].append({'id': x[1], 'name': x[2], 'value': x[3], 'description': x[4], 'category': x[5], 'files': files, 'tags': tags})
|
||||
chal_type = get_chal_class(x.type)
|
||||
json['game'].append({
|
||||
'id': x.id,
|
||||
'type': chal_type.name,
|
||||
'name': x.name,
|
||||
'value': x.value,
|
||||
'description': x.description,
|
||||
'category': x.category,
|
||||
'files': files,
|
||||
'tags': tags
|
||||
})
|
||||
|
||||
db.session.close()
|
||||
return jsonify(json)
|
||||
|
@ -87,7 +98,10 @@ def solves(teamid=None):
|
|||
if is_admin():
|
||||
solves = Solves.query.filter_by(teamid=session['id']).all()
|
||||
elif user_can_view_challenges():
|
||||
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.teamid == session['id'], Teams.banned == False).all()
|
||||
if authed():
|
||||
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.teamid == session['id'], Teams.banned == False).all()
|
||||
else:
|
||||
return jsonify({'solves': []})
|
||||
else:
|
||||
return redirect(url_for('auth.login', next='solves'))
|
||||
else:
|
||||
|
@ -111,7 +125,7 @@ def solves(teamid=None):
|
|||
'chalid': None,
|
||||
'team': award.teamid,
|
||||
'value': award.value,
|
||||
'category': award.category,
|
||||
'category': award.category or "Award",
|
||||
'time': unix_time(award.date)
|
||||
})
|
||||
json['solves'].sort(key=lambda k: k['time'])
|
||||
|
@ -179,8 +193,8 @@ def chal(chalid):
|
|||
# Challange not solved yet
|
||||
if not solves:
|
||||
chal = Challenges.query.filter_by(id=chalid).first_or_404()
|
||||
key = unicode(request.form['key'].strip().lower())
|
||||
keys = json.loads(chal.flags)
|
||||
provided_key = unicode(request.form['key'].strip())
|
||||
saved_keys = Keys.query.filter_by(chal=chal.id).all()
|
||||
|
||||
# Hit max attempts
|
||||
max_tries = int(get_config("max_tries"))
|
||||
|
@ -190,32 +204,18 @@ def chal(chalid):
|
|||
'message': "You have 0 tries remaining"
|
||||
})
|
||||
|
||||
for x in keys:
|
||||
if x['type'] == 0: # static key
|
||||
print(x['flag'], key.strip().lower())
|
||||
if x['flag'] and x['flag'].strip().lower() == key.strip().lower():
|
||||
if ctftime():
|
||||
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=key)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
|
||||
# return '1' # key was correct
|
||||
return jsonify({'status': '1', 'message': 'Correct'})
|
||||
elif x['type'] == 1: # regex
|
||||
res = re.match(x['flag'], key, re.IGNORECASE)
|
||||
if res and res.group() == key:
|
||||
if ctftime():
|
||||
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=key)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
|
||||
# return '1' # key was correct
|
||||
return jsonify({'status': '1', 'message': 'Correct'})
|
||||
chal_class = get_chal_class(chal.type)
|
||||
if chal_class.solve(chal, provided_key):
|
||||
if ctftime():
|
||||
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=provided_key)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
|
||||
return jsonify({'status': '1', 'message': 'Correct'})
|
||||
|
||||
if ctftime():
|
||||
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||
wrong = WrongKeys(teamid=session['id'], chalid=chalid, flag=provided_key)
|
||||
db.session.add(wrong)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
@ -236,4 +236,7 @@ def chal(chalid):
|
|||
# return '2' # challenge was already solved
|
||||
return jsonify({'status': '2', 'message': 'You already solved this'})
|
||||
else:
|
||||
return '-1'
|
||||
return jsonify({
|
||||
'status': '-1',
|
||||
'message': "You must be logged in to solve a challenge"
|
||||
})
|
||||
|
|
|
@ -60,15 +60,16 @@ class Challenges(db.Model):
|
|||
description = db.Column(db.Text)
|
||||
value = db.Column(db.Integer)
|
||||
category = db.Column(db.String(80))
|
||||
flags = db.Column(db.Text)
|
||||
type = db.Column(db.Integer)
|
||||
hidden = db.Column(db.Boolean)
|
||||
|
||||
def __init__(self, name, description, value, category, flags):
|
||||
def __init__(self, name, description, value, category, type=0):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.value = value
|
||||
self.category = category
|
||||
self.flags = json.dumps(flags)
|
||||
self.type = type
|
||||
# self.flags = json.dumps(flags)
|
||||
|
||||
def __repr__(self):
|
||||
return '<chal %r>' % self.name
|
||||
|
@ -124,6 +125,7 @@ class Keys(db.Model):
|
|||
chal = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||
key_type = db.Column(db.Integer)
|
||||
flag = db.Column(db.Text)
|
||||
data = db.Column(db.Text)
|
||||
|
||||
def __init__(self, chal, flag, key_type):
|
||||
self.chal = chal
|
||||
|
@ -131,7 +133,7 @@ class Keys(db.Model):
|
|||
self.key_type = key_type
|
||||
|
||||
def __repr__(self):
|
||||
return self.flag
|
||||
return "<Flag {0} for challenge {1}>".format(self.flag, self.chal)
|
||||
|
||||
|
||||
class Teams(db.Model):
|
||||
|
|
|
@ -5,9 +5,11 @@ import os
|
|||
|
||||
def init_plugins(app):
|
||||
modules = glob.glob(os.path.dirname(__file__) + "/*")
|
||||
blacklist = {'keys', 'challenges'}
|
||||
for module in modules:
|
||||
if os.path.isdir(module):
|
||||
module = '.' + os.path.basename(module)
|
||||
module_name = os.path.basename(module)
|
||||
if os.path.isdir(module) and module_name not in blacklist:
|
||||
module = '.' + module_name
|
||||
module = importlib.import_module(module, package='CTFd.plugins')
|
||||
module.load(app)
|
||||
print(" * Loaded module, %s" % module)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
from CTFd.plugins.keys import get_key_class
|
||||
from CTFd.models import db, Keys
|
||||
|
||||
class BaseChallenge(object):
|
||||
id = None
|
||||
name = None
|
||||
|
||||
class CTFdStandardChallenge(BaseChallenge):
|
||||
id = 0
|
||||
name = "standard"
|
||||
|
||||
@staticmethod
|
||||
def solve(chal, provided_key):
|
||||
chal_keys = Keys.query.filter_by(chal=chal.id).all()
|
||||
for chal_key in chal_keys:
|
||||
if get_key_class(chal_key.key_type).compare(chal_key.flag, provided_key):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
CHALLENGE_CLASSES = {
|
||||
0 : CTFdStandardChallenge
|
||||
}
|
||||
|
||||
def get_chal_class(class_id):
|
||||
cls = CHALLENGE_CLASSES.get(class_id)
|
||||
if cls is None:
|
||||
raise KeyError
|
||||
return cls
|
|
@ -0,0 +1,48 @@
|
|||
import re
|
||||
import string
|
||||
import hmac
|
||||
|
||||
|
||||
class BaseKey(object):
|
||||
id = None
|
||||
name = None
|
||||
|
||||
@staticmethod
|
||||
def compare(self, saved, provided):
|
||||
return True
|
||||
|
||||
class CTFdStaticKey(BaseKey):
|
||||
id = 0
|
||||
name = "static"
|
||||
|
||||
@staticmethod
|
||||
def compare(saved, provided):
|
||||
if len(saved) != len(provided):
|
||||
return False
|
||||
result = 0
|
||||
for x, y in zip(saved, provided):
|
||||
result |= ord(x) ^ ord(y)
|
||||
return result == 0
|
||||
|
||||
|
||||
class CTFdRegexKey(BaseKey):
|
||||
id = 1
|
||||
name = "regex"
|
||||
|
||||
@staticmethod
|
||||
def compare(saved, provided):
|
||||
res = re.match(saved, provided, re.IGNORECASE)
|
||||
return res and res.group() == provided
|
||||
|
||||
|
||||
KEY_CLASSES = {
|
||||
0 : CTFdStaticKey,
|
||||
1 : CTFdRegexKey
|
||||
}
|
||||
|
||||
|
||||
def get_key_class(class_id):
|
||||
cls = KEY_CLASSES.get(class_id)
|
||||
if cls is None:
|
||||
raise KeyError
|
||||
return cls
|
|
@ -21,6 +21,22 @@ String.prototype.hashCode = function() {
|
|||
return hash;
|
||||
};
|
||||
|
||||
function load_edit_key_modal(key_id, key_type_name) {
|
||||
$.get(script_root + '/static/admin/js/templates/keys/'+key_type_name+'/edit-'+key_type_name+'-modal.hbs', function(template_data){
|
||||
$.get(script_root + '/admin/keys/' + key_id, function(key_data){
|
||||
$('#edit-keys').empty();
|
||||
var template = Handlebars.compile(template_data);
|
||||
$('#edit-keys').append(template(key_data));
|
||||
$('#key-id').val(key_id);
|
||||
$('#submit-keys').click(function (e) {
|
||||
e.preventDefault();
|
||||
updatekey()
|
||||
});
|
||||
$('#edit-keys').modal();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadchal(id, update) {
|
||||
// $('#chal *').show()
|
||||
// $('#chal > h1').hide()
|
||||
|
@ -54,23 +70,31 @@ function submitkey(chal, key) {
|
|||
})
|
||||
}
|
||||
|
||||
function create_key(chal, key, key_type) {
|
||||
$.post(script_root + "/admin/keys", {
|
||||
chal: chal,
|
||||
key: key,
|
||||
key_type: key_type,
|
||||
nonce: $('#nonce').val()
|
||||
}, function (data) {
|
||||
if (data == "1"){
|
||||
loadkeys(chal);
|
||||
$("#create-keys").modal('toggle');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadkeys(chal){
|
||||
$.get(script_root + '/admin/keys/' + chal, function(data){
|
||||
$.get(script_root + '/admin/chal/' + chal + '/keys', function(data){
|
||||
$('#keys-chal').val(chal);
|
||||
keys = $.parseJSON(JSON.stringify(data));
|
||||
keys = keys['keys'];
|
||||
$('#current-keys').empty();
|
||||
for(x=0; x<keys.length; x++){
|
||||
var elem = $('<div class="col-md-4">');
|
||||
|
||||
elem.append($("<div class='form-group'>").append($("<input class='current-key form-control' type='text'>").val(keys[x].key)));
|
||||
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+x+']" value="0">Static</div>');
|
||||
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+x+']" value="1">Regex</div>');
|
||||
elem.append('<a href="#" onclick="$(this).parent().remove()" class="btn btn-danger key-remove-button">Remove</a>');
|
||||
|
||||
$('#current-keys').append(elem);
|
||||
$('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true);
|
||||
}
|
||||
$.get(script_root + "/static/admin/js/templates/admin-keys-table.hbs", function(data){
|
||||
var template = Handlebars.compile(data);
|
||||
var wrapper = {keys: keys};
|
||||
$('#current-keys').append(template(wrapper));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -89,6 +113,34 @@ function updatekeys(){
|
|||
$('#update-keys').modal('hide');
|
||||
}
|
||||
|
||||
|
||||
function deletekey(key_id){
|
||||
$.post(script_root + '/admin/keys/'+key_id+'/delete', {'nonce': $('#nonce').val()}, function(data){
|
||||
if (data == "1") {
|
||||
$('tr[name={0}]'.format(key_id)).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatekey(){
|
||||
var key_id = $('#key-id').val();
|
||||
var chal = $('#keys-chal').val();
|
||||
var key_data = $('#key-data').val();
|
||||
var key_type = $('#key-type').val();
|
||||
var nonce = $('#nonce').val();
|
||||
$.post(script_root + '/admin/keys/'+key_id, {
|
||||
'chal':chal,
|
||||
'key':key_data,
|
||||
'key_type': key_type,
|
||||
'nonce': nonce
|
||||
}, function(data){
|
||||
if (data == "1") {
|
||||
loadkeys(chal);
|
||||
$('#edit-keys').modal('toggle');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadtags(chal){
|
||||
$('#tags-chal').val(chal)
|
||||
$('#current-tags').empty()
|
||||
|
@ -125,6 +177,25 @@ function updatetags(){
|
|||
loadchal(chal)
|
||||
}
|
||||
|
||||
function updatefiles(){
|
||||
chal = $('#files-chal').val();
|
||||
var form = $('#update-files form')[0];
|
||||
var formData = new FormData(form);
|
||||
$.ajax({
|
||||
url: script_root + '/admin/files/'+chal,
|
||||
data: formData,
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(data){
|
||||
form.reset();
|
||||
loadfiles(chal);
|
||||
$('#update-files').modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadfiles(chal){
|
||||
$('#update-files form').attr('action', script_root+'/admin/files/'+chal)
|
||||
$.get(script_root + '/admin/files/' + chal, function(data){
|
||||
|
@ -182,11 +253,11 @@ function loadchals(){
|
|||
loadfiles(this.value);
|
||||
});
|
||||
|
||||
$('.create-challenge').click(function (e) {
|
||||
$('#new-chal-category').val($($(this).siblings()[0]).text().trim());
|
||||
$('#new-chal-title').text($($(this).siblings()[0]).text().trim());
|
||||
$('#new-challenge').modal();
|
||||
});
|
||||
// $('.create-challenge').click(function (e) {
|
||||
// $('#new-chal-category').val($($(this).siblings()[0]).text().trim());
|
||||
// $('#new-chal-title').text($($(this).siblings()[0]).text().trim());
|
||||
// $('#new-challenge').modal();
|
||||
// });
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -205,6 +276,11 @@ $('#submit-tags').click(function (e) {
|
|||
updatetags()
|
||||
});
|
||||
|
||||
$('#submit-files').click(function (e) {
|
||||
e.preventDefault();
|
||||
updatefiles()
|
||||
});
|
||||
|
||||
$('#delete-chal form').submit(function(e){
|
||||
e.preventDefault();
|
||||
$.post(script_root + '/admin/chal/delete', $(this).serialize(), function(data){
|
||||
|
@ -247,22 +323,41 @@ $('#new-desc-edit').on('shown.bs.tab', function (event) {
|
|||
});
|
||||
|
||||
// Open New Challenge modal when New Challenge button is clicked
|
||||
$('.create-challenge').click(function (e) {
|
||||
$('#create-challenge').modal();
|
||||
});
|
||||
// $('.create-challenge').click(function (e) {
|
||||
// $('#create-challenge').modal();
|
||||
// });
|
||||
|
||||
|
||||
$('#create-key').click(function(e){
|
||||
var amt = $('#current-keys input[type=text]').length
|
||||
$.get(script_root + '/admin/key_types', function(data){
|
||||
$("#create-keys-select").empty();
|
||||
var option = "<option> -- </option>";
|
||||
$("#create-keys-select").append(option);
|
||||
for (var key in data){
|
||||
var option = "<option value='{0}'>{1}</option>".format(key, data[key]);
|
||||
$("#create-keys-select").append(option);
|
||||
}
|
||||
$("#create-keys").modal();
|
||||
});
|
||||
});
|
||||
|
||||
var elem = $('<div class="col-md-4">');
|
||||
$('#create-keys-select').change(function(){
|
||||
var key_type_name = $(this).find("option:selected").text();
|
||||
|
||||
elem.append($("<div class='form-group'>").append($("<input class='current-key form-control' type='text'>")));
|
||||
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+amt+']" value="0" checked>Static</div>');
|
||||
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+amt+']" value="1">Regex</div>');
|
||||
elem.append('<a href="#" onclick="$(this).parent().remove()" class="btn btn-danger key-remove-button">Remove</a>');
|
||||
$.get(script_root + '/static/admin/js/templates/keys/'+key_type_name +'/'+key_type_name+'.hbs', function(template_data){
|
||||
var template = Handlebars.compile(template_data);
|
||||
$("#create-keys-entry-div").html(template());
|
||||
$("#create-keys-button-div").show();
|
||||
});
|
||||
});
|
||||
|
||||
$('#current-keys').append(elem);
|
||||
|
||||
$('#create-keys-submit').click(function (e) {
|
||||
e.preventDefault();
|
||||
var chalid = $('#create-keys').find('.chal-id').val();
|
||||
var key_data = $('#create-keys').find('input[name=key]').val();
|
||||
var key_type = $('#create-keys-select').val();
|
||||
create_key(chalid, key_data, key_type);
|
||||
});
|
||||
|
||||
$(function(){
|
||||
|
|
|
@ -104,7 +104,7 @@ function category_breakdown_graph(){
|
|||
|
||||
var data = [{
|
||||
values: counts,
|
||||
labels: categories,
|
||||
labels: keys,
|
||||
hole: .4,
|
||||
type: 'pie'
|
||||
}];
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<table id="keysboard" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center"><b>Type</b></td>
|
||||
<td class="text-center"><b>Key</b></td>
|
||||
<td class="text-center"><b>Settings</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each keys}}
|
||||
<tr name="{{this.id}}">
|
||||
<td class="key-type">{{this.type_name}}</td>
|
||||
<td class="key-value">{{this.key}}</td>
|
||||
<td class="text-center"><span>
|
||||
<i role="button" class="fa fa-pencil-square-o" onclick="javascript:load_edit_key_modal({{this.id}}, '{{this.type_name}}')"></i>
|
||||
<i role="button" class="fa fa-times" onclick="javascript:deletekey({{this.id}})"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
|
@ -0,0 +1,84 @@
|
|||
<div class="col-md-6 col-md-offset-3">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/chal/new" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" name="name" placeholder="Enter challenge name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="category">Category</label>
|
||||
<input type="text" class="form-control" name="category" placeholder="Enter challenge category">
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="new-desc-edit">
|
||||
<li role="presentation" class="active"><a href="#new-desc-write" aria-controls="home" role="tab" data-toggle="tab">Write</a></li>
|
||||
<li role="presentation"><a href="#new-desc-preview" aria-controls="home" role="tab" data-toggle="tab">Preview</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="new-desc-write">
|
||||
<div class="form-group">
|
||||
<label for="message-text" class="control-label">Message:</label>
|
||||
<textarea id="new-desc-editor" class="form-control" name="desc" rows="10"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane content" id="new-desc-preview">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="value">Value</label>
|
||||
<input type="number" class="form-control" name="value" placeholder="Enter value" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<div class="col-md-8">
|
||||
<label for="key">Key</label>
|
||||
<input type="text" class="form-control" name="key" placeholder="Enter key">
|
||||
</div>
|
||||
<div class="form-vertical">
|
||||
<div class="col-md-2">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="key_type[0]" value="0" checked>
|
||||
Static
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="key_type[0]" value="1">
|
||||
Regex
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<div class="col-md-9">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input name="hidden" type="checkbox">
|
||||
Hide challenge on creation
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<div class="col-md-9">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<input type="hidden" value="0" name="chaltype" id="chaltype">
|
||||
<div style="text-align:center">
|
||||
<button class="btn btn-theme btn-outlined create-challenge-submit" type="submit">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
// Markdown Preview
|
||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#desc-preview'){
|
||||
$(event.target.hash).html(marked($('#desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
||||
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#new-desc-preview'){
|
||||
$(event.target.hash).html(marked($('#new-desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<h3>Regex Key</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/key/{{id}}" style="text-align:center">
|
||||
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
|
||||
<input type="hidden" id="key-type" value="1">
|
||||
<input type="hidden" id="key-id">
|
||||
<hr>
|
||||
<div class="row">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button id="submit-keys" class="btn btn-theme btn-outlined">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
<label for="create-key-regex" class="control-label">Enter Regex Key Data</label>
|
||||
<input type="text" id="create-key-regex" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
|
|
@ -0,0 +1,19 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center">
|
||||
<h3>Static Key</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="POST" action="{{ request.script_root }}/admin/key/{{id}}" style="text-align:center">
|
||||
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter static key data">
|
||||
<input type="hidden" id="key-type" value="0">
|
||||
<input type="hidden" id="key-id">
|
||||
<hr>
|
||||
<div class="row">
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
<button id="submit-keys" class="btn btn-theme btn-outlined">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
<label for="create-key-static" class="control-label">Enter Static Key Data</label>
|
||||
<input type="text" id="create-key-static" class="form-control" name="key" value="{{key}}" placeholder="Enter static key data">
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,11 @@
|
|||
var challenges;
|
||||
var templates = {};
|
||||
|
||||
Handlebars.registerHelper('splitSlash', function(filename) {
|
||||
var filename = filename.split("/");
|
||||
filename = filename[filename.length - 1];
|
||||
return filename;
|
||||
});
|
||||
|
||||
function loadchal(id) {
|
||||
var obj = $.grep(challenges['game'], function (e) {
|
||||
|
@ -17,34 +24,54 @@ function loadchalbyname(chalname) {
|
|||
}
|
||||
|
||||
function updateChalWindow(obj) {
|
||||
window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
|
||||
var chal = $('#chal-window');
|
||||
chal.find('.chal-name').text(obj.name);
|
||||
chal.find('.chal-desc').html(marked(obj.description, {'gfm':true, 'breaks':true}));
|
||||
chal.find('.chal-files').empty();
|
||||
for (var i = 0; i < obj.files.length; i++) {
|
||||
var filename = obj.files[i].split('/');
|
||||
filename = filename[filename.length - 1];
|
||||
$('#chal-window').find('.chal-files').append("<div class='col-md-3 file-button-wrapper'><a class='file-button' href='" + script_root + '/files/' + obj.files[i] + "'><label class='challenge-wrapper file-wrapper hide-text'>" + filename + "</label></a></div>")
|
||||
}
|
||||
$.get(script_root + '/static/original/js/templates/challenges/'+obj.type+'/'+obj.type+'-challenge-modal.hbs', function(template_data){
|
||||
$('#chal-window').empty();
|
||||
templates[obj.type] = template_data;
|
||||
var template_data = templates[obj.type];
|
||||
var template = Handlebars.compile(template_data);
|
||||
var solves = obj.solves == 1 ? " Solve" : " Solves";
|
||||
solves = obj.solves + solves;
|
||||
|
||||
var tags = chal.find('.chal-tags');
|
||||
tags.empty();
|
||||
var tag = "<span class='label label-primary chal-tag'>{0}</span>";
|
||||
for (var i = 0; i < obj.tags.length; i++){
|
||||
var data = tag.format(obj.tags[i]);
|
||||
tags.append($(data));
|
||||
}
|
||||
var nonce = $('#nonce').val();
|
||||
var wrapper = {
|
||||
id: obj.id,
|
||||
name: obj.name,
|
||||
value: obj.value,
|
||||
tags: obj.tags,
|
||||
desc: marked(obj.description, {'gfm':true, 'breaks':true}),
|
||||
solves: solves,
|
||||
files: obj.files,
|
||||
};
|
||||
|
||||
chal.find('.chal-value').text(obj.value);
|
||||
chal.find('.chal-category').text(obj.category);
|
||||
chal.find('#chal-id').val(obj.id);
|
||||
var solves = obj.solves == 1 ? " Solve" : " Solves";
|
||||
chal.find('.chal-solves').text(obj.solves + solves);
|
||||
$('#answer').val("");
|
||||
$('#chal-window').append(template(wrapper));
|
||||
$.getScript(script_root + '/static/original/js/templates/challenges/'+obj.type+'/'+obj.type+'-challenge-script.js',
|
||||
function() {
|
||||
// Handle Solves tab
|
||||
$('.chal-solves').click(function (e) {
|
||||
getsolves($('#chal-id').val())
|
||||
});
|
||||
$('.nav-tabs a').click(function (e) {
|
||||
e.preventDefault();
|
||||
$(this).tab('show')
|
||||
});
|
||||
|
||||
$('pre code').each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
// Handle modal toggling
|
||||
$('#chal-window').on('hide.bs.modal', function (event) {
|
||||
$("#answer-input").removeClass("wrong");
|
||||
$("#answer-input").removeClass("correct");
|
||||
$("#incorrect-key").slideUp();
|
||||
$("#correct-key").slideUp();
|
||||
$("#already-solved").slideUp();
|
||||
$("#too-fast").slideUp();
|
||||
});
|
||||
|
||||
// $('pre code').each(function(i, block) {
|
||||
// hljs.highlightBlock(block);
|
||||
// });
|
||||
|
||||
window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
|
||||
$('#chal-window').modal();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -71,7 +98,7 @@ function submitkey(chal, key, nonce) {
|
|||
result_message.text(result.message);
|
||||
|
||||
if (result.status == -1){
|
||||
window.location="/login"
|
||||
window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash
|
||||
return
|
||||
}
|
||||
else if (result.status == 0){ // Incorrect key
|
||||
|
@ -164,7 +191,7 @@ function getsolves(id){
|
|||
});
|
||||
}
|
||||
|
||||
function loadchals() {
|
||||
function loadchals(refresh) {
|
||||
$.get(script_root + "/chals", function (data) {
|
||||
var categories = [];
|
||||
challenges = $.parseJSON(JSON.stringify(data));
|
||||
|
@ -215,7 +242,9 @@ function loadchals() {
|
|||
}
|
||||
};
|
||||
|
||||
updatesolves(load_location_hash);
|
||||
if (!refresh){
|
||||
updatesolves(load_location_hash);
|
||||
}
|
||||
marksolves();
|
||||
|
||||
$('.challenge-button').click(function (e) {
|
||||
|
@ -264,7 +293,7 @@ function colorhash (x) {
|
|||
}
|
||||
|
||||
function update(){
|
||||
loadchals();
|
||||
loadchals(true);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
|
|
@ -114,7 +114,7 @@ function category_breakdown_graph() {
|
|||
|
||||
var data = [{
|
||||
values: counts,
|
||||
labels: categories,
|
||||
labels: keys,
|
||||
hole: .4,
|
||||
type: 'pie'
|
||||
}];
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content content">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#challenge" aria-controls="challenge" role="tab">Challenge</a></li>
|
||||
<li role="presentation"><a href="#solves" aria-controls="solves" class="chal-solves" role="tab">{{solves}}</a></li>
|
||||
</ul>
|
||||
<div class="modal-body">
|
||||
<div role="tabpanel">
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane fade in active" id="challenge">
|
||||
<h3 class='chal-name'>{{ name }}</h3>
|
||||
<h4 class="chal-value">{{ value }}</h4>
|
||||
<div class="chal-tags">
|
||||
{{#each tags }}
|
||||
<span class='label label-primary chal-tag'>{{this}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
<span class="chal-desc">{{{ desc }}}</span>
|
||||
<div class="chal-files file-row row">
|
||||
{{#each files}}
|
||||
<div class='col-md-3 file-button-wrapper'>
|
||||
<a class='file-button' href='/files/{{this}}'>
|
||||
<label class='challenge-wrapper file-wrapper hide-text'>
|
||||
{{splitSlash this}}
|
||||
</label>
|
||||
</a>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="row submit-row">
|
||||
<div class="col-md-9" style="padding-right:0px;padding-left:10px;">
|
||||
<span class="input">
|
||||
<input class="input-field" type="text" name="answer" id="answer-input" placeholder="Key" />
|
||||
</span>
|
||||
|
||||
<input id="chal-id" type="hidden" value="{{id}}">
|
||||
</div>
|
||||
<div class="col-md-3 key-submit">
|
||||
<button type="submit" id="submit-key" tabindex="5" class="btn btn-md btn-theme btn-outlined pull-right" style="height:46.375px">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row notification-row">
|
||||
<div id="result-notification" class="alert alert-dismissable" role="alert">
|
||||
<strong id="result-message"></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="solves">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Name</b>
|
||||
</td>
|
||||
<td><b>Date</b>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="chal-solves-names">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,25 @@
|
|||
$('#submit-key').unbind('click');
|
||||
$('#submit-key').click(function (e) {
|
||||
e.preventDefault();
|
||||
submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val())
|
||||
});
|
||||
|
||||
$("#answer-input").keyup(function(event){
|
||||
if(event.keyCode == 13){
|
||||
$("#submit-key").click();
|
||||
}
|
||||
});
|
||||
|
||||
$(".input-field").bind({
|
||||
focus: function() {
|
||||
$(this).parent().addClass('input--filled' );
|
||||
$label = $(this).siblings(".input-label");
|
||||
},
|
||||
blur: function() {
|
||||
if ($(this).val() === '') {
|
||||
$(this).parent().removeClass('input--filled' );
|
||||
$label = $(this).siblings(".input-label");
|
||||
$label.removeClass('input--hide' );
|
||||
}
|
||||
}
|
||||
});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -15,6 +15,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/admin/css/style.css">
|
||||
<script src="{{ request.script_root }}/static/admin/js/vendor/moment.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/vendor/moment-timezone-with-data.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/vendor/handlebars.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var script_root = "{{ request.script_root }}";
|
||||
</script>
|
||||
|
|
|
@ -184,6 +184,34 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="create-keys" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<input type="hidden" class="chal-id" name="chal-id">
|
||||
<div class="modal-header text-center">
|
||||
<h3>Create Key</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="create-keys-select-div">
|
||||
<label for="create-keys-select" class="control-label">Choose Key Type</label>
|
||||
<select class="form-control" id="create-keys-select">
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<div id="create-keys-entry-div">
|
||||
</div>
|
||||
<br>
|
||||
<div style="text-align:center;display:none;" id="create-keys-button-div">
|
||||
<button id="create-keys-submit" class="btn btn-theme btn-outlined">Create Key</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-keys" class="modal fade" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<div id="update-keys" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
@ -224,7 +252,7 @@
|
|||
<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>
|
||||
<button class="btn btn-theme btn-outlined" id="submit-files">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -291,7 +319,7 @@
|
|||
<div style="text-align:center">
|
||||
<br>
|
||||
<h1 class="text-center">Challenges</h1>
|
||||
<button class="btn btn-theme btn-outlined create-challenge">New Challenge</button>
|
||||
<a class="btn btn-theme btn-outlined create-challenge" href="{{ request.script_root }}/admin/chal/new" role="button">New Challenge</a>
|
||||
<div>
|
||||
<table id='challenges'>
|
||||
</table>
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<style>
|
||||
.btn-primary { background-color: #337ab7; }
|
||||
.btn-danger { background-color: #d9534f; }
|
||||
.col-md-4 { margin-bottom: 15px; }
|
||||
.key-remove-button { margin-top: 10px; }
|
||||
.delete-tag { color: white; margin-left: 3px; cursor: pointer; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="create-challenge" class="container main-container">
|
||||
<h1 class="text-center">Create Challenge</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="create-chals-select-div">
|
||||
<label for="create-chals-select" class="control-label">Choose Challenge Type</label>
|
||||
<select class="form-control" id="create-chals-select">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div id="create-chal-entry-div">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/admin/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/vendor/codemirror.min.js"></script>
|
||||
<script>
|
||||
|
||||
function load_chal_template(chal_type_name){
|
||||
$.get(script_root + '/static/admin/js/templates/challenges/'+ chal_type_name +'/' + chal_type_name + '-challenge-create.hbs', function(template_data){
|
||||
var template = Handlebars.compile(template_data);
|
||||
$("#create-chal-entry-div").html(template({'nonce':nonce}));
|
||||
$.getScript(script_root + '/static/admin/js/templates/challenges/'+chal_type_name+'/'+chal_type_name+'-challenge-create.js', function(){
|
||||
console.log('loaded');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
nonce = "{{ nonce }}";
|
||||
$.get(script_root + '/admin/chal_types', function(data){
|
||||
console.log(data);
|
||||
$("#create-chals-select").empty();
|
||||
var chal_type_amt = Object.keys(data).length;
|
||||
if (chal_type_amt > 1){
|
||||
var option = "<option> -- </option>";
|
||||
$("#create-chals-select").append(option);
|
||||
for (var key in data){
|
||||
var option = "<option value='{0}'>{1}</option>".format(key, data[key]);
|
||||
$("#create-chals-select").append(option);
|
||||
}
|
||||
} else if (chal_type_amt == 1) {
|
||||
var key = Object.keys(data)[0];
|
||||
$("#create-chals-select").parent().parent().parent().empty();
|
||||
load_chal_template(data[key]);
|
||||
}
|
||||
});
|
||||
$('#create-chals-select').change(function(){
|
||||
var chal_type_name = $(this).find("option:selected").text();
|
||||
load_chal_template(chal_type_name);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -124,15 +124,15 @@ input[type="checkbox"] { margin: 0px !important; position: relative; top: 5px; }
|
|||
<tbody>
|
||||
{% for team in teams %}
|
||||
<tr name="{{ team.id }}">
|
||||
<td class="team-id">{{ team.id }}</td>
|
||||
<td class="team-name"><a href="{{ request.script_root }}/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
|
||||
<td class="team-id" value="{{ team.id }}">{{ team.id }}</td>
|
||||
<td class="team-name" value="{{ team.name }}"><a href="{{ request.script_root }}/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
|
||||
</td>
|
||||
<td class="team-email">{{ team.email | truncate(32) }}</td>
|
||||
<td class="team-website">{% if team.website and (team.website.startswith('http://') or team.website.startswith('https://')) %}<a href="{{ team.website }}">{{ team.website | truncate(32) }}</a>{% endif %}
|
||||
</td>
|
||||
<td class="team-affiliation"><span>{% if team.affiliation %}{{ team.affiliation | truncate(20) }}{% endif %}</span>
|
||||
<td class="team-affiliation" value="{{ team.affiliation if team.affiliation is not none }}"><span>{% if team.affiliation %}{{ team.affiliation | truncate(20) }}{% endif %}</span>
|
||||
</td>
|
||||
<td class="team-country"><span>{% if team.country %}{{ team.country }}{% endif %}</span>
|
||||
<td class="team-country" value="{{ team.country if team.country is not none }}"><span>{% if team.country %}{{ team.country }}{% endif %}</span>
|
||||
</td>
|
||||
<td class="team-admin">
|
||||
<div class="center-block checkbox text-center">
|
||||
|
@ -263,12 +263,12 @@ $('#delete-user').click(function(e){
|
|||
|
||||
$('.fa-pencil-square-o').click(function(){
|
||||
var elem = $(this).parent().parent().parent();
|
||||
var id = elem.find('.team-id').text().trim();
|
||||
var name = elem.find('.team-name').text().trim();
|
||||
var email = elem.find('.team-email').text().trim();
|
||||
var website = elem.find('.team-website').text().trim();
|
||||
var affiliation = elem.find('.team-affiliation').text().trim();
|
||||
var country = elem.find('.team-country').text().trim();
|
||||
var id = elem.find('.team-id').attr('value') || '';
|
||||
var name = elem.find('.team-name').attr('value') || '';
|
||||
var email = elem.find('.team-email').attr('value') || '';
|
||||
var website = elem.find('.team-website > a').attr('href') || '';
|
||||
var affiliation = elem.find('.team-affiliation').attr('value') || '';
|
||||
var country = elem.find('.team-country').attr('value') || '';
|
||||
|
||||
load_update_modal(id, name, email, website, affiliation, country);
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/user.css">
|
||||
{% block stylesheets %}{% endblock %}
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/moment.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/original/js/vendor/handlebars.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var script_root = "{{ request.script_root }}";
|
||||
</script>
|
||||
|
|
|
@ -101,62 +101,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="chal-window" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content content">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#challenge" aria-controls="challenge" role="tab">Challenge</a></li>
|
||||
<li role="presentation"><a href="#solves" aria-controls="solves" class="chal-solves" role="tab"></a></li>
|
||||
</ul>
|
||||
<div class="modal-body">
|
||||
<div role="tabpanel">
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane fade in active" id="challenge">
|
||||
<h3 class='chal-name'></h3>
|
||||
<h4 class="chal-value"></h4>
|
||||
<div class="chal-tags"></div>
|
||||
<p class="chal-desc"></p>
|
||||
<div class="chal-files file-row row">
|
||||
</div>
|
||||
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
|
||||
|
||||
<div class="row submit-row">
|
||||
<div class="col-md-9" style="padding-right:0px;padding-left:10px;">
|
||||
<span class="input">
|
||||
<input class="input-field" type="text" name="answer" id="answer-input" placeholder="Key" />
|
||||
</span>
|
||||
<input type="hidden" id="nonce" name="nonce" value={{ nonce }}>
|
||||
<input id="chal-id" type="hidden">
|
||||
</div>
|
||||
<div class="col-md-3 key-submit">
|
||||
<button type="submit" id="submit-key" tabindex="5" class="btn btn-md btn-theme btn-outlined pull-right" style="height:46.375px">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row notification-row">
|
||||
<div id="result-notification" class="alert alert-dismissable" role="alert">
|
||||
<strong id="result-message"></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="solves">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Name</b>
|
||||
</td>
|
||||
<td><b>Date</b>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="chal-solves-names">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="chal-window" tabindex="-1" role="dialog">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,77 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ ctf_name() }}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico"
|
||||
type="image/x-icon">
|
||||
<link rel="icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/bootstrap.min.css">
|
||||
<link rel="stylesheet"
|
||||
href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/font-awesome/css/font-awesome.min.css"/>
|
||||
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/lato.css' rel='stylesheet'
|
||||
type='text/css'>
|
||||
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/raleway.css' rel='stylesheet'
|
||||
type='text/css'>
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/user.css">
|
||||
{% block stylesheets %}{% endblock %}
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/moment.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var script_root = "{{ request.script_root }}";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="body-container">
|
||||
<div class="navbar navbar-inverse home">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button class="navbar-toggle" data-target=".navbar-collapse" data-toggle="collapse" type="button">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="{{ request.script_root }}/" class="navbar-brand">{{ ctf_name() }}</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse" aria-expanded="false" style="height: 0px">
|
||||
<ul class="nav navbar-nav">
|
||||
{% for page in pages() %}
|
||||
<li><a href="{{ request.script_root }}/{{ page.route }}">{{ page.route|title }}</a></li>
|
||||
{% endfor %}
|
||||
<li><a href="{{ request.script_root }}/teams">Teams</a></li>
|
||||
<li><a href="{{ request.script_root }}/scoreboard">Scoreboard</a></li>
|
||||
<li><a href="{{ request.script_root }}/challenges">Challenges</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if username is defined %}
|
||||
{% if admin %}
|
||||
<li><a href="{{ request.script_root }}/admin">Admin</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ request.script_root }}/team/{{ id }}">Team</a></li>
|
||||
<li><a href="{{ request.script_root }}/profile">Profile</a></li>
|
||||
<li><a href="{{ request.script_root }}/logout">Logout</a></li>
|
||||
{% else %}
|
||||
{% if can_register() %}
|
||||
<li><a href="{{ request.script_root }}/register">Register</a></li>
|
||||
<li><a style="padding-left:0px;padding-right:0px;">|</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ request.script_root }}/login">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container main-container">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/jquery.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/marked.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/bootstrap.min.js"></script>
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,5 +12,5 @@ WORKDIR /opt/CTFd
|
|||
RUN pip install -r requirements.txt
|
||||
RUN pip install pymysql
|
||||
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "-w", "4", "CTFd:create_app()"]
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "-w", "4", "CTFd:create_app()", "--access-logfile", "/opt/CTFd/CTFd/logs/access.log", "--error-logfile", "/opt/CTFd/CTFd/logs/error.log"]
|
||||
EXPOSE 8000
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
"""Adds challenge types and uses keys table
|
||||
|
||||
Revision ID: 87733981ca0e
|
||||
Revises: cb3cfcc47e2f
|
||||
Create Date: 2017-02-04 14:50:16.999303
|
||||
|
||||
"""
|
||||
from CTFd.models import db, Challenges, Keys
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.sql import text, table, column
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import json
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '87733981ca0e'
|
||||
down_revision = 'cb3cfcc47e2f'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
keys_table = table('keys',
|
||||
column('id', db.Integer),
|
||||
column('chal', db.Integer),
|
||||
column('key_type', db.Integer),
|
||||
column('flag', db.Text)
|
||||
)
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
## Copy over flags data to Keys table
|
||||
print "Getting bind..."
|
||||
conn = op.get_bind()
|
||||
|
||||
print "Executing: SELECT id, flags from challenges"
|
||||
res = conn.execute(text("SELECT id, flags from challenges"))
|
||||
results = res.fetchall()
|
||||
|
||||
print "There are {} results".format(len(results))
|
||||
new_keys = []
|
||||
print "Processing existing flags"
|
||||
for r in results:
|
||||
if r[1]: ## Check if flags are NULL
|
||||
data = json.loads(r[1])
|
||||
for old_keys in data:
|
||||
new_keys.append({'chal':r[0], 'flag':old_keys.get('flag'), 'key_type':old_keys.get('type')})
|
||||
if new_keys:
|
||||
## Base CTFd databases actually already insert into Keys but the database does not make use of them
|
||||
## This prevents duplicate entries of keys
|
||||
print "Executing: TRUNCATE keys"
|
||||
conn.execute(text("TRUNCATE `keys`"))
|
||||
|
||||
print "Bulk inserting the keys"
|
||||
op.bulk_insert(keys_table, new_keys)
|
||||
|
||||
## Add type column to challenges
|
||||
print "Adding type column to challenges"
|
||||
op.add_column('challenges', sa.Column('type', sa.Integer(), nullable=True, default=0))
|
||||
|
||||
## Set all NULLs to 0
|
||||
print "Setting all NULLs to 0"
|
||||
conn.execute("UPDATE challenges set type=0 WHERE type IS NULL")
|
||||
|
||||
## Drop flags from challenges
|
||||
print "Dropping flags column from challenges"
|
||||
op.drop_column('challenges', 'flags')
|
||||
|
||||
print "Finished"
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
print "Getting bind..."
|
||||
conn = op.get_bind()
|
||||
|
||||
print "Adding flags column back to challenges table"
|
||||
op.add_column('challenges', sa.Column('flags', sa.TEXT(), nullable=True))
|
||||
|
||||
print "Dropping type column from challenges table"
|
||||
op.drop_column('challenges', 'type')
|
||||
|
||||
print "Executing: SELECT id, flags from challenges"
|
||||
res = conn.execute("SELECT id, flags from challenges")
|
||||
results = res.fetchall()
|
||||
|
||||
print "There are {} results".format(len(results))
|
||||
for chal_id in results:
|
||||
new_keys = Keys.query.filter_by(chal=chal_id[0]).all()
|
||||
old_flags = []
|
||||
for new_key in new_keys:
|
||||
flag_dict = {'flag': new_key.flag, 'type': new_key.key_type}
|
||||
old_flags.append(flag_dict)
|
||||
old_flags =json.dumps(old_flags)
|
||||
print "Updating challenge {} to insert {}".format(chal_id[0], flag_dict)
|
||||
conn.execute(text('UPDATE challenges SET flags=:flags WHERE id=:id'), id=chal_id[0], flags=old_flags)
|
||||
|
||||
print "Finished"
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,28 @@
|
|||
"""Add data column to keys table
|
||||
|
||||
Revision ID: a4e30c94c360
|
||||
Revises: 87733981ca0e
|
||||
Create Date: 2017-02-13 21:43:46.929248
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a4e30c94c360'
|
||||
down_revision = '87733981ca0e'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('keys', sa.Column('data', sa.Text(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('keys', 'data')
|
||||
# ### end Alembic commands ###
|
|
@ -1,148 +0,0 @@
|
|||
"""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 ###
|
|
@ -0,0 +1,174 @@
|
|||
"""Base 1.0.0 CTFd database
|
||||
|
||||
Revision ID: cb3cfcc47e2f
|
||||
Revises:
|
||||
Create Date: 2017-01-17 15:39:42.804290
|
||||
|
||||
"""
|
||||
from CTFd import create_app
|
||||
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():
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
# ### 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 ###
|
|
@ -220,8 +220,7 @@ if __name__ == '__main__':
|
|||
print("GENERATING CHALLENGES")
|
||||
for x in range(CHAL_AMOUNT):
|
||||
word = gen_word()
|
||||
flags = [{'flag': word, 'type': 0}]
|
||||
db.session.add(Challenges(word, gen_sentence(), gen_value(), gen_category(), flags))
|
||||
db.session.add(Challenges(word, gen_sentence(), gen_value(), gen_category()))
|
||||
db.session.commit()
|
||||
db.session.add(Keys(x + 1, word, 0))
|
||||
db.session.commit()
|
||||
|
|
Loading…
Reference in New Issue