General fixes and PEP8 enforcement (#258)

* Fixing index page links when you deploy on a subdirectory

* Updating travis for pep8

* autopep8 with just formatting changes
This commit is contained in:
Kevin Chung
2017-05-12 00:34:20 -04:00
committed by GitHub
parent b4d0d1ecab
commit e16d3a0b6e
21 changed files with 152 additions and 147 deletions

View File

@@ -5,4 +5,5 @@ python:
install: install:
- pip install -r development.txt - pip install -r development.txt
script: script:
- pep8 --ignore E501,E712 CTFd/ tests/
- nosetests - nosetests

View File

@@ -13,6 +13,7 @@ from CTFd import utils
__version__ = '1.0.2' __version__ = '1.0.2'
class ThemeLoader(FileSystemLoader): class ThemeLoader(FileSystemLoader):
def get_source(self, environment, template): def get_source(self, environment, template):
if template.startswith('admin/'): if template.startswith('admin/'):
@@ -34,21 +35,21 @@ def create_app(config='CTFd.config.Config'):
if url.drivername == 'postgres': if url.drivername == 'postgres':
url.drivername = 'postgresql' url.drivername = 'postgresql'
## Creates database if the database database does not exist # Creates database if the database database does not exist
if not database_exists(url): if not database_exists(url):
create_database(url) create_database(url)
## Register database # Register database
db.init_app(app) db.init_app(app)
## Register Flask-Migrate # Register Flask-Migrate
migrate.init_app(app, db) migrate.init_app(app, db)
## This creates tables instead of db.create_all() # This creates tables instead of db.create_all()
## Allows migrations to happen properly # Allows migrations to happen properly
migrate_upgrade() migrate_upgrade()
## Alembic sqlite support is lacking so we should just create_all anyway # Alembic sqlite support is lacking so we should just create_all anyway
if url.drivername.startswith('sqlite'): if url.drivername.startswith('sqlite'):
db.create_all() db.create_all()
@@ -59,10 +60,10 @@ def create_app(config='CTFd.config.Config'):
version = utils.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
utils.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! /*\\")
print("/*\\ Please backup your database before proceeding! /*\\") print("/*\\ Please backup your database before proceeding! /*\\")
print("/*\\ CTFd maintainers are not responsible for any data loss! /*\\") print("/*\\ CTFd maintainers are not responsible for any data loss! /*\\")

View File

@@ -142,12 +142,12 @@ def admin_hints(hintid):
db.session.add(hint) db.session.add(hint)
db.session.commit() db.session.commit()
json_data = { json_data = {
'hint': hint.hint, 'hint': hint.hint,
'type': hint.type, 'type': hint.type,
'chal': hint.chal, 'chal': hint.chal,
'cost': hint.cost, 'cost': hint.cost,
'id': hint.id 'id': hint.id
} }
db.session.close() db.session.close()
return jsonify(json_data) return jsonify(json_data)
@@ -192,7 +192,7 @@ def admin_get_values(chalid, prop):
'key': x.flag, 'key': x.flag,
'type': x.key_type, 'type': x.key_type,
'type_name': get_key_class(x.key_type).name 'type_name': get_key_class(x.key_type).name
}) })
return jsonify(json_data) return jsonify(json_data)
elif prop == 'tags': elif prop == 'tags':
tags = Tags.query.filter_by(chal=chalid).all() tags = Tags.query.filter_by(chal=chalid).all()
@@ -202,19 +202,19 @@ def admin_get_values(chalid, prop):
'id': x.id, 'id': x.id,
'chal': x.chal, 'chal': x.chal,
'tag': x.tag 'tag': x.tag
}) })
return jsonify(json_data) return jsonify(json_data)
elif prop == 'hints': elif prop == 'hints':
hints = Hints.query.filter_by(chal=chalid) hints = Hints.query.filter_by(chal=chalid)
json_data = {'hints': []} json_data = {'hints': []}
for hint in hints: for hint in hints:
json_data['hints'].append({ json_data['hints'].append({
'hint': hint.hint, 'hint': hint.hint,
'type': hint.type, 'type': hint.type,
'chal': hint.chal, 'chal': hint.chal,
'cost': hint.cost, 'cost': hint.cost,
'id': hint.id 'id': hint.id
}) })
return jsonify(json_data) return jsonify(json_data)

View File

@@ -6,6 +6,7 @@ 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'])
@admins_only @admins_only
def list_container(): def list_container():
@@ -63,4 +64,4 @@ def new_container():
files = request.files.getlist('files[]') files = request.files.getlist('files[]')
utils.create_image(name=name, buildfile=buildfile, files=files) utils.create_image(name=name, buildfile=buildfile, files=files)
utils.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

@@ -7,6 +7,7 @@ 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'])
@admins_only @admins_only
def admin_key_types(): def admin_key_types():
@@ -16,6 +17,7 @@ def admin_key_types():
return jsonify(data) return jsonify(data)
@admin_keys.route('/admin/keys', defaults={'keyid': None}, methods=['POST', 'GET']) @admin_keys.route('/admin/keys', defaults={'keyid': None}, methods=['POST', 'GET'])
@admin_keys.route('/admin/keys/<int:keyid>', methods=['POST', 'GET']) @admin_keys.route('/admin/keys/<int:keyid>', methods=['POST', 'GET'])
@admins_only @admins_only
@@ -53,7 +55,6 @@ def admin_keys_view(keyid):
return '1' return '1'
@admin_keys.route('/admin/keys/<int:keyid>/delete', methods=['POST']) @admin_keys.route('/admin/keys/<int:keyid>/delete', methods=['POST'])
@admins_only @admins_only
def admin_delete_keys(keyid): def admin_delete_keys(keyid):
@@ -62,4 +63,4 @@ def admin_delete_keys(keyid):
db.session.delete(key) db.session.delete(key)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
return '1' return '1'

View File

@@ -6,6 +6,7 @@ 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'])
@admins_only @admins_only
def admin_css(): def admin_css():
@@ -59,4 +60,4 @@ def delete_page(pageroute):
db.session.delete(page) db.session.delete(page)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
return '1' return '1'

View File

@@ -7,6 +7,7 @@ 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')
@admins_only @admins_only
def admin_scoreboard_view(): def admin_scoreboard_view():
@@ -24,4 +25,4 @@ def admin_scores():
json_data = {'teams': []} json_data = {'teams': []}
for i, x in enumerate(teams): for i, x in enumerate(teams):
json_data['teams'].append({'place': i + 1, 'id': x.teamid, 'name': x.name, 'score': int(x.score)}) json_data['teams'].append({'place': i + 1, 'id': x.teamid, 'name': x.name, 'score': int(x.score)})
return jsonify(json_data) return jsonify(json_data)

View File

@@ -6,11 +6,13 @@ 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')
@admins_only @admins_only
def admin_graphs(): def admin_graphs():
return render_template('admin/graphs.html') return render_template('admin/graphs.html')
@admin_statistics.route('/admin/graphs/<graph_type>') @admin_statistics.route('/admin/graphs/<graph_type>')
@admins_only @admins_only
def admin_graph(graph_type): def admin_graph(graph_type):
@@ -31,6 +33,7 @@ def admin_graph(graph_type):
json_data[name] = count json_data[name] = count
return jsonify(json_data) return jsonify(json_data)
@admin_statistics.route('/admin/statistics', methods=['GET']) @admin_statistics.route('/admin/statistics', methods=['GET'])
@admins_only @admins_only
def admin_stats(): def admin_stats():
@@ -66,6 +69,7 @@ def admin_stats():
most_solved=most_solved, most_solved=most_solved,
least_solved=least_solved) least_solved=least_solved)
@admin_statistics.route('/admin/wrong_keys', defaults={'page': '1'}, methods=['GET']) @admin_statistics.route('/admin/wrong_keys', defaults={'page': '1'}, methods=['GET'])
@admin_statistics.route('/admin/wrong_keys/<int:page>', methods=['GET']) @admin_statistics.route('/admin/wrong_keys/<int:page>', methods=['GET'])
@admins_only @admins_only
@@ -77,11 +81,11 @@ def admin_wrong_key(page):
wrong_keys = WrongKeys.query.add_columns(WrongKeys.id, WrongKeys.chalid, WrongKeys.flag, WrongKeys.teamid, WrongKeys.date, 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')) \ Challenges.name.label('chal_name'), Teams.name.label('team_name')) \
.join(Challenges) \ .join(Challenges) \
.join(Teams) \ .join(Teams) \
.order_by(WrongKeys.date.desc()) \ .order_by(WrongKeys.date.desc()) \
.slice(page_start, page_end) \ .slice(page_start, page_end) \
.all() .all()
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0] 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) pages = int(wrong_count / results_per_page) + (wrong_count % results_per_page > 0)
@@ -100,13 +104,13 @@ def admin_correct_key(page):
solves = Solves.query.add_columns(Solves.id, Solves.chalid, Solves.teamid, Solves.date, Solves.flag, 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')) \ Challenges.name.label('chal_name'), Teams.name.label('team_name')) \
.join(Challenges) \ .join(Challenges) \
.join(Teams) \ .join(Teams) \
.order_by(Solves.date.desc()) \ .order_by(Solves.date.desc()) \
.slice(page_start, page_end) \ .slice(page_start, page_end) \
.all() .all()
solve_count = db.session.query(db.func.count(Solves.id)).first()[0] 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) 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) return render_template('admin/correct_keys.html', solves=solves, pages=pages, curr_page=page)

View File

@@ -8,6 +8,7 @@ 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'})
@admin_teams.route('/admin/teams/<int:page>') @admin_teams.route('/admin/teams/<int:page>')
@admins_only @admins_only
@@ -225,6 +226,7 @@ def delete_wrong_key(keyid):
db.session.close() db.session.close()
return '1' return '1'
@admin_teams.route('/admin/awards/<int:award_id>/delete', methods=['POST']) @admin_teams.route('/admin/awards/<int:award_id>/delete', methods=['POST'])
@admins_only @admins_only
def delete_award(award_id): def delete_award(award_id):
@@ -234,6 +236,7 @@ def delete_award(award_id):
db.session.close() db.session.close()
return '1' return '1'
@admin_teams.route('/admin/teams/<int:teamid>/awards', methods=['GET']) @admin_teams.route('/admin/teams/<int:teamid>/awards', methods=['GET'])
@admins_only @admins_only
def admin_awards(teamid): def admin_awards(teamid):
@@ -270,4 +273,4 @@ def create_award():
return '1' return '1'
except Exception as e: except Exception as e:
print(e) print(e)
return '0' return '0'

View File

@@ -19,7 +19,7 @@ auth = Blueprint('auth', __name__)
def confirm_user(data=None): def confirm_user(data=None):
if not utils.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:
s = Signer(app.config['SECRET_KEY']) s = Signer(app.config['SECRET_KEY'])
email = s.unsign(urllib.unquote_plus(data.decode('base64'))) email = s.unsign(urllib.unquote_plus(data.decode('base64')))
@@ -36,7 +36,7 @@ def confirm_user(data=None):
if utils.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 utils.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()
@@ -130,15 +130,15 @@ def register():
session['admin'] = team.admin session['admin'] = team.admin
session['nonce'] = utils.sha512(os.urandom(10)) session['nonce'] = utils.sha512(os.urandom(10))
if utils.can_send_mail() and utils.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"),
request.form['name'].encode('utf-8'), request.form['name'].encode('utf-8'),
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 utils.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.
utils.sendmail(request.form['email'], "You've successfully registered for {}".format(utils.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()
@@ -159,9 +159,9 @@ def login():
if team: if team:
if team and bcrypt_sha256.verify(request.form['password'], team.password): if team and bcrypt_sha256.verify(request.form['password'], team.password):
try: try:
session.regenerate() # NO SESSION FIXATION FOR YOU session.regenerate() # NO SESSION FIXATION FOR YOU
except: except:
pass # TODO: Some session objects don't implement regenerate :( pass # TODO: Some session objects don't implement regenerate :(
session['username'] = team.name session['username'] = team.name
session['id'] = team.id session['id'] = team.id
session['admin'] = team.admin session['admin'] = team.admin
@@ -174,7 +174,7 @@ def login():
if request.args.get('next') and utils.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
errors.append("Your username or password is incorrect") errors.append("Your username or password is incorrect")
db.session.close() db.session.close()
return render_template('login.html', errors=errors) return render_template('login.html', errors=errors)

View File

@@ -14,6 +14,7 @@ from CTFd import utils
challenges = Blueprint('challenges', __name__) challenges = Blueprint('challenges', __name__)
@challenges.route('/hints/<int:hintid>', methods=['GET', 'POST']) @challenges.route('/hints/<int:hintid>', methods=['GET', 'POST'])
def hints_view(hintid): def hints_view(hintid):
hint = Hints.query.filter_by(id=hintid).first_or_404() hint = Hints.query.filter_by(id=hintid).first_or_404()
@@ -22,15 +23,15 @@ def hints_view(hintid):
if request.method == 'GET': if request.method == 'GET':
if unlock: if unlock:
return jsonify({ return jsonify({
'hint': hint.hint, 'hint': hint.hint,
'chal': hint.chal, 'chal': hint.chal,
'cost': hint.cost 'cost': hint.cost
}) })
else: else:
return jsonify({ return jsonify({
'chal': hint.chal, 'chal': hint.chal,
'cost': hint.cost 'cost': hint.cost
}) })
elif request.method == 'POST': elif request.method == 'POST':
if not unlock: if not unlock:
team = Teams.query.filter_by(id=session['id']).first() team = Teams.query.filter_by(id=session['id']).first()
@@ -42,18 +43,18 @@ def hints_view(hintid):
db.session.add(award) db.session.add(award)
db.session.commit() db.session.commit()
json_data = { json_data = {
'hint': hint.hint, 'hint': hint.hint,
'chal': hint.chal, 'chal': hint.chal,
'cost': hint.cost 'cost': hint.cost
} }
db.session.close() db.session.close()
return jsonify(json_data) return jsonify(json_data)
else: else:
json_data = { json_data = {
'hint': hint.hint, 'hint': hint.hint,
'chal': hint.chal, 'chal': hint.chal,
'cost': hint.cost 'cost': hint.cost
} }
db.session.close() db.session.close()
return jsonify(json_data) return jsonify(json_data)
@@ -63,10 +64,10 @@ def challenges_view():
errors = [] errors = []
start = utils.get_config('start') or 0 start = utils.get_config('start') or 0
end = utils.get_config('end') or 0 end = utils.get_config('end') or 0
if not utils.is_admin(): # User is not an admin if not utils.is_admin(): # User is not an admin
if not utils.ctftime(): if not utils.ctftime():
# It is not CTF time # It is not CTF time
if utils.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 utils.get_config('start') and not utils.ctf_started(): if utils.get_config('start') and not utils.ctf_started():
@@ -74,9 +75,9 @@ def challenges_view():
if (utils.get_config('end') and utils.ctf_ended()) and not utils.view_after_ctf(): if (utils.get_config('end') and utils.ctf_ended()) and not utils.view_after_ctf():
errors.append('{} has ended'.format(utils.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 utils.get_config('verify_emails') and not utils.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 utils.user_can_view_challenges(): # Do we allow unauthenticated users? if utils.user_can_view_challenges(): # Do we allow unauthenticated users?
if utils.get_config('start') and not utils.ctf_started(): if utils.get_config('start') and not utils.ctf_started():
errors.append('{} has not started yet'.format(utils.ctf_name())) errors.append('{} has not started yet'.format(utils.ctf_name()))
if (utils.get_config('end') and utils.ctf_ended()) and not utils.view_after_ctf(): if (utils.get_config('end') and utils.ctf_ended()) and not utils.view_after_ctf():
@@ -104,9 +105,9 @@ def chals():
hints = [] hints = []
for hint in Hints.query.filter_by(chal=x.id).all(): for hint in Hints.query.filter_by(chal=x.id).all():
if hint.id in unlocked_hints: if hint.id in unlocked_hints:
hints.append({'id':hint.id, 'cost':hint.cost, 'hint':hint.hint}) hints.append({'id': hint.id, 'cost': hint.cost, 'hint': hint.hint})
else: else:
hints.append({'id':hint.id, 'cost':hint.cost}) hints.append({'id': hint.id, 'cost': hint.cost})
# hints = [{'id':hint.id, 'cost':hint.cost} for hint in Hints.query.filter_by(chal=x.id).all()] # hints = [{'id':hint.id, 'cost':hint.cost} for hint in Hints.query.filter_by(chal=x.id).all()]
chal_type = get_chal_class(x.type) chal_type = get_chal_class(x.type)
json['game'].append({ json['game'].append({
@@ -293,7 +294,7 @@ def chal(chalid):
logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data)) logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data))
# return '0' # key was wrong # return '0' # key was wrong
if max_tries: if max_tries:
attempts_left = max_tries - fails - 1 ## Off by one since fails has changed since it was gotten attempts_left = max_tries - fails - 1 # Off by one since fails has changed since it was gotten
tries_str = 'tries' tries_str = 'tries'
if attempts_left == 1: if attempts_left == 1:
tries_str = 'try' tries_str = 'try'

View File

@@ -1,6 +1,6 @@
import os import os
##### GENERATE SECRET KEY ##### ''' GENERATE SECRET KEY '''
with open('.ctfd_secret_key', 'a+b') as secret: with open('.ctfd_secret_key', 'a+b') as secret:
secret.seek(0) # Seek to beginning of file since a+ mode leaves you at the end and w+ deletes the file secret.seek(0) # Seek to beginning of file since a+ mode leaves you at the end and w+ deletes the file
@@ -10,7 +10,8 @@ with open('.ctfd_secret_key', 'a+b') as secret:
secret.write(key) secret.write(key)
secret.flush() secret.flush()
##### SERVER SETTINGS ##### ''' SERVER SETTINGS '''
class Config(object): class Config(object):
''' '''
@@ -25,7 +26,6 @@ class Config(object):
''' '''
SECRET_KEY = os.environ.get('SECRET_KEY') or key SECRET_KEY = os.environ.get('SECRET_KEY') or key
''' '''
SQLALCHEMY_DATABASE_URI is the URI that specifies the username, password, hostname, port, and database of the server SQLALCHEMY_DATABASE_URI is the URI that specifies the username, password, hostname, port, and database of the server
used to hold the CTFd database. used to hold the CTFd database.
@@ -34,52 +34,44 @@ class Config(object):
''' '''
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///{}/ctfd.db'.format(os.path.dirname(os.path.abspath(__file__))) SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///{}/ctfd.db'.format(os.path.dirname(os.path.abspath(__file__)))
''' '''
SQLALCHEMY_TRACK_MODIFICATIONS is automatically disabled to suppress warnings and save memory. You should only enable SQLALCHEMY_TRACK_MODIFICATIONS is automatically disabled to suppress warnings and save memory. You should only enable
this if you need it. this if you need it.
''' '''
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
''' '''
SESSION_TYPE is a configuration value used for Flask-Session. It is currently unused in CTFd. SESSION_TYPE is a configuration value used for Flask-Session. It is currently unused in CTFd.
http://pythonhosted.org/Flask-Session/#configuration http://pythonhosted.org/Flask-Session/#configuration
''' '''
SESSION_TYPE = "filesystem" SESSION_TYPE = "filesystem"
''' '''
SESSION_FILE_DIR is a configuration value used for Flask-Session. It is currently unused in CTFd. SESSION_FILE_DIR is a configuration value used for Flask-Session. It is currently unused in CTFd.
http://pythonhosted.org/Flask-Session/#configuration http://pythonhosted.org/Flask-Session/#configuration
''' '''
SESSION_FILE_DIR = "/tmp/flask_session" SESSION_FILE_DIR = "/tmp/flask_session"
''' '''
SESSION_COOKIE_HTTPONLY controls if cookies should be set with the HttpOnly flag. SESSION_COOKIE_HTTPONLY controls if cookies should be set with the HttpOnly flag.
''' '''
SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True
''' '''
PERMANENT_SESSION_LIFETIME is the lifetime of a session. PERMANENT_SESSION_LIFETIME is the lifetime of a session.
''' '''
PERMANENT_SESSION_LIFETIME = 604800 # 7 days in seconds PERMANENT_SESSION_LIFETIME = 604800 # 7 days in seconds
''' '''
HOST specifies the hostname where the CTFd instance will exist. It is currently unused. HOST specifies the hostname where the CTFd instance will exist. It is currently unused.
''' '''
HOST = ".ctfd.io" HOST = ".ctfd.io"
''' '''
MAILFROM_ADDR is the email address that emails are sent from if not overridden in the configuration panel. MAILFROM_ADDR is the email address that emails are sent from if not overridden in the configuration panel.
''' '''
MAILFROM_ADDR = "noreply@ctfd.io" MAILFROM_ADDR = "noreply@ctfd.io"
''' '''
UPLOAD_FOLDER is the location where files are uploaded. UPLOAD_FOLDER is the location where files are uploaded.
The default destination is the CTFd/uploads folder. If you need Amazon S3 files The default destination is the CTFd/uploads folder. If you need Amazon S3 files
@@ -87,14 +79,12 @@ class Config(object):
''' '''
UPLOAD_FOLDER = os.path.join(os.path.dirname(__file__), 'uploads') UPLOAD_FOLDER = os.path.join(os.path.dirname(__file__), 'uploads')
''' '''
TEMPLATES_AUTO_RELOAD specifies whether Flask should check for modifications to templates and TEMPLATES_AUTO_RELOAD specifies whether Flask should check for modifications to templates and
reload them automatically reload them automatically
''' '''
TEMPLATES_AUTO_RELOAD = True TEMPLATES_AUTO_RELOAD = True
''' '''
TRUSTED_PROXIES defines a set of regular expressions used for finding a user's IP address if the CTFd instance TRUSTED_PROXIES defines a set of regular expressions used for finding a user's IP address if the CTFd instance
is behind a proxy. If you are running a CTF and users are on the same network as you, you may choose to remove is behind a proxy. If you are running a CTF and users are on the same network as you, you may choose to remove
@@ -114,7 +104,6 @@ class Config(object):
'^192\.168\.' '^192\.168\.'
] ]
''' '''
CACHE_TYPE specifies how CTFd should cache configuration values. If CACHE_TYPE is set to 'redis', CTFd will make use CACHE_TYPE specifies how CTFd should cache configuration values. If CACHE_TYPE is set to 'redis', CTFd will make use
of the REDIS_URL specified in environment variables. You can also choose to hardcode the REDIS_URL here. of the REDIS_URL specified in environment variables. You can also choose to hardcode the REDIS_URL here.

View File

@@ -1,10 +1,12 @@
from CTFd.plugins.keys import get_key_class from CTFd.plugins.keys import get_key_class
from CTFd.models import db, Keys from CTFd.models import db, Keys
class BaseChallenge(object): class BaseChallenge(object):
id = None id = None
name = None name = None
class CTFdStandardChallenge(BaseChallenge): class CTFdStandardChallenge(BaseChallenge):
id = 0 id = 0
name = "standard" name = "standard"
@@ -19,11 +21,12 @@ class CTFdStandardChallenge(BaseChallenge):
CHALLENGE_CLASSES = { CHALLENGE_CLASSES = {
0 : CTFdStandardChallenge 0: CTFdStandardChallenge
} }
def get_chal_class(class_id): def get_chal_class(class_id):
cls = CHALLENGE_CLASSES.get(class_id) cls = CHALLENGE_CLASSES.get(class_id)
if cls is None: if cls is None:
raise KeyError raise KeyError
return cls return cls

View File

@@ -11,6 +11,7 @@ class BaseKey(object):
def compare(self, saved, provided): def compare(self, saved, provided):
return True return True
class CTFdStaticKey(BaseKey): class CTFdStaticKey(BaseKey):
id = 0 id = 0
name = "static" name = "static"
@@ -36,8 +37,8 @@ class CTFdRegexKey(BaseKey):
KEY_CLASSES = { KEY_CLASSES = {
0 : CTFdStaticKey, 0: CTFdStaticKey,
1 : CTFdRegexKey 1: CTFdRegexKey
} }
@@ -45,4 +46,4 @@ def get_key_class(class_id):
cls = KEY_CLASSES.get(class_id) cls = KEY_CLASSES.get(class_id)
if cls is None: if cls is None:
raise KeyError raise KeyError
return cls return cls

View File

@@ -10,16 +10,16 @@ scoreboard = Blueprint('scoreboard', __name__)
def get_standings(admin=False, count=None): def get_standings(admin=False, count=None):
scores = db.session.query( scores = db.session.query(
Solves.teamid.label('teamid'), Solves.teamid.label('teamid'),
db.func.sum(Challenges.value).label('score'), db.func.sum(Challenges.value).label('score'),
db.func.max(Solves.date).label('date') db.func.max(Solves.date).label('date')
).join(Challenges).group_by(Solves.teamid) ).join(Challenges).group_by(Solves.teamid)
awards = db.session.query( awards = db.session.query(
Awards.teamid.label('teamid'), Awards.teamid.label('teamid'),
db.func.sum(Awards.value).label('score'), db.func.sum(Awards.value).label('score'),
db.func.max(Awards.date).label('date') db.func.max(Awards.date).label('date')
).group_by(Awards.teamid) ).group_by(Awards.teamid)
freeze = utils.get_config('freeze') freeze = utils.get_config('freeze')
if not admin and freeze: if not admin and freeze:
@@ -29,28 +29,28 @@ def get_standings(admin=False, count=None):
results = union_all(scores, awards).alias('results') results = union_all(scores, awards).alias('results')
sumscores = db.session.query( sumscores = db.session.query(
results.columns.teamid, results.columns.teamid,
db.func.sum(results.columns.score).label('score'), db.func.sum(results.columns.score).label('score'),
db.func.max(results.columns.date).label('date') db.func.max(results.columns.date).label('date')
).group_by(results.columns.teamid).subquery() ).group_by(results.columns.teamid).subquery()
if admin: if admin:
standings_query = db.session.query( standings_query = db.session.query(
Teams.id.label('teamid'), Teams.id.label('teamid'),
Teams.name.label('name'), Teams.name.label('name'),
Teams.banned, sumscores.columns.score Teams.banned, sumscores.columns.score
)\ )\
.join(sumscores, Teams.id == sumscores.columns.teamid) \ .join(sumscores, Teams.id == sumscores.columns.teamid) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date) .order_by(sumscores.columns.score.desc(), sumscores.columns.date)
else: else:
standings_query = db.session.query( standings_query = db.session.query(
Teams.id.label('teamid'), Teams.id.label('teamid'),
Teams.name.label('name'), Teams.name.label('name'),
sumscores.columns.score sumscores.columns.score
)\ )\
.join(sumscores, Teams.id == sumscores.columns.teamid) \ .join(sumscores, Teams.id == sumscores.columns.teamid) \
.filter(Teams.banned == False) \ .filter(Teams.banned == False) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date) .order_by(sumscores.columns.score.desc(), sumscores.columns.date)
if count is None: if count is None:
standings = standings_query.all() standings = standings_query.all()
@@ -102,7 +102,6 @@ def topteams(count):
solves = Solves.query.filter_by(teamid=team.teamid) solves = Solves.query.filter_by(teamid=team.teamid)
awards = Awards.query.filter_by(teamid=team.teamid) awards = Awards.query.filter_by(teamid=team.teamid)
freeze = utils.get_config('freeze') freeze = utils.get_config('freeze')
if freeze: if freeze:
@@ -112,7 +111,6 @@ def topteams(count):
solves = solves.all() solves = solves.all()
awards = awards.all() awards = awards.all()
json['scores'][team.name] = [] json['scores'][team.name] = []
for x in solves: for x in solves:
json['scores'][team.name].append({ json['scores'][team.name].append({

View File

@@ -226,7 +226,6 @@ def is_scoreboard_frozen():
return False return False
def ctftime(): def ctftime():
""" Checks whether it's CTF time or not. """ """ Checks whether it's CTF time or not. """
@@ -308,7 +307,7 @@ def get_ip():
combined = "(" + ")|(".join(trusted_proxies) + ")" combined = "(" + ")|(".join(trusted_proxies) + ")"
route = request.access_route + [request.remote_addr] route = request.access_route + [request.remote_addr]
for addr in reversed(route): for addr in reversed(route):
if not re.match(combined, addr): # IP is not trusted but we trust the proxies if not re.match(combined, addr): # IP is not trusted but we trust the proxies
remote_addr = addr remote_addr = addr
break break
else: else:
@@ -316,7 +315,7 @@ def get_ip():
return remote_addr return remote_addr
def get_kpm(teamid): # keys per minute def get_kpm(teamid): # keys per minute
one_min_ago = datetime.datetime.utcnow() + datetime.timedelta(minutes=-1) one_min_ago = datetime.datetime.utcnow() + datetime.timedelta(minutes=-1)
return len(db.session.query(WrongKeys).filter(WrongKeys.teamid == teamid, WrongKeys.date >= one_min_ago).all()) return len(db.session.query(WrongKeys).filter(WrongKeys.teamid == teamid, WrongKeys.date >= one_min_ago).all())
@@ -355,7 +354,7 @@ def upload_file(file, chalid):
def delete_file(file_id): def delete_file(file_id):
f = Files.query.filter_by(id=file_id).first_or_404() f = Files.query.filter_by(id=file_id).first_or_404()
upload_folder = os.path.join(app.root_path, app.config['UPLOAD_FOLDER']) upload_folder = os.path.join(app.root_path, app.config['UPLOAD_FOLDER'])
if os.path.exists(os.path.join(upload_folder, f.location)): # Some kind of os.path.isfile issue on Windows... if os.path.exists(os.path.join(upload_folder, f.location)): # Some kind of os.path.isfile issue on Windows...
os.unlink(os.path.join(upload_folder, f.location)) os.unlink(os.path.join(upload_folder, f.location))
db.session.delete(f) db.session.delete(f)
db.session.commit() db.session.commit()
@@ -669,7 +668,7 @@ def export_ctf(segments=None):
] ]
} }
## Backup database # Backup database
backup = io.BytesIO() backup = io.BytesIO()
backup_zip = zipfile.ZipFile(backup, 'w') backup_zip = zipfile.ZipFile(backup, 'w')
@@ -682,7 +681,7 @@ def export_ctf(segments=None):
result_file.seek(0) result_file.seek(0)
backup_zip.writestr('db/{}.json'.format(item), result_file.read()) backup_zip.writestr('db/{}.json'.format(item), result_file.read())
## Backup uploads # Backup uploads
upload_folder = os.path.join(os.path.normpath(app.root_path), get_config('UPLOAD_FOLDER')) upload_folder = os.path.join(os.path.normpath(app.root_path), get_config('UPLOAD_FOLDER'))
for root, dirs, files in os.walk(upload_folder): for root, dirs, files in os.walk(upload_folder):
for file in files: for file in files:
@@ -730,7 +729,7 @@ def import_ctf(backup, segments=None, erase=False):
] ]
} }
## Need special handling of metadata # Need special handling of metadata
if 'metadata' in segments: if 'metadata' in segments:
meta = groups['metadata'] meta = groups['metadata']
segments.remove('metadata') segments.remove('metadata')
@@ -741,7 +740,7 @@ def import_ctf(backup, segments=None, erase=False):
path = "db/{}.json".format(item) path = "db/{}.json".format(item)
data = backup.open(path).read() data = backup.open(path).read()
## Some JSON files will be empty # Some JSON files will be empty
if data: if data:
if item == 'config': if item == 'config':
saved = json.loads(data) saved = json.loads(data)
@@ -772,11 +771,10 @@ def import_ctf(backup, segments=None, erase=False):
if container: if container:
container.buildfile = buildfile container.buildfile = buildfile
else: else:
container = Containers(name, buildfile) container = Containers(name, buildfile)
db.session.add(container) db.session.add(container)
db.session.commit() db.session.commit()
for segment in segments: for segment in segments:
group = groups[segment] group = groups[segment]
for item in group: for item in group:
@@ -791,24 +789,24 @@ def import_ctf(backup, segments=None, erase=False):
else: else:
continue continue
## Extracting files # Extracting files
files = [f for f in backup.namelist() if f.startswith('uploads/')] files = [f for f in backup.namelist() if f.startswith('uploads/')]
upload_folder = app.config.get('UPLOAD_FOLDER') upload_folder = app.config.get('UPLOAD_FOLDER')
for f in files: for f in files:
filename = f.split(os.sep, 1) filename = f.split(os.sep, 1)
if len(filename) < 2: ## just an empty uploads directory (e.g. uploads/) if len(filename) < 2: # just an empty uploads directory (e.g. uploads/)
continue continue
filename = filename[1] ## Get the second entry in the list (the actual filename) filename = filename[1] # Get the second entry in the list (the actual filename)
full_path = os.path.join(upload_folder, filename) full_path = os.path.join(upload_folder, filename)
dirname = os.path.dirname(full_path) dirname = os.path.dirname(full_path)
## Create any parent directories for the file # Create any parent directories for the file
if not os.path.exists(dirname): if not os.path.exists(dirname):
os.makedirs(dirname) os.makedirs(dirname)
source = backup.open(f) source = backup.open(f)
target = file(full_path, "wb") target = file(full_path, "wb")
with source, target: with source, target:
shutil.copyfileobj(source, target) shutil.copyfileobj(source, target)

View File

@@ -45,7 +45,7 @@ def setup():
# Index page # Index page
page = Pages('index', """<div class="container main-container"> page = Pages('index', """<div class="container main-container">
<img class="logo" src="{0}/static/original/img/logo.png" /> <img class="logo" src="static/original/img/logo.png" />
<h3 class="text-center"> <h3 class="text-center">
<p>A cool CTF platform from <a href="https://ctfd.io">ctfd.io</a></p> <p>A cool CTF platform from <a href="https://ctfd.io">ctfd.io</a></p>
<p>Follow us on social media:</p> <p>Follow us on social media:</p>
@@ -55,7 +55,7 @@ def setup():
</h3> </h3>
<br> <br>
<h4 class="text-center"> <h4 class="text-center">
<a href="{0}/admin">Click here</a> to login and setup your CTF <a href="admin">Click here</a> to login and setup your CTF
</h4> </h4>
</div>""".format(request.script_root)) </div>""".format(request.script_root))

View File

@@ -2,4 +2,5 @@
coverage>=4.1 coverage>=4.1
mock>=2.0.0 mock>=2.0.0
nose>=1.3.7 nose>=1.3.7
rednose>=1.1.1 rednose>=1.1.1
pep8==1.7.0

View File

@@ -106,6 +106,7 @@ def gen_solve(db, chalid, teamid, ip='127.0.0.1', flag='rightkey'):
db.session.commit() db.session.commit()
return solve return solve
def gen_wrongkey(db, teamid, chalid, flag='wrongkey'): def gen_wrongkey(db, teamid, chalid, flag='wrongkey'):
wrongkey = WrongKeys(teamid, chalid, flag) wrongkey = WrongKeys(teamid, chalid, flag)
db.session.add(wrongkey) db.session.add(wrongkey)
@@ -117,4 +118,4 @@ def gen_tracking(db, ip, team):
tracking = Tracking(ip, team) tracking = Tracking(ip, team)
db.session.add(tracking) db.session.add(tracking)
db.session.commit() db.session.commit()
return tracking return tracking

View File

@@ -73,4 +73,4 @@ def test_admin_config():
with app.app_context(): with app.app_context():
client = login_as_user(app, name="admin", password="password") client = login_as_user(app, name="admin", password="password")
r = client.get('/admin/config') r = client.get('/admin/config')
assert r.status_code == 200 assert r.status_code == 200

View File

@@ -22,7 +22,7 @@ def test_register_user():
def test_register_duplicate_teamname(): def test_register_duplicate_teamname():
"""A user shouldn't be able to use and already registered team name""" """A user shouldn't be able to use an already registered team name"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app, name="user1", email="user1@ctfd.io", password="password") register_user(app, name="user1", email="user1@ctfd.io", password="password")
@@ -74,7 +74,7 @@ def test_user_isnt_admin():
def test_user_get_teams(): def test_user_get_teams():
"""Can a registered user can load /teams""" """Can a registered user load /teams"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -84,7 +84,7 @@ def test_user_get_teams():
def test_user_get_scoreboard(): def test_user_get_scoreboard():
"""Can a registered user can load /scoreboard""" """Can a registered user load /scoreboard"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -94,7 +94,7 @@ def test_user_get_scoreboard():
def test_user_get_scores(): def test_user_get_scores():
"""Can a registered user can load /scores""" """Can a registered user load /scores"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -104,7 +104,7 @@ def test_user_get_scores():
def test_user_get_topteams(): def test_user_get_topteams():
"""Can a registered user can load /top/10""" """Can a registered user load /top/10"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -114,7 +114,7 @@ def test_user_get_topteams():
def test_user_get_challenges(): def test_user_get_challenges():
"""Can a registered user can load /challenges""" """Can a registered user load /challenges"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -124,7 +124,7 @@ def test_user_get_challenges():
def test_user_get_chals(): def test_user_get_chals():
"""Can a registered user can load /chals""" """Can a registered user load /chals"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -134,7 +134,7 @@ def test_user_get_chals():
def test_user_get_solves_per_chal(): def test_user_get_solves_per_chal():
"""Can a registered user can load /chals/solves""" """Can a registered user load /chals/solves"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -144,7 +144,7 @@ def test_user_get_solves_per_chal():
def test_user_get_solves(): def test_user_get_solves():
"""Can a registered user can load /solves""" """Can a registered user load /solves"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -154,7 +154,7 @@ def test_user_get_solves():
def test_user_get_team_page(): def test_user_get_team_page():
"""Can a registered user can load their public profile (/team/2)""" """Can a registered user load their public profile (/team/2)"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)
@@ -164,7 +164,7 @@ def test_user_get_team_page():
def test_user_get_profile(): def test_user_get_profile():
"""Can a registered user can load their private profile (/profile)""" """Can a registered user load their private profile (/profile)"""
app = create_ctfd() app = create_ctfd()
with app.app_context(): with app.app_context():
register_user(app) register_user(app)