Plugins enhanced utils (#231)

* Updating utils functions to be monkey patchable

* Also fixing a team email update issue

* Adding more tests
This commit is contained in:
Kevin Chung
2017-03-22 20:00:45 -04:00
committed by GitHub
parent ede7f1bad2
commit a3a7d75ae8
17 changed files with 351 additions and 195 deletions

View File

@@ -8,7 +8,8 @@ from sqlalchemy.exc import OperationalError, ProgrammingError
from sqlalchemy_utils import database_exists, create_database from sqlalchemy_utils import database_exists, create_database
from six.moves import input from six.moves import input
from CTFd.utils import get_config, set_config, cache, migrate, migrate_upgrade, migrate_stamp from CTFd.utils import cache, migrate, migrate_upgrade, migrate_stamp
from CTFd import utils
__version__ = '1.0.1' __version__ = '1.0.1'
@@ -16,7 +17,7 @@ class ThemeLoader(FileSystemLoader):
def get_source(self, environment, template): def get_source(self, environment, template):
if template.startswith('admin/'): if template.startswith('admin/'):
return super(ThemeLoader, self).get_source(environment, template) return super(ThemeLoader, self).get_source(environment, template)
theme = get_config('ctf_theme') theme = utils.get_config('ctf_theme')
template = "/".join([theme, template]) template = "/".join([theme, template])
return super(ThemeLoader, self).get_source(environment, template) return super(ThemeLoader, self).get_source(environment, template)
@@ -53,10 +54,10 @@ def create_app(config='CTFd.config.Config'):
cache.init_app(app) cache.init_app(app)
app.cache = cache app.cache = cache
version = get_config('ctf_version') version = utils.get_config('ctf_version')
if not version: ## Upgrading from an unversioned CTFd if not version: ## Upgrading from an unversioned CTFd
set_config('ctf_version', __version__) utils.set_config('ctf_version', __version__)
if version and (StrictVersion(version) < StrictVersion(__version__)): ## Upgrading from an older version of CTFd if version and (StrictVersion(version) < StrictVersion(__version__)): ## Upgrading from an older version of CTFd
print("/*\\ CTFd has updated and must update the database! /*\\") print("/*\\ CTFd has updated and must update the database! /*\\")
@@ -65,13 +66,13 @@ def create_app(config='CTFd.config.Config'):
if input('Run database migrations (Y/N)').lower().strip() == 'y': if input('Run database migrations (Y/N)').lower().strip() == 'y':
migrate_stamp() migrate_stamp()
migrate_upgrade() migrate_upgrade()
set_config('ctf_version', __version__) utils.set_config('ctf_version', __version__)
else: else:
print('/*\\ Ignored database migrations... /*\\') print('/*\\ Ignored database migrations... /*\\')
exit() exit()
if not get_config('ctf_theme'): if not utils.get_config('ctf_theme'):
set_config('ctf_theme', 'original') utils.set_config('ctf_theme', 'original')
from CTFd.views import views from CTFd.views import views
from CTFd.challenges import challenges from CTFd.challenges import challenges

View File

@@ -7,9 +7,7 @@ from flask import current_app as app, render_template, request, redirect, jsonif
from passlib.hash import bcrypt_sha256 from passlib.hash import bcrypt_sha256
from sqlalchemy.sql import not_ from sqlalchemy.sql import not_
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
set_config, sendmail, rmdir, create_image, delete_image, run_image, container_status, container_ports, \
container_stop, container_start, get_themes, cache, upload_file, get_configurable_plugins
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
from CTFd.scoreboard import get_standings from CTFd.scoreboard import get_standings
from CTFd.plugins.keys import get_key_class, KEY_CLASSES from CTFd.plugins.keys import get_key_class, KEY_CLASSES
@@ -22,6 +20,8 @@ from CTFd.admin.containers import admin_containers
from CTFd.admin.keys import admin_keys from CTFd.admin.keys import admin_keys
from CTFd.admin.teams import admin_teams from CTFd.admin.teams import admin_teams
from CTFd import utils
admin = Blueprint('admin', __name__) admin = Blueprint('admin', __name__)
@@ -38,13 +38,13 @@ def admin_view():
@admins_only @admins_only
def admin_plugin_config(plugin): def admin_plugin_config(plugin):
if request.method == 'GET': if request.method == 'GET':
if plugin in get_configurable_plugins(): if plugin in utils.get_configurable_plugins():
config = open(os.path.join(app.root_path, 'plugins', plugin, 'config.html')).read() config = open(os.path.join(app.root_path, 'plugins', plugin, 'config.html')).read()
return render_template('admin/page.html', content=config) return render_template('admin/page.html', content=config)
abort(404) abort(404)
elif request.method == 'POST': elif request.method == 'POST':
for k, v in request.form.items(): for k, v in request.form.items():
set_config(k, v) utils.set_config(k, v)
return '1' return '1'
@@ -80,28 +80,28 @@ def admin_config():
mail_tls = None mail_tls = None
mail_ssl = None mail_ssl = None
finally: finally:
view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered) view_challenges_unregistered = utils.set_config('view_challenges_unregistered', view_challenges_unregistered)
view_scoreboard_if_authed = set_config('view_scoreboard_if_authed', view_scoreboard_if_authed) view_scoreboard_if_authed = utils.set_config('view_scoreboard_if_authed', view_scoreboard_if_authed)
hide_scores = set_config('hide_scores', hide_scores) hide_scores = utils.set_config('hide_scores', hide_scores)
prevent_registration = set_config('prevent_registration', prevent_registration) prevent_registration = utils.set_config('prevent_registration', prevent_registration)
prevent_name_change = set_config('prevent_name_change', prevent_name_change) prevent_name_change = utils.set_config('prevent_name_change', prevent_name_change)
view_after_ctf = set_config('view_after_ctf', view_after_ctf) view_after_ctf = utils.set_config('view_after_ctf', view_after_ctf)
verify_emails = set_config('verify_emails', verify_emails) verify_emails = utils.set_config('verify_emails', verify_emails)
mail_tls = set_config('mail_tls', mail_tls) mail_tls = utils.set_config('mail_tls', mail_tls)
mail_ssl = set_config('mail_ssl', mail_ssl) mail_ssl = utils.set_config('mail_ssl', mail_ssl)
mail_server = set_config("mail_server", request.form.get('mail_server', None)) mail_server = utils.set_config("mail_server", request.form.get('mail_server', None))
mail_port = set_config("mail_port", request.form.get('mail_port', None)) mail_port = utils.set_config("mail_port", request.form.get('mail_port', None))
mail_username = set_config("mail_username", request.form.get('mail_username', None)) mail_username = utils.set_config("mail_username", request.form.get('mail_username', None))
mail_password = set_config("mail_password", request.form.get('mail_password', None)) mail_password = utils.set_config("mail_password", request.form.get('mail_password', None))
ctf_name = set_config("ctf_name", request.form.get('ctf_name', None)) ctf_name = utils.set_config("ctf_name", request.form.get('ctf_name', None))
ctf_theme = set_config("ctf_theme", request.form.get('ctf_theme', None)) ctf_theme = utils.set_config("ctf_theme", request.form.get('ctf_theme', None))
mailfrom_addr = set_config("mailfrom_addr", request.form.get('mailfrom_addr', None)) mailfrom_addr = utils.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_base_url = utils.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)) mg_api_key = utils.set_config("mg_api_key", request.form.get('mg_api_key', None))
db_start = Config.query.filter_by(key='start').first() db_start = Config.query.filter_by(key='start').first()
db_start.value = start db_start.value = start
@@ -120,36 +120,36 @@ def admin_config():
with app.app_context(): with app.app_context():
cache.clear() cache.clear()
ctf_name = get_config('ctf_name') ctf_name = utils.get_config('ctf_name')
ctf_theme = get_config('ctf_theme') ctf_theme = utils.get_config('ctf_theme')
hide_scores = get_config('hide_scores') hide_scores = utils.get_config('hide_scores')
mail_server = get_config('mail_server') mail_server = utils.get_config('mail_server')
mail_port = get_config('mail_port') mail_port = utils.get_config('mail_port')
mail_username = get_config('mail_username') mail_username = utils.get_config('mail_username')
mail_password = get_config('mail_password') mail_password = utils.get_config('mail_password')
mailfrom_addr = get_config('mailfrom_addr') mailfrom_addr = utils.get_config('mailfrom_addr')
mg_api_key = get_config('mg_api_key') mg_api_key = utils.get_config('mg_api_key')
mg_base_url = get_config('mg_base_url') mg_base_url = utils.get_config('mg_base_url')
view_after_ctf = get_config('view_after_ctf') view_after_ctf = utils.get_config('view_after_ctf')
start = get_config('start') start = utils.get_config('start')
end = get_config('end') end = utils.get_config('end')
mail_tls = get_config('mail_tls') mail_tls = utils.get_config('mail_tls')
mail_ssl = get_config('mail_ssl') mail_ssl = utils.get_config('mail_ssl')
view_challenges_unregistered = get_config('view_challenges_unregistered') view_challenges_unregistered = utils.get_config('view_challenges_unregistered')
view_scoreboard_if_authed = get_config('view_scoreboard_if_authed') view_scoreboard_if_authed = utils.get_config('view_scoreboard_if_authed')
prevent_registration = get_config('prevent_registration') prevent_registration = utils.get_config('prevent_registration')
prevent_name_change = get_config('prevent_name_change') prevent_name_change = utils.get_config('prevent_name_change')
verify_emails = get_config('verify_emails') verify_emails = utils.get_config('verify_emails')
db.session.commit() db.session.commit()
db.session.close() db.session.close()
themes = get_themes() themes = utils.get_themes()
themes.remove(ctf_theme) themes.remove(ctf_theme)
return render_template('admin/config.html', return render_template('admin/config.html',

View File

@@ -1,10 +1,11 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
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.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.keys import get_key_class, KEY_CLASSES
from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES
from CTFd import utils
import os import os
admin_challenges = Blueprint('admin_challenges', __name__) admin_challenges = Blueprint('admin_challenges', __name__)
@@ -109,7 +110,7 @@ def admin_files(chalid):
files = request.files.getlist('files[]') files = request.files.getlist('files[]')
for f in files: for f in files:
upload_file(file=f, chalid=chalid) utils.upload_file(file=f, chalid=chalid)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
@@ -162,7 +163,7 @@ def admin_create_chal():
db.session.commit() db.session.commit()
for f in files: for f in files:
upload_file(file=f, chalid=chal.id) utils.upload_file(file=f, chalid=chal.id)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
@@ -183,7 +184,7 @@ def admin_delete_chal():
for file in files: for file in files:
upload_folder = app.config['UPLOAD_FOLDER'] upload_folder = app.config['UPLOAD_FOLDER']
folder = os.path.dirname(os.path.join(os.path.normpath(app.root_path), upload_folder, file.location)) folder = os.path.dirname(os.path.join(os.path.normpath(app.root_path), upload_folder, file.location))
rmdir(folder) utils.rmdir(folder)
Tags.query.filter_by(chal=challenge.id).delete() Tags.query.filter_by(chal=challenge.id).delete()
Challenges.query.filter_by(id=challenge.id).delete() Challenges.query.filter_by(id=challenge.id).delete()
db.session.commit() db.session.commit()

View File

@@ -1,9 +1,9 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
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.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
from CTFd import utils
admin_containers = Blueprint('admin_containers', __name__) admin_containers = Blueprint('admin_containers', __name__)
@admin_containers.route('/admin/containers', methods=['GET']) @admin_containers.route('/admin/containers', methods=['GET'])
@@ -11,8 +11,8 @@ admin_containers = Blueprint('admin_containers', __name__)
def list_container(): def list_container():
containers = Containers.query.all() containers = Containers.query.all()
for c in containers: for c in containers:
c.status = container_status(c.name) c.status = utils.container_status(c.name)
c.ports = ', '.join(container_ports(c.name, verbose=True)) c.ports = ', '.join(utils.container_ports(c.name, verbose=True))
return render_template('admin/containers.html', containers=containers) return render_template('admin/containers.html', containers=containers)
@@ -20,7 +20,7 @@ def list_container():
@admins_only @admins_only
def stop_container(container_id): def stop_container(container_id):
container = Containers.query.filter_by(id=container_id).first_or_404() container = Containers.query.filter_by(id=container_id).first_or_404()
if container_stop(container.name): if utils.container_stop(container.name):
return '1' return '1'
else: else:
return '0' return '0'
@@ -30,13 +30,13 @@ def stop_container(container_id):
@admins_only @admins_only
def run_container(container_id): def run_container(container_id):
container = Containers.query.filter_by(id=container_id).first_or_404() container = Containers.query.filter_by(id=container_id).first_or_404()
if container_status(container.name) == 'missing': if utils.container_status(container.name) == 'missing':
if run_image(container.name): if utils.run_image(container.name):
return '1' return '1'
else: else:
return '0' return '0'
else: else:
if container_start(container.name): if utils.container_start(container.name):
return '1' return '1'
else: else:
return '0' return '0'
@@ -46,7 +46,7 @@ def run_container(container_id):
@admins_only @admins_only
def delete_container(container_id): def delete_container(container_id):
container = Containers.query.filter_by(id=container_id).first_or_404() container = Containers.query.filter_by(id=container_id).first_or_404()
if delete_image(container.name): if utils.delete_image(container.name):
db.session.delete(container) db.session.delete(container)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
@@ -61,6 +61,6 @@ def new_container():
return redirect(url_for('admin_containers.list_container')) return redirect(url_for('admin_containers.list_container'))
buildfile = request.form.get('buildfile') buildfile = request.form.get('buildfile')
files = request.files.getlist('files[]') files = request.files.getlist('files[]')
create_image(name=name, buildfile=buildfile, files=files) utils.create_image(name=name, buildfile=buildfile, files=files)
run_image(name) utils.run_image(name)
return redirect(url_for('admin_containers.list_container')) return redirect(url_for('admin_containers.list_container'))

View File

@@ -1,10 +1,10 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
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.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.keys import get_key_class, KEY_CLASSES
from CTFd import utils
admin_keys = Blueprint('admin_keys', __name__) admin_keys = Blueprint('admin_keys', __name__)
@admin_keys.route('/admin/key_types', methods=['GET']) @admin_keys.route('/admin/key_types', methods=['GET'])

View File

@@ -1,9 +1,9 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
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.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
from CTFd import utils
admin_pages = Blueprint('admin_pages', __name__) admin_pages = Blueprint('admin_pages', __name__)
@admin_pages.route('/admin/css', methods=['GET', 'POST']) @admin_pages.route('/admin/css', methods=['GET', 'POST'])
@@ -11,7 +11,7 @@ admin_pages = Blueprint('admin_pages', __name__)
def admin_css(): def admin_css():
if request.method == 'POST': if request.method == 'POST':
css = request.form['css'] css = request.form['css']
css = set_config('css', css) css = utils.set_config('css', css)
with app.app_context(): with app.app_context():
cache.clear() cache.clear()
return '1' return '1'
@@ -49,7 +49,7 @@ def admin_pages_view(route):
db.session.close() db.session.close()
return redirect(url_for('admin_pages.admin_pages_view')) return redirect(url_for('admin_pages.admin_pages_view'))
pages = Pages.query.all() pages = Pages.query.all()
return render_template('admin/pages.html', routes=pages, css=get_config('css')) return render_template('admin/pages.html', routes=pages, css=utils.get_config('css'))
@admin_pages.route('/admin/page/<pageroute>/delete', methods=['POST']) @admin_pages.route('/admin/page/<pageroute>/delete', methods=['POST'])

View File

@@ -1,10 +1,10 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
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.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
from CTFd.scoreboard import get_standings from CTFd.scoreboard import get_standings
from CTFd import utils
admin_scoreboard = Blueprint('admin_scoreboard', __name__) admin_scoreboard = Blueprint('admin_scoreboard', __name__)
@admin_scoreboard.route('/admin/scoreboard') @admin_scoreboard.route('/admin/scoreboard')

View File

@@ -1,9 +1,9 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
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.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
from CTFd import utils
admin_statistics = Blueprint('admin_statistics', __name__) admin_statistics = Blueprint('admin_statistics', __name__)
@admin_statistics.route('/admin/graphs') @admin_statistics.route('/admin/graphs')

View File

@@ -1,11 +1,11 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils import admins_only, is_admin, unix_time, get_config, \ from CTFd.utils import admins_only, is_admin, cache
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.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
from passlib.hash import bcrypt_sha256 from passlib.hash import bcrypt_sha256
from sqlalchemy.sql import not_ from sqlalchemy.sql import not_
from CTFd import utils
admin_teams = Blueprint('admin_teams', __name__) admin_teams = Blueprint('admin_teams', __name__)
@admin_teams.route('/admin/teams', defaults={'page': '1'}) @admin_teams.route('/admin/teams', defaults={'page': '1'})
@@ -84,7 +84,8 @@ def admin_team(teamid):
return jsonify({'data': errors}) return jsonify({'data': errors})
else: else:
user.name = name user.name = name
user.email = email if email:
user.email = email
if password: if password:
user.password = bcrypt_sha256.encrypt(password) user.password = bcrypt_sha256.encrypt(password)
user.website = website user.website = website
@@ -101,7 +102,7 @@ def email_user(teamid):
message = request.form.get('msg', None) message = request.form.get('msg', None)
team = Teams.query.filter(Teams.id == teamid).first() team = Teams.query.filter(Teams.id == teamid).first()
if message and team: if message and team:
if sendmail(team.email, message): if utils.sendmail(team.email, message):
return '1' return '1'
return '0' return '0'
@@ -160,7 +161,7 @@ def admin_solves(teamid="all"):
'team': x.teamid, 'team': x.teamid,
'value': x.chal.value, 'value': x.chal.value,
'category': x.chal.category, 'category': x.chal.category,
'time': unix_time(x.date) 'time': utils.unix_time(x.date)
}) })
for award in awards: for award in awards:
json_data['solves'].append({ json_data['solves'].append({
@@ -169,7 +170,7 @@ def admin_solves(teamid="all"):
'team': award.teamid, 'team': award.teamid,
'value': award.value, 'value': award.value,
'category': award.category or "Award", 'category': award.category or "Award",
'time': unix_time(award.date) 'time': utils.unix_time(award.date)
}) })
json_data['solves'].sort(key=lambda k: k['time']) json_data['solves'].sort(key=lambda k: k['time'])
return jsonify(json_data) return jsonify(json_data)

View File

@@ -8,8 +8,8 @@ from flask import current_app as app, render_template, request, redirect, url_fo
from itsdangerous import TimedSerializer, BadTimeSignature, Signer, BadSignature from itsdangerous import TimedSerializer, BadTimeSignature, Signer, BadSignature
from passlib.hash import bcrypt_sha256 from passlib.hash import bcrypt_sha256
from CTFd.utils import sha512, is_safe_url, authed, can_send_mail, sendmail, can_register, get_config, verify_email
from CTFd.models import db, Teams from CTFd.models import db, Teams
from CTFd import utils
auth = Blueprint('auth', __name__) auth = Blueprint('auth', __name__)
@@ -17,7 +17,7 @@ auth = Blueprint('auth', __name__)
@auth.route('/confirm', methods=['POST', 'GET']) @auth.route('/confirm', methods=['POST', 'GET'])
@auth.route('/confirm/<data>', methods=['GET']) @auth.route('/confirm/<data>', methods=['GET'])
def confirm_user(data=None): def confirm_user(data=None):
if not get_config('verify_emails'): if not utils.get_config('verify_emails'):
return redirect(url_for('challenges.challenges_view')) return redirect(url_for('challenges.challenges_view'))
if data and request.method == "GET": # User is confirming email account if data and request.method == "GET": # User is confirming email account
try: try:
@@ -33,17 +33,17 @@ def confirm_user(data=None):
logger = logging.getLogger('regs') 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'))) 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() db.session.close()
if authed(): if utils.authed():
return redirect(url_for('challenges.challenges_view')) return redirect(url_for('challenges.challenges_view'))
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
if not data and request.method == "GET": # User has been directed to the confirm page because his account is not verified if not data and request.method == "GET": # User has been directed to the confirm page because his account is not verified
if not authed(): if not utils.authed():
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
team = Teams.query.filter_by(id=session['id']).first_or_404() team = Teams.query.filter_by(id=session['id']).first_or_404()
if team.verified: if team.verified:
return redirect(url_for('views.profile')) return redirect(url_for('views.profile'))
else: else:
verify_email(team.email) utils.verify_email(team.email)
return render_template('confirm.html', team=team) return render_template('confirm.html', team=team)
@@ -80,7 +80,7 @@ Did you initiate a password reset?
""".format(url_for('auth.reset_password', _external=True), urllib.quote_plus(token.encode('base64'))) """.format(url_for('auth.reset_password', _external=True), urllib.quote_plus(token.encode('base64')))
sendmail(email, text) utils.sendmail(email, text)
return render_template('reset_password.html', errors=['If that account exists you will receive an email, please check your inbox']) return render_template('reset_password.html', errors=['If that account exists you will receive an email, please check your inbox'])
return render_template('reset_password.html') return render_template('reset_password.html')
@@ -88,7 +88,7 @@ Did you initiate a password reset?
@auth.route('/register', methods=['POST', 'GET']) @auth.route('/register', methods=['POST', 'GET'])
def register(): def register():
if not can_register(): if not utils.can_register():
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
if request.method == 'POST': if request.method == 'POST':
errors = [] errors = []
@@ -128,9 +128,9 @@ def register():
session['username'] = team.name session['username'] = team.name
session['id'] = team.id session['id'] = team.id
session['admin'] = team.admin session['admin'] = team.admin
session['nonce'] = sha512(os.urandom(10)) session['nonce'] = utils.sha512(os.urandom(10))
if can_send_mail() and get_config('verify_emails'): # Confirming users is enabled and we can send email. if utils.can_send_mail() and utils.get_config('verify_emails'): # Confirming users is enabled and we can send email.
db.session.close() db.session.close()
logger = logging.getLogger('regs') logger = logging.getLogger('regs')
logger.warn("[{0}] {1} registered (UNCONFIRMED) with {2}".format(time.strftime("%m/%d/%Y %X"), logger.warn("[{0}] {1} registered (UNCONFIRMED) with {2}".format(time.strftime("%m/%d/%Y %X"),
@@ -138,8 +138,8 @@ def register():
request.form['email'].encode('utf-8'))) request.form['email'].encode('utf-8')))
return redirect(url_for('auth.confirm_user')) return redirect(url_for('auth.confirm_user'))
else: # Don't care about confirming users else: # Don't care about confirming users
if can_send_mail(): # We want to notify the user that they have registered. if utils.can_send_mail(): # We want to notify the user that they have registered.
sendmail(request.form['email'], "You've successfully registered for {}".format(get_config('ctf_name'))) utils.sendmail(request.form['email'], "You've successfully registered for {}".format(utils.get_config('ctf_name')))
db.session.close() db.session.close()
@@ -165,13 +165,13 @@ def login():
session['username'] = team.name session['username'] = team.name
session['id'] = team.id session['id'] = team.id
session['admin'] = team.admin session['admin'] = team.admin
session['nonce'] = sha512(os.urandom(10)) session['nonce'] = utils.sha512(os.urandom(10))
db.session.close() db.session.close()
logger = logging.getLogger('logins') logger = logging.getLogger('logins')
logger.warn("[{0}] {1} logged in".format(time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'))) logger.warn("[{0}] {1} logged in".format(time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8')))
if request.args.get('next') and is_safe_url(request.args.get('next')): if request.args.get('next') and utils.is_safe_url(request.args.get('next')):
return redirect(request.args.get('next')) return redirect(request.args.get('next'))
return redirect(url_for('challenges.challenges_view')) return redirect(url_for('challenges.challenges_view'))
else: # This user exists but the password is wrong else: # This user exists but the password is wrong
@@ -189,6 +189,6 @@ def login():
@auth.route('/logout') @auth.route('/logout')
def logout(): def logout():
if authed(): if utils.authed():
session.clear() session.clear()
return redirect(url_for('views.static_html')) return redirect(url_for('views.static_html'))

View File

@@ -6,37 +6,38 @@ import time
from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint
from sqlalchemy.sql import or_ 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, hide_scores
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards
from CTFd.plugins.keys import get_key_class from CTFd.plugins.keys import get_key_class
from CTFd.plugins.challenges import get_chal_class from CTFd.plugins.challenges import get_chal_class
from CTFd import utils
challenges = Blueprint('challenges', __name__) challenges = Blueprint('challenges', __name__)
@challenges.route('/challenges', methods=['GET']) @challenges.route('/challenges', methods=['GET'])
def challenges_view(): def challenges_view():
errors = [] errors = []
start = get_config('start') or 0 start = utils.get_config('start') or 0
end = get_config('end') or 0 end = utils.get_config('end') or 0
if not is_admin(): # User is not an admin if not utils.is_admin(): # User is not an admin
if not ctftime(): if not utils.ctftime():
# It is not CTF time # It is not CTF time
if view_after_ctf(): # But we are allowed to view after the CTF ends if utils.view_after_ctf(): # But we are allowed to view after the CTF ends
pass pass
else: # We are NOT allowed to view after the CTF ends else: # We are NOT allowed to view after the CTF ends
if get_config('start') and not ctf_started(): if utils.get_config('start') and not utils.ctf_started():
errors.append('{} has not started yet'.format(ctf_name())) errors.append('{} has not started yet'.format(utils.ctf_name()))
if (get_config('end') and ctf_ended()) and not view_after_ctf(): if (utils.get_config('end') and utils.ctf_ended()) and not utils.view_after_ctf():
errors.append('{} has ended'.format(ctf_name())) errors.append('{} has ended'.format(utils.ctf_name()))
return render_template('chals.html', errors=errors, start=int(start), end=int(end)) return render_template('chals.html', errors=errors, start=int(start), end=int(end))
if get_config('verify_emails') and not is_verified(): # User is not confirmed if utils.get_config('verify_emails') and not utils.is_verified(): # User is not confirmed
return redirect(url_for('auth.confirm_user')) return redirect(url_for('auth.confirm_user'))
if user_can_view_challenges(): # Do we allow unauthenticated users? if utils.user_can_view_challenges(): # Do we allow unauthenticated users?
if get_config('start') and not ctf_started(): if utils.get_config('start') and not utils.ctf_started():
errors.append('{} has not started yet'.format(ctf_name())) errors.append('{} has not started yet'.format(utils.ctf_name()))
if (get_config('end') and ctf_ended()) and not view_after_ctf(): if (utils.get_config('end') and utils.ctf_ended()) and not utils.view_after_ctf():
errors.append('{} has ended'.format(ctf_name())) errors.append('{} has ended'.format(utils.ctf_name()))
return render_template('chals.html', errors=errors, start=int(start), end=int(end)) return render_template('chals.html', errors=errors, start=int(start), end=int(end))
else: else:
return redirect(url_for('auth.login', next='challenges')) return redirect(url_for('auth.login', next='challenges'))
@@ -44,13 +45,13 @@ def challenges_view():
@challenges.route('/chals', methods=['GET']) @challenges.route('/chals', methods=['GET'])
def chals(): def chals():
if not is_admin(): if not utils.is_admin():
if not ctftime(): if not utils.ctftime():
if view_after_ctf(): if utils.view_after_ctf():
pass pass
else: else:
return redirect(url_for('views.static_html')) return redirect(url_for('views.static_html'))
if user_can_view_challenges() and (ctf_started() or is_admin()): if utils.user_can_view_challenges() and (utils.ctf_started() or utils.is_admin()):
chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).order_by(Challenges.value).all() chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).order_by(Challenges.value).all()
json = {'game': []} json = {'game': []}
for x in chals: for x in chals:
@@ -77,14 +78,14 @@ def chals():
@challenges.route('/chals/solves') @challenges.route('/chals/solves')
def solves_per_chal(): def solves_per_chal():
if not user_can_view_challenges(): if not utils.user_can_view_challenges():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves')).join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).group_by(Solves.chalid).subquery() solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves')).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, Challenges.name) \ solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all() .join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
json = {} json = {}
if hide_scores(): if utils.hide_scores():
for chal, count, name in solves: for chal, count, name in solves:
json[chal] = -1 json[chal] = -1
else: else:
@@ -100,10 +101,10 @@ def solves(teamid=None):
solves = None solves = None
awards = None awards = None
if teamid is None: if teamid is None:
if is_admin(): if utils.is_admin():
solves = Solves.query.filter_by(teamid=session['id']).all() solves = Solves.query.filter_by(teamid=session['id']).all()
elif user_can_view_challenges(): elif utils.user_can_view_challenges():
if authed(): if utils.authed():
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.teamid == session['id'], Teams.banned == False).all() solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.teamid == session['id'], Teams.banned == False).all()
else: else:
return jsonify({'solves': []}) return jsonify({'solves': []})
@@ -121,7 +122,7 @@ def solves(teamid=None):
'team': solve.teamid, 'team': solve.teamid,
'value': solve.chal.value, 'value': solve.chal.value,
'category': solve.chal.category, 'category': solve.chal.category,
'time': unix_time(solve.date) 'time': utils.unix_time(solve.date)
}) })
if awards: if awards:
for award in awards: for award in awards:
@@ -131,7 +132,7 @@ def solves(teamid=None):
'team': award.teamid, 'team': award.teamid,
'value': award.value, 'value': award.value,
'category': award.category or "Award", 'category': award.category or "Award",
'time': unix_time(award.date) 'time': utils.unix_time(award.date)
}) })
json['solves'].sort(key=lambda k: k['time']) json['solves'].sort(key=lambda k: k['time'])
return jsonify(json) return jsonify(json)
@@ -139,13 +140,13 @@ def solves(teamid=None):
@challenges.route('/maxattempts') @challenges.route('/maxattempts')
def attempts(): def attempts():
if not user_can_view_challenges(): if not utils.user_can_view_challenges():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
chals = Challenges.query.add_columns('id').all() chals = Challenges.query.add_columns('id').all()
json = {'maxattempts': []} json = {'maxattempts': []}
for chal, chalid in chals: for chal, chalid in chals:
fails = WrongKeys.query.filter_by(teamid=session['id'], chalid=chalid).count() fails = WrongKeys.query.filter_by(teamid=session['id'], chalid=chalid).count()
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0: if fails >= int(utils.get_config("max_tries")) and int(utils.get_config("max_tries")) > 0:
json['maxattempts'].append({'chalid': chalid}) json['maxattempts'].append({'chalid': chalid})
return jsonify(json) return jsonify(json)
@@ -161,11 +162,11 @@ def fails(teamid):
@challenges.route('/chal/<int:chalid>/solves', methods=['GET']) @challenges.route('/chal/<int:chalid>/solves', methods=['GET'])
def who_solved(chalid): def who_solved(chalid):
if not user_can_view_challenges(): if not utils.user_can_view_challenges():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
json = {'teams': []} json = {'teams': []}
if hide_scores(): if utils.hide_scores():
return jsonify(json) return jsonify(json)
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid, Teams.banned == False).order_by(Solves.date.asc()) solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid, Teams.banned == False).order_by(Solves.date.asc())
for solve in solves: for solve in solves:
@@ -175,19 +176,19 @@ def who_solved(chalid):
@challenges.route('/chal/<int:chalid>', methods=['POST']) @challenges.route('/chal/<int:chalid>', methods=['POST'])
def chal(chalid): def chal(chalid):
if ctf_ended() and not view_after_ctf(): if utils.ctf_ended() and not utils.view_after_ctf():
return redirect(url_for('challenges.challenges_view')) return redirect(url_for('challenges.challenges_view'))
if not user_can_view_challenges(): if not utils.user_can_view_challenges():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
if authed() and is_verified() and (ctf_started() or view_after_ctf()): if utils.authed() and utils.is_verified() and (utils.ctf_started() or utils.view_after_ctf()):
fails = WrongKeys.query.filter_by(teamid=session['id'], chalid=chalid).count() fails = WrongKeys.query.filter_by(teamid=session['id'], chalid=chalid).count()
logger = logging.getLogger('keys') logger = logging.getLogger('keys')
data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id'])) data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), utils.get_kpm(session['id']))
print("[{0}] {1} submitted {2} with kpm {3}".format(*data)) print("[{0}] {1} submitted {2} with kpm {3}".format(*data))
# Anti-bruteforce / submitting keys too quickly # Anti-bruteforce / submitting keys too quickly
if get_kpm(session['id']) > 10: if utils.get_kpm(session['id']) > 10:
if ctftime(): if utils.ctftime():
wrong = WrongKeys(session['id'], chalid, request.form['key']) wrong = WrongKeys(session['id'], chalid, request.form['key'])
db.session.add(wrong) db.session.add(wrong)
db.session.commit() db.session.commit()
@@ -214,15 +215,15 @@ def chal(chalid):
chal_class = get_chal_class(chal.type) chal_class = get_chal_class(chal.type)
if chal_class.solve(chal, provided_key): if chal_class.solve(chal, provided_key):
if ctftime(): if utils.ctftime():
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=provided_key) solve = Solves(chalid=chalid, teamid=session['id'], ip=utils.get_ip(), flag=provided_key)
db.session.add(solve) db.session.add(solve)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data)) logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
return jsonify({'status': '1', 'message': 'Correct'}) return jsonify({'status': '1', 'message': 'Correct'})
if ctftime(): if utils.ctftime():
wrong = WrongKeys(teamid=session['id'], chalid=chalid, flag=provided_key) wrong = WrongKeys(teamid=session['id'], chalid=chalid, flag=provided_key)
db.session.add(wrong) db.session.add(wrong)
db.session.commit() db.session.commit()

View File

@@ -1,9 +1,10 @@
from flask import render_template, jsonify, Blueprint, redirect, url_for, request from flask import render_template, jsonify, Blueprint, redirect, url_for, request
from sqlalchemy.sql.expression import union_all from sqlalchemy.sql.expression import union_all
from CTFd.utils import unix_time, authed, get_config, hide_scores
from CTFd.models import db, Teams, Solves, Awards, Challenges from CTFd.models import db, Teams, Solves, Awards, Challenges
from CTFd import utils
scoreboard = Blueprint('scoreboard', __name__) scoreboard = Blueprint('scoreboard', __name__)
@@ -35,9 +36,9 @@ def get_standings(admin=False, count=None):
@scoreboard.route('/scoreboard') @scoreboard.route('/scoreboard')
def scoreboard_view(): def scoreboard_view():
if get_config('view_scoreboard_if_authed') and not authed(): if utils.get_config('view_scoreboard_if_authed') and not utils.authed():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
if hide_scores(): if utils.hide_scores():
return render_template('scoreboard.html', errors=['Scores are currently hidden']) return render_template('scoreboard.html', errors=['Scores are currently hidden'])
standings = get_standings() standings = get_standings()
return render_template('scoreboard.html', teams=standings) return render_template('scoreboard.html', teams=standings)
@@ -46,9 +47,9 @@ def scoreboard_view():
@scoreboard.route('/scores') @scoreboard.route('/scores')
def scores(): def scores():
json = {'standings': []} json = {'standings': []}
if get_config('view_scoreboard_if_authed') and not authed(): if utils.get_config('view_scoreboard_if_authed') and not utils.authed():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
if hide_scores(): if utils.hide_scores():
return jsonify(json) return jsonify(json)
standings = get_standings() standings = get_standings()
@@ -61,9 +62,9 @@ def scores():
@scoreboard.route('/top/<int:count>') @scoreboard.route('/top/<int:count>')
def topteams(count): def topteams(count):
json = {'scores': {}} json = {'scores': {}}
if get_config('view_scoreboard_if_authed') and not authed(): if utils.get_config('view_scoreboard_if_authed') and not utils.authed():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
if hide_scores(): if utils.hide_scores():
return jsonify(json) return jsonify(json)
if count > 20 or count < 0: if count > 20 or count < 0:
@@ -80,14 +81,14 @@ def topteams(count):
'chal': x.chalid, 'chal': x.chalid,
'team': x.teamid, 'team': x.teamid,
'value': x.chal.value, 'value': x.chal.value,
'time': unix_time(x.date) 'time': utils.unix_time(x.date)
}) })
for award in awards: for award in awards:
json['scores'][team.name].append({ json['scores'][team.name].append({
'chal': None, 'chal': None,
'team': award.teamid, 'team': award.teamid,
'value': award.value, 'value': award.value,
'time': unix_time(award.date) 'time': utils.unix_time(award.date)
}) })
json['scores'][team.name] = sorted(json['scores'][team.name], key=lambda k: k['time']) json['scores'][team.name] = sorted(json['scores'][team.name], key=lambda k: k['time'])
return jsonify(json) return jsonify(json)

View File

@@ -198,8 +198,10 @@ $('#update-user').click(function(e){
console.log($.grep(user_data, function(e){ return e.name == 'name'; })[0]['value']) console.log($.grep(user_data, function(e){ return e.name == 'name'; })[0]['value'])
console.log(row.find('.team-name > a')) console.log(row.find('.team-name > a'))
row.find('.team-name > a').text( $.grep(user_data, function(e){ return e.name == 'name'; })[0]['value'] ); row.find('.team-name > a').text( $.grep(user_data, function(e){ return e.name == 'name'; })[0]['value'] );
row.find('.team-email').text( $.grep(user_data, function(e){ return e.name == 'email'; })[0]['value'] ); var new_email = $.grep(user_data, function(e){ return e.name == 'email'; })[0]['value'];
if (new_email){
row.find('.team-email').text( new_email );
}
row.find('.team-website > a').empty() row.find('.team-website > a').empty()
var website = $.grep(user_data, function(e){ return e.name == 'website'; })[0]['value'] var website = $.grep(user_data, function(e){ return e.name == 'website'; })[0]['value']
row.find('.team-website').append($('<a>').attr('href', website).text(website)); row.find('.team-website').append($('<a>').attr('href', website).text(website));

View File

@@ -5,9 +5,9 @@ from flask import current_app as app, render_template, request, redirect, abort,
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
from passlib.hash import bcrypt_sha256 from passlib.hash import bcrypt_sha256
from CTFd.utils import authed, is_setup, validate_url, get_config, set_config, sha512, cache, ctftime, view_after_ctf, ctf_started, \
is_admin, hide_scores
from CTFd.models import db, Teams, Solves, Awards, Files, Pages from CTFd.models import db, Teams, Solves, Awards, Files, Pages
from CTFd.utils import cache
from CTFd import utils
views = Blueprint('views', __name__) views = Blueprint('views', __name__)
@@ -16,7 +16,7 @@ views = Blueprint('views', __name__)
def redirect_setup(): def redirect_setup():
if request.path.startswith("/static"): if request.path.startswith("/static"):
return return
if not is_setup() and request.path != "/setup": if not utils.is_setup() and request.path != "/setup":
return redirect(url_for('views.setup')) return redirect(url_for('views.setup'))
@@ -25,15 +25,15 @@ def setup():
# with app.app_context(): # with app.app_context():
# admin = Teams.query.filter_by(admin=True).first() # admin = Teams.query.filter_by(admin=True).first()
if not is_setup(): if not utils.is_setup():
if not session.get('nonce'): if not session.get('nonce'):
session['nonce'] = sha512(os.urandom(10)) session['nonce'] = utils.sha512(os.urandom(10))
if request.method == 'POST': if request.method == 'POST':
ctf_name = request.form['ctf_name'] ctf_name = request.form['ctf_name']
ctf_name = set_config('ctf_name', ctf_name) ctf_name = utils.set_config('ctf_name', ctf_name)
# CSS # CSS
css = set_config('start', '') css = utils.set_config('start', '')
# Admin user # Admin user
name = request.form['name'] name = request.form['name']
@@ -56,29 +56,29 @@ def setup():
</div>""".format(request.script_root)) </div>""".format(request.script_root))
# max attempts per challenge # max attempts per challenge
max_tries = set_config("max_tries", 0) max_tries = utils.set_config("max_tries", 0)
# Start time # Start time
start = set_config('start', None) start = utils.set_config('start', None)
end = set_config('end', None) end = utils.set_config('end', None)
# Challenges cannot be viewed by unregistered users # Challenges cannot be viewed by unregistered users
view_challenges_unregistered = set_config('view_challenges_unregistered', None) view_challenges_unregistered = utils.set_config('view_challenges_unregistered', None)
# Allow/Disallow registration # Allow/Disallow registration
prevent_registration = set_config('prevent_registration', None) prevent_registration = utils.set_config('prevent_registration', None)
# Verify emails # Verify emails
verify_emails = set_config('verify_emails', None) verify_emails = utils.set_config('verify_emails', None)
mail_server = set_config('mail_server', None) mail_server = utils.set_config('mail_server', None)
mail_port = set_config('mail_port', None) mail_port = utils.set_config('mail_port', None)
mail_tls = set_config('mail_tls', None) mail_tls = utils.set_config('mail_tls', None)
mail_ssl = set_config('mail_ssl', None) mail_ssl = utils.set_config('mail_ssl', None)
mail_username = set_config('mail_username', None) mail_username = utils.set_config('mail_username', None)
mail_password = set_config('mail_password', None) mail_password = utils.set_config('mail_password', None)
setup = set_config('setup', True) setup = utils.set_config('setup', True)
db.session.add(page) db.session.add(page)
db.session.add(admin) db.session.add(admin)
@@ -87,7 +87,7 @@ def setup():
session['username'] = admin.name session['username'] = admin.name
session['id'] = admin.id session['id'] = admin.id
session['admin'] = admin.admin session['admin'] = admin.admin
session['nonce'] = sha512(os.urandom(10)) session['nonce'] = utils.sha512(os.urandom(10))
db.session.close() db.session.close()
app.setup = False app.setup = False
@@ -102,7 +102,7 @@ def setup():
# Custom CSS handler # Custom CSS handler
@views.route('/static/user.css') @views.route('/static/user.css')
def custom_css(): def custom_css():
return Response(get_config("css"), mimetype='text/css') return Response(utils.get_config("css"), mimetype='text/css')
# Static HTML files # Static HTML files
@@ -124,7 +124,7 @@ def teams(page):
page_start = results_per_page * (page - 1) page_start = results_per_page * (page - 1)
page_end = results_per_page * (page - 1) + results_per_page page_end = results_per_page * (page - 1) + results_per_page
if get_config('verify_emails'): if utils.get_config('verify_emails'):
count = Teams.query.filter_by(verified=True, banned=False).count() count = Teams.query.filter_by(verified=True, banned=False).count()
teams = Teams.query.filter_by(verified=True, banned=False).slice(page_start, page_end).all() teams = Teams.query.filter_by(verified=True, banned=False).slice(page_start, page_end).all()
else: else:
@@ -136,7 +136,7 @@ def teams(page):
@views.route('/team/<int:teamid>', methods=['GET', 'POST']) @views.route('/team/<int:teamid>', methods=['GET', 'POST'])
def team(teamid): def team(teamid):
if get_config('view_scoreboard_if_authed') and not authed(): if utils.get_config('view_scoreboard_if_utils.authed') and not utils.authed():
return redirect(url_for('auth.login', next=request.path)) return redirect(url_for('auth.login', next=request.path))
errors = [] errors = []
user = Teams.query.filter_by(id=teamid).first_or_404() user = Teams.query.filter_by(id=teamid).first_or_404()
@@ -146,7 +146,7 @@ def team(teamid):
place = user.place() place = user.place()
db.session.close() db.session.close()
if hide_scores() and teamid != session.get('id'): if utils.hide_scores() and teamid != session.get('id'):
errors.append('Scores are currently hidden') errors.append('Scores are currently hidden')
if errors: if errors:
@@ -163,7 +163,7 @@ def team(teamid):
@views.route('/profile', methods=['POST', 'GET']) @views.route('/profile', methods=['POST', 'GET'])
def profile(): def profile():
if authed(): if utils.authed():
if request.method == "POST": if request.method == "POST":
errors = [] errors = []
@@ -175,7 +175,7 @@ def profile():
user = Teams.query.filter_by(id=session['id']).first() user = Teams.query.filter_by(id=session['id']).first()
if not get_config('prevent_name_change'): if not utils.get_config('prevent_name_change'):
names = Teams.query.filter_by(name=name).first() names = Teams.query.filter_by(name=name).first()
name_len = len(request.form['name']) == 0 name_len = len(request.form['name']) == 0
@@ -187,13 +187,13 @@ def profile():
errors.append("Your old password doesn't match what we have.") errors.append("Your old password doesn't match what we have.")
if not valid_email: if not valid_email:
errors.append("That email doesn't look right") errors.append("That email doesn't look right")
if not get_config('prevent_name_change') and names and name != session['username']: if not utils.get_config('prevent_name_change') and names and name != session['username']:
errors.append('That team name is already taken') errors.append('That team name is already taken')
if emails and emails.id != session['id']: if emails and emails.id != session['id']:
errors.append('That email has already been used') errors.append('That email has already been used')
if not get_config('prevent_name_change') and name_len: if not utils.get_config('prevent_name_change') and name_len:
errors.append('Pick a longer team name') errors.append('Pick a longer team name')
if website.strip() and not validate_url(website): if website.strip() and not utils.validate_url(website):
errors.append("That doesn't look like a valid URL") errors.append("That doesn't look like a valid URL")
if len(errors) > 0: if len(errors) > 0:
@@ -201,11 +201,11 @@ def profile():
affiliation=affiliation, country=country, errors=errors) affiliation=affiliation, country=country, errors=errors)
else: else:
team = Teams.query.filter_by(id=session['id']).first() team = Teams.query.filter_by(id=session['id']).first()
if not get_config('prevent_name_change'): if not utils.get_config('prevent_name_change'):
team.name = name team.name = name
if team.email != email.lower(): if team.email != email.lower():
team.email = email.lower() team.email = email.lower()
if get_config('verify_emails'): if utils.get_config('verify_emails'):
team.verified = False team.verified = False
session['username'] = team.name session['username'] = team.name
@@ -224,8 +224,8 @@ def profile():
website = user.website website = user.website
affiliation = user.affiliation affiliation = user.affiliation
country = user.country country = user.country
prevent_name_change = get_config('prevent_name_change') prevent_name_change = utils.get_config('prevent_name_change')
confirm_email = get_config('verify_emails') and not user.verified confirm_email = utils.get_config('verify_emails') and not user.verified
return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation, return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation,
country=country, prevent_name_change=prevent_name_change, confirm_email=confirm_email) country=country, prevent_name_change=prevent_name_change, confirm_email=confirm_email)
else: else:
@@ -237,9 +237,9 @@ def profile():
def file_handler(path): def file_handler(path):
f = Files.query.filter_by(location=path).first_or_404() f = Files.query.filter_by(location=path).first_or_404()
if f.chal: if f.chal:
if not is_admin(): if not utils.is_admin():
if not ctftime(): if not utils.ctftime():
if view_after_ctf() and ctf_started(): if utils.view_after_ctf() and utils.ctf_started():
pass pass
else: else:
abort(403) abort(403)

View File

@@ -1,4 +1,5 @@
from CTFd import create_app from CTFd import create_app
from CTFd.models import *
from sqlalchemy_utils import database_exists, create_database, drop_database from sqlalchemy_utils import database_exists, create_database, drop_database
from sqlalchemy.engine.url import make_url from sqlalchemy.engine.url import make_url
@@ -57,4 +58,63 @@ def login_as_user(app, name="user", password="password"):
"nonce": sess.get('nonce') "nonce": sess.get('nonce')
} }
client.post('/login', data=data) client.post('/login', data=data)
return client return client
def gen_challenge(db, name='chal_name', description='chal_description', value=100, category='chal_category', type=0):
chal = Challenges(name, description, value, category)
db.session.add(chal)
db.session.commit()
return chal
def gen_award(db, teamid, name="award_name", value=100):
award = Awards(teamid, name, value)
db.session.add(award)
db.session.commit()
return award
def gen_tag(db, chal, tag='tag_tag'):
tag = Tags(chal, tag)
db.session.add(tag)
db.session.commit()
return tag
def gen_file():
pass
def gen_key(db, chal, flag='flag', key_type=0):
key = Keys(chal, flag, key_type)
db.session.add(key)
db.session.commit()
return key
def gen_team(db, name='name', email='user@ctfd.io', password='password'):
team = Teams(name, email, password)
db.session.add(team)
db.session.commit()
return team
def gen_solve(db, chalid, teamid, ip='127.0.0.1', flag='rightkey'):
solve = Solves(chalid, teamid, ip, flag)
db.session.add(solve)
db.session.commit()
return solve
def gen_wrongkey(db, teamid, chalid, flag='wrongkey'):
wrongkey = WrongKeys(teamid, chalid, flag)
db.session.add(wrongkey)
db.session.commit()
return wrongkey
def gen_tracking(db, ip, team):
tracking = Tracking(ip, team)
db.session.add(tracking)
db.session.commit()
return tracking

View File

@@ -0,0 +1,76 @@
from tests.helpers import create_ctfd, register_user, login_as_user
from CTFd.models import Teams
def test_admin_panel():
"""Does the admin panel return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin')
assert r.status_code == 302
r = client.get('/admin/graphs')
assert r.status_code == 200
def test_admin_pages():
"""Does admin pages return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/pages')
assert r.status_code == 200
def test_admin_teams():
"""Does admin teams return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/teams')
assert r.status_code == 200
def test_admin_scoreboard():
"""Does admin scoreboard return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/scoreboard')
assert r.status_code == 200
def test_admin_containers():
"""Does admin containers return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/containers')
assert r.status_code == 200
def test_admin_chals():
"""Does admin chals return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/chals')
assert r.status_code == 200
def test_admin_statistics():
"""Does admin statistics return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/statistics')
assert r.status_code == 200
def test_admin_config():
"""Does admin config return a 200 by default"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/config')
assert r.status_code == 200

View File

@@ -1,5 +1,6 @@
from tests.helpers import create_ctfd, register_user, login_as_user from tests.helpers import create_ctfd, register_user, login_as_user, gen_challenge
from CTFd.models import Teams from CTFd.models import Teams
import json
def test_index(): def test_index():
@@ -191,4 +192,16 @@ def test_user_get_reset_password():
register_user(app) register_user(app)
client = app.test_client() client = app.test_client()
r = client.get('/reset_password') r = client.get('/reset_password')
assert r.status_code == 200 assert r.status_code == 200
def test_viewing_challenges():
"""Test that users can see added challenges"""
app = create_ctfd()
with app.app_context():
register_user(app)
client = login_as_user(app)
gen_challenge(app.db)
r = client.get('/chals')
chals = json.loads(r.data)
assert len(chals['game']) == 1