Bugfixes and architectural changes

Moved some folders around, starting to remove subdomain handling,
blueprints, custom css, removed digital ocean interface, fixed some bugs
This commit is contained in:
CodeKevin
2015-09-13 23:55:15 -04:00
parent 5f6e610f2d
commit 7d766372df
53 changed files with 1191 additions and 1151 deletions

View File

@@ -3,22 +3,14 @@ from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.mail import Mail, Message from flask.ext.mail import Mail, Message
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from flask.ext.session import Session from flask.ext.session import Session
import logging
import os import os
import sqlalchemy import sqlalchemy
def create_app(subdomain="", username="", password=""):
app = Flask("CTFd", static_folder="../static", template_folder="../templates")
with app.app_context():
app.config.from_object('CTFd.config')
if subdomain: def create_app(config='CTFd.config'):
app.config.update( app = Flask("CTFd")
SQLALCHEMY_DATABASE_URI = 'mysql://'+username+':'+password+'@localhost:3306/' + subdomain + '_ctfd', with app.app_context():
HOST = subdomain + app.config["HOST"], app.config.from_object(config)
SESSION_FILE_DIR = app.config['SESSION_FILE_DIR'] + "/" + subdomain,
DEBUG = True
)
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking
@@ -26,52 +18,27 @@ def create_app(subdomain="", username="", password=""):
db.create_all() db.create_all()
app.db = db app.db = db
# app.setup = True
global mail global mail
mail = Mail(app) mail = Mail(app)
Session(app) Session(app)
from CTFd.views import init_views from CTFd.views import views
init_views(app) from CTFd.challenges import challenges
from CTFd.errors import init_errors from CTFd.scoreboard import scoreboard
init_errors(app) from CTFd.auth import auth
from CTFd.challenges import init_challenges from CTFd.admin import admin
init_challenges(app) from CTFd.utils import init_utils, init_errors, init_logs
from CTFd.scoreboard import init_scoreboard
init_scoreboard(app)
from CTFd.auth import init_auth
init_auth(app)
from CTFd.admin import init_admin
init_admin(app)
from CTFd.utils import init_utils
init_utils(app) init_utils(app)
init_errors(app)
init_logs(app)
app.register_blueprint(views)
app.register_blueprint(challenges)
app.register_blueprint(scoreboard)
app.register_blueprint(auth)
app.register_blueprint(admin)
return app return app
# logger_keys = logging.getLogger('keys')
# logger_logins = logging.getLogger('logins')
# logger_regs = logging.getLogger('regs')
# logger_keys.setLevel(logging.INFO)
# logger_logins.setLevel(logging.INFO)
# logger_regs.setLevel(logging.INFO)
# try:
# parent = os.path.dirname(__file__)
# except:
# parent = os.path.dirname(os.path.realpath(sys.argv[0]))
# key_log = RotatingFileHandler(os.path.join(parent, 'logs', 'keys.log'), maxBytes=10000)
# login_log = RotatingFileHandler(os.path.join(parent, 'logs', 'logins.log'), maxBytes=10000)
# register_log = RotatingFileHandler(os.path.join(parent, 'logs', 'registers.log'), maxBytes=10000)
# logger_keys.addHandler(key_log)
# logger_logins.addHandler(login_log)
# logger_regs.addHandler(register_log)
# logger_keys.propagate = 0
# logger_logins.propagate = 0
# logger_regs.propagate = 0

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
from flask import render_template, request, redirect, abort, jsonify, url_for, session from flask import render_template, request, redirect, abort, jsonify, url_for, session, Blueprint
from CTFd.utils import sha512, is_safe_url, authed, mailserver, sendmail, can_register from CTFd.utils import sha512, is_safe_url, authed, mailserver, sendmail, can_register
from CTFd.models import db, Teams from CTFd.models import db, Teams
@@ -11,131 +11,129 @@ import time
import re import re
import os import os
def init_auth(app): auth = Blueprint('auth', __name__)
@app.context_processor
def inject_user():
if authed():
return dict(session)
return dict()
@app.route('/reset_password', methods=['POST', 'GET'])
@app.route('/reset_password/<data>', methods=['POST', 'GET'])
def reset_password(data=None):
if data is not None and request.method == "GET":
return render_template('reset_password.html', mode='set')
if data is not None and request.method == "POST":
try:
s = TimedSerializer(app.config['SECRET_KEY'])
name = s.loads(data.decode('base64'), max_age=1800)
except BadTimeSignature:
return render_template('reset_password.html', errors=['Your link has expired'])
team = Teams.query.filter_by(name=name).first()
team.password = bcrypt_sha256.encrypt(request.form['password'].strip())
db.session.commit()
db.session.close()
return redirect('/login')
if request.method == 'POST': @auth.route('/reset_password', methods=['POST', 'GET'])
email = request.form['email'].strip() @auth.route('/reset_password/<data>', methods=['POST', 'GET'])
team = Teams.query.filter_by(email=email).first() def reset_password(data=None):
if not team: if data is not None and request.method == "GET":
return render_template('reset_password.html', errors=['Check your email']) return render_template('reset_password.html', mode='set')
if data is not None and request.method == "POST":
try:
s = TimedSerializer(app.config['SECRET_KEY']) s = TimedSerializer(app.config['SECRET_KEY'])
token = s.dumps(team.name) name = s.loads(data.decode('base64'), max_age=1800)
text = """ except BadTimeSignature:
return render_template('reset_password.html', errors=['Your link has expired'])
team = Teams.query.filter_by(name=name).first()
team.password = bcrypt_sha256.encrypt(request.form['password'].strip())
db.session.commit()
db.session.close()
return redirect('/login')
if request.method == 'POST':
email = request.form['email'].strip()
team = Teams.query.filter_by(email=email).first()
if not team:
return render_template('reset_password.html', errors=['Check your email'])
s = TimedSerializer(app.config['SECRET_KEY'])
token = s.dumps(team.name)
text = """
Did you initiate a password reset? Did you initiate a password reset?
{0}/reset_password/{1} {0}/reset_password/{1}
""".format(app.config['HOST'], token.encode('base64')) """.format(app.config['HOST'], token.encode('base64'))
sendmail(email, text) sendmail(email, text)
return render_template('reset_password.html', errors=['Check your email']) return render_template('reset_password.html', errors=['Check your email'])
return render_template('reset_password.html') return render_template('reset_password.html')
@app.route('/register', methods=['POST', 'GET'])
def register():
if not can_register():
return redirect('/login')
if request.method == 'POST':
errors = []
name = request.form['name']
email = request.form['email']
password = request.form['password']
name_len = len(name) == 0 @auth.route('/register', methods=['POST', 'GET'])
names = Teams.query.add_columns('name', 'id').filter_by(name=name).first() def register():
emails = Teams.query.add_columns('email', 'id').filter_by(email=email).first() if not can_register():
pass_short = len(password) == 0 return redirect('/login')
pass_long = len(password) > 128 if request.method == 'POST':
valid_email = re.match("[^@]+@[^@]+\.[^@]+", request.form['email']) errors = []
name = request.form['name']
email = request.form['email']
password = request.form['password']
if not valid_email: name_len = len(name) == 0
errors.append("That email doesn't look right") names = Teams.query.add_columns('name', 'id').filter_by(name=name).first()
if names: emails = Teams.query.add_columns('email', 'id').filter_by(email=email).first()
errors.append('That team name is already taken') pass_short = len(password) == 0
if emails: pass_long = len(password) > 128
errors.append('That email has already been used') valid_email = re.match("[^@]+@[^@]+\.[^@]+", request.form['email'])
if pass_short:
errors.append('Pick a longer password')
if pass_long:
errors.append('Pick a shorter password')
if name_len:
errors.append('Pick a longer team name')
if len(errors) > 0: if not valid_email:
return render_template('register.html', errors=errors, name=request.form['name'], email=request.form['email'], password=request.form['password']) errors.append("That email doesn't look right")
else: if names:
with app.app_context(): errors.append('That team name is already taken')
team = Teams(name, email, password) if emails:
db.session.add(team) errors.append('That email has already been used')
db.session.commit() if pass_short:
if mailserver(): errors.append('Pick a longer password')
sendmail(request.form['email'], "You've successfully registered for the CTF") if pass_long:
errors.append('Pick a shorter password')
if name_len:
errors.append('Pick a longer team name')
if len(errors) > 0:
return render_template('register.html', errors=errors, name=request.form['name'], email=request.form['email'], password=request.form['password'])
else:
with app.app_context():
team = Teams(name, email, password)
db.session.add(team)
db.session.commit()
if mailserver():
sendmail(request.form['email'], "You've successfully registered for the CTF")
db.session.close()
logger = logging.getLogger('regs')
logger.warn("[{0}] {1} registered with {2}".format(time.strftime("%m/%d/%Y %X"), request.form['name'].encode('utf-8'), request.form['email'].encode('utf-8')))
return redirect('/login')
else:
return render_template('register.html')
@auth.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
errors = []
name = request.form['name']
team = Teams.query.filter_by(name=name).first()
if team and bcrypt_sha256.verify(request.form['password'], team.password):
try:
session.regenerate() # NO SESSION FIXATION FOR YOU
except:
pass # TODO: Some session objects don't implement regenerate :(
session['username'] = team.name
session['id'] = team.id
session['admin'] = team.admin
session['nonce'] = sha512(os.urandom(10))
db.session.close() db.session.close()
logger = logging.getLogger('regs') logger = logging.getLogger('logins')
logger.warn("[{0}] {1} registered with {2}".format(time.strftime("%m/%d/%Y %X"), request.form['name'].encode('utf-8'), request.form['email'].encode('utf-8'))) logger.warn("[{0}] {1} logged in".format(time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8')))
return redirect('/login')
else: # if request.args.get('next') and is_safe_url(request.args.get('next')):
return render_template('register.html') # return redirect(request.args.get('next'))
return redirect('/team/{0}'.format(team.id))
@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
errors = []
name = request.form['name']
team = Teams.query.filter_by(name=name).first()
if team and bcrypt_sha256.verify(request.form['password'], team.password):
try:
session.regenerate() # NO SESSION FIXATION FOR YOU
except:
pass # TODO: Some session objects don't implement regenerate :(
session['username'] = team.name
session['id'] = team.id
session['admin'] = team.admin
session['nonce'] = sha512(os.urandom(10))
db.session.close()
logger = logging.getLogger('logins')
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')):
# return redirect(request.args.get('next'))
return redirect('/team/{0}'.format(team.id))
else:
errors.append("That account doesn't seem to exist")
db.session.close()
return render_template('login.html', errors=errors)
else: else:
errors.append("That account doesn't seem to exist")
db.session.close() db.session.close()
return render_template('login.html') return render_template('login.html', errors=errors)
else:
db.session.close()
return render_template('login.html')
@app.route('/logout') @auth.route('/logout')
def logout(): def logout():
if authed(): if authed():
session.clear() session.clear()
return redirect('/') return redirect('/')

View File

@@ -1,4 +1,4 @@
from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session, Blueprint
from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, can_view_challenges, is_admin, get_config from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, can_view_challenges, is_admin, get_config
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys
@@ -7,143 +7,152 @@ import time
import re import re
import logging import logging
def init_challenges(app): challenges = Blueprint('challenges', __name__)
@app.route('/challenges', methods=['GET'])
def challenges():
if not is_admin():
if not ctftime():
if view_after_ctf():
pass
else:
return redirect('/')
if can_view_challenges():
return render_template('chals.html', ctftime=ctftime())
else:
return redirect(url_for('login', next="challenges"))
@app.route('/chals', methods=['GET'])
def chals():
if not is_admin():
if not ctftime():
if view_after_ctf():
pass
else:
return redirect('/')
if can_view_challenges():
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
json = {'game':[]} @challenges.route('/challenges', methods=['GET'])
for x in chals: def challenges_view():
files = [ str(f.location) for f in Files.query.filter_by(chal=x.id).all() ] if not is_admin():
json['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5], 'files':files})
db.session.close()
return jsonify(json)
else:
db.session.close()
return redirect('/login')
@app.route('/chals/solves')
def chals_per_solves():
if can_view_challenges():
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
json = {}
for chal, count in solves:
json[chal.chal.name] = count
return jsonify(json)
return redirect(url_for('login', next="/chals/solves"))
@app.route('/solves')
@app.route('/solves/<teamid>')
def solves(teamid=None):
if teamid is None:
if authed():
solves = Solves.query.filter_by(teamid=session['id']).all()
else:
abort(401)
else:
solves = Solves.query.filter_by(teamid=teamid).all()
db.session.close()
json = {'solves':[]}
for x in solves:
json['solves'].append({ 'chal':x.chal.name, 'chalid':x.chalid,'team':x.teamid, 'value': x.chal.value, 'category':x.chal.category, 'time':unix_time(x.date)})
return jsonify(json)
@app.route('/maxattempts')
def attempts():
chals = Challenges.query.add_columns('id').all()
json = {'maxattempts':[]}
for chal, chalid in chals:
fails = WrongKeys.query.filter_by(team=session['id'], chal=chalid).count()
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
json['maxattempts'].append({'chalid':chalid})
return jsonify(json)
@app.route('/fails/<teamid>', methods=['GET'])
def fails(teamid):
fails = WrongKeys.query.filter_by(team=teamid).count()
solves = Solves.query.filter_by(teamid=teamid).count()
db.session.close()
json = {'fails':str(fails), 'solves': str(solves)}
return jsonify(json)
@app.route('/chal/<chalid>/solves', methods=['GET'])
def who_solved(chalid):
solves = Solves.query.filter_by(chalid=chalid)
json = {'teams':[]}
for solve in solves:
json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date})
return jsonify(json)
@app.route('/chal/<chalid>', methods=['POST'])
def chal(chalid):
if not ctftime(): if not ctftime():
return redirect('/challenges') if view_after_ctf():
if authed(): pass
fails = WrongKeys.query.filter_by(team=session['id'],chal=chalid).count()
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']))
print "[{0}] {1} submitted {2} with kpm {3}".format(*data)
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
return "4" #too many tries on this challenge
if get_kpm(session['id']) > 10:
wrong = WrongKeys(session['id'], chalid, request.form['key'])
db.session.add(wrong)
db.session.commit()
db.session.close()
logger.warn("[{0}] {1} submitted {2} with kpm {3} [TOO FAST]".format(*data))
return "3" # Submitting too fast
solves = Solves.query.filter_by(teamid=session['id'], chalid=chalid).first()
if not solves:
keys = Keys.query.filter_by(chal=chalid).all()
key = request.form['key'].strip().lower()
for x in keys:
if x.key_type == 0: #static key
if x.flag.strip().lower() == key:
solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key)
db.session.add(solve)
db.session.commit()
db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
return "1" # key was correct
elif x.key_type == 1: #regex
res = re.match(str(x), key, re.IGNORECASE)
if res and res.group() == key:
solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key)
db.session.add(solve)
db.session.commit()
db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
return "1" # key was correct
wrong = WrongKeys(session['id'], chalid, request.form['key'])
db.session.add(wrong)
db.session.commit()
db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data))
return '0' # key was wrong
else: else:
logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data)) return redirect('/')
return "2" # challenge was already solved if can_view_challenges():
return render_template('chals.html', ctftime=ctftime())
else:
return redirect(url_for('login', next="challenges"))
@challenges.route('/chals', methods=['GET'])
def chals():
if not is_admin():
if not ctftime():
if view_after_ctf():
pass
else:
return redirect('/')
if can_view_challenges():
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
json = {'game':[]}
for x in chals:
files = [ str(f.location) for f in Files.query.filter_by(chal=x.id).all() ]
json['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5], 'files':files})
db.session.close()
return jsonify(json)
else:
db.session.close()
return redirect('/login')
@challenges.route('/chals/solves')
def chals_per_solves():
if can_view_challenges():
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
json = {}
for chal, count in solves:
json[chal.chal.name] = count
return jsonify(json)
return redirect(url_for('login', next="/chals/solves"))
@challenges.route('/solves')
@challenges.route('/solves/<teamid>')
def solves(teamid=None):
if teamid is None:
if authed():
solves = Solves.query.filter_by(teamid=session['id']).all()
else: else:
return "-1" abort(401)
else:
solves = Solves.query.filter_by(teamid=teamid).all()
db.session.close()
json = {'solves':[]}
for x in solves:
json['solves'].append({ 'chal':x.chal.name, 'chalid':x.chalid,'team':x.teamid, 'value': x.chal.value, 'category':x.chal.category, 'time':unix_time(x.date)})
return jsonify(json)
@challenges.route('/maxattempts')
def attempts():
chals = Challenges.query.add_columns('id').all()
json = {'maxattempts':[]}
for chal, chalid in chals:
fails = WrongKeys.query.filter_by(team=session['id'], chal=chalid).count()
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
json['maxattempts'].append({'chalid':chalid})
return jsonify(json)
@challenges.route('/fails/<teamid>', methods=['GET'])
def fails(teamid):
fails = WrongKeys.query.filter_by(team=teamid).count()
solves = Solves.query.filter_by(teamid=teamid).count()
db.session.close()
json = {'fails':str(fails), 'solves': str(solves)}
return jsonify(json)
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
def who_solved(chalid):
solves = Solves.query.filter_by(chalid=chalid)
json = {'teams':[]}
for solve in solves:
json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date})
return jsonify(json)
@challenges.route('/chal/<chalid>', methods=['POST'])
def chal(chalid):
if not ctftime():
return redirect('/challenges')
if authed():
fails = WrongKeys.query.filter_by(team=session['id'],chal=chalid).count()
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']))
print "[{0}] {1} submitted {2} with kpm {3}".format(*data)
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
return "4" #too many tries on this challenge
if get_kpm(session['id']) > 10:
wrong = WrongKeys(session['id'], chalid, request.form['key'])
db.session.add(wrong)
db.session.commit()
db.session.close()
logger.warn("[{0}] {1} submitted {2} with kpm {3} [TOO FAST]".format(*data))
return "3" # Submitting too fast
solves = Solves.query.filter_by(teamid=session['id'], chalid=chalid).first()
if not solves:
keys = Keys.query.filter_by(chal=chalid).all()
key = request.form['key'].strip().lower()
for x in keys:
if x.key_type == 0: #static key
if x.flag.strip().lower() == key:
solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key)
db.session.add(solve)
db.session.commit()
db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
return "1" # key was correct
elif x.key_type == 1: #regex
res = re.match(str(x), key, re.IGNORECASE)
if res and res.group() == key:
solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key)
db.session.add(solve)
db.session.commit()
db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
return "1" # key was correct
wrong = WrongKeys(session['id'], chalid, request.form['key'])
db.session.add(wrong)
db.session.commit()
db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data))
return '0' # key was wrong
else:
logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data))
return "2" # challenge was already solved
else:
return "-1"

View File

@@ -1,18 +0,0 @@
from flask import current_app as app, render_template
def init_errors(app):
@app.errorhandler(404)
def page_not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(error):
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def general_error(error):
return render_template('errors/500.html'), 500
@app.errorhandler(502)
def gateway_error(error):
return render_template('errors/502.html'), 502

View File

@@ -1,4 +1,3 @@
#from CTFd import db
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
from socket import inet_aton, inet_ntoa from socket import inet_aton, inet_ntoa
@@ -79,7 +78,6 @@ class Keys(db.Model):
key_type = db.Column(db.Integer) key_type = db.Column(db.Integer)
flag = db.Column(db.Text) flag = db.Column(db.Text)
def __init__(self, chal, flag, key_type): def __init__(self, chal, flag, key_type):
self.chal = chal self.chal = chal
self.flag = flag self.flag = flag
@@ -181,6 +179,7 @@ class Tracking(db.Model):
def __repr__(self): def __repr__(self):
return '<ip %r>' % self.team return '<ip %r>' % self.team
class Config(db.Model): class Config(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.Text) key = db.Column(db.Text)

View File

@@ -1,46 +1,50 @@
from flask import current_app as app, session, render_template, jsonify from flask import current_app as app, session, render_template, jsonify, Blueprint
from CTFd.utils import unix_time from CTFd.utils import unix_time
from CTFd.models import db, Teams, Solves, Challenges from CTFd.models import db, Teams, Solves, Challenges
def init_scoreboard(app): scoreboard = Blueprint('scoreboard', __name__)
@app.route('/scoreboard')
def scoreboard():
score = db.func.sum(Challenges.value).label('score')
quickest = db.func.max(Solves.date).label('quickest')
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest)
db.session.close()
return render_template('scoreboard.html', teams=teams)
@app.route('/scores')
def scores():
score = db.func.sum(Challenges.value).label('score')
quickest = db.func.max(Solves.date).label('quickest')
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest)
db.session.close()
json = {'teams':[]}
for i, x in enumerate(teams):
json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)})
return jsonify(json)
@app.route('/top/<count>') @scoreboard.route('/scoreboard')
def topteams(count): def scoreboard_view():
try: score = db.func.sum(Challenges.value).label('score')
count = int(count) quickest = db.func.max(Solves.date).label('quickest')
except: teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest)
count = 10 db.session.close()
if count > 20 or count < 0: return render_template('scoreboard.html', teams=teams)
count = 10
json = {'scores':{}}
score = db.func.sum(Challenges.value).label('score') @scoreboard.route('/scores')
quickest = db.func.max(Solves.date).label('quickest') def scores():
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest).limit(count) score = db.func.sum(Challenges.value).label('score')
quickest = db.func.max(Solves.date).label('quickest')
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest)
db.session.close()
json = {'teams':[]}
for i, x in enumerate(teams):
json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)})
return jsonify(json)
for team in teams:
solves = Solves.query.filter_by(teamid=team.teamid).all()
json['scores'][team.name] = []
for x in solves:
json['scores'][team.name].append({'id':x.teamid, 'chal':x.chalid, 'team':x.teamid, 'value': x.chal.value, 'time':unix_time(x.date)})
return jsonify(json) @scoreboard.route('/top/<count>')
def topteams(count):
try:
count = int(count)
except:
count = 10
if count > 20 or count < 0:
count = 10
json = {'scores':{}}
score = db.func.sum(Challenges.value).label('score')
quickest = db.func.max(Solves.date).label('quickest')
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest).limit(count)
for team in teams:
solves = Solves.query.filter_by(teamid=team.teamid).all()
json['scores'][team.name] = []
for x in solves:
json['scores'][team.name].append({'id':x.teamid, 'chal':x.chalid, 'team':x.teamid, 'value': x.chal.value, 'time':unix_time(x.date)})
return jsonify(json)

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -9,7 +9,7 @@
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon"> <link rel="icon" href="/static/img/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/normalize.min.css" /> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/normalize.min.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/foundation.min.css" /> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/foundation.min.css" />
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" /> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="/static/admin/css/style.css"> <link rel="stylesheet" type="text/css" href="/static/admin/css/style.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
</head> </head>
@@ -32,22 +32,13 @@
<!-- Left Nav Section --> <!-- Left Nav Section -->
<ul class="left"> <ul class="left">
<li><a href="/admin/graphs">Graphs</a> <li><a href="/admin/graphs">Graphs</a></li>
</li> <li><a href="/admin/pages">Pages</a></li>
<li><a href="/admin/pages">Pages</a> <li><a href="/admin/teams">Teams</a></li>
</li> <li><a href="/admin/scoreboard">Scoreboard</a></li>
<li><a href="/admin/hosts">Hosts</a> <li><a href="/admin/chals">Challenges</a></li>
</li> <li><a href="/admin/statistics">Statistics</a></li>
<li><a href="/admin/teams">Teams</a> <li><a href="/admin/config">Config</a></li>
</li>
<li><a href="/admin/scoreboard">Scoreboard</a>
</li>
<li><a href="/admin/chals">Challenges</a>
</li>
<li><a href="/admin/statistics">Statistics</a>
</li>
<li><a href="/admin/config">Config</a>
</li>
</ul> </ul>
</section> </section>
</nav> </nav>

View File

@@ -22,11 +22,6 @@
<input id='mg_api_key' name='mg_api_key' type='text' placeholder="Mailgun API Key" {% if mg_api_key is defined and mg_api_key != None %}value="{{ mg_api_key }}"{% endif %}> <input id='mg_api_key' name='mg_api_key' type='text' placeholder="Mailgun API Key" {% if mg_api_key is defined and mg_api_key != None %}value="{{ mg_api_key }}"{% endif %}>
</div> </div>
<div class="row">
<label for="start">Digital Ocean API Key:</label>
<input id='do_api_key' name='do_api_key' type='text' placeholder="Digital Ocean API Key" {% if do_api_key is defined and do_api_key != None %}value="{{ do_api_key }}"{% endif %}>
</div>
<div class="row"> <div class="row">
<label for="start">Start Date:</label> <label for="start">Start Date:</label>
<input id='start' name='start' type='text' placeholder="Start Date (UTC timestamp)" {% if start is defined and start != None %}value="{{ start }}"{% endif %}> <input id='start' name='start' type='text' placeholder="Start Date (UTC timestamp)" {% if start is defined and start != None %}value="{{ start }}"{% endif %}>

View File

@@ -16,30 +16,38 @@
</form> </form>
<a class="close-reveal-modal">&#215;</a> <a class="close-reveal-modal">&#215;</a>
</div> </div>
<table id="pages">
<thead> <div class="small-6 columns">
<tr> <h3>CSS editor <a onclick="save_css()"><i class="fa fa-floppy-o"></i></a></h3>
<td><b>Route</b></td> <div style="height: 500px;" id="admin-css-editor" name="css">{{ css }}</div>
<td class="text-center" style="width: 150px;"><b>Settings</b></td> </div>
</tr>
</thead> <div class="small-6 columns">
<tbody> <h3>HTML Pages <a href="/admin/pages?mode=create"><i class="fa fa-plus"></i></a></h3>
{% for route in routes %} <table id="pages">
<tr name="{{ route.route }}"> <thead>
<td class="route-name"><a href="/admin/pages/{{ route.route }}">{{ route.route }}</a></td> <tr>
<td class="text-center"><i class="fa fa-times"></i></td> <td><b>Route</b></td>
</tr> <td class="text-center" style="width: 150px;"><b>Settings</b></td>
{% endfor %} </tr>
</tbody> </thead>
</table> <tbody>
<form method="POST"> {% for route in routes %}
<input name='nonce' type='hidden' value="{{ nonce }}"> <tr name="{{ route.route }}">
<button class="radius" type='submit'>New Page</button> <td class="route-name"><a href="/admin/pages/{{ route.route }}">{{ route.route }}</a></td>
</form> <td class="text-center"><i class="fa fa-times"></i></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ace.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/theme-github.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-css.js"></script>
<script> <script>
$('#delete-route').click(function(e){ $('#delete-route').click(function(e){
e.preventDefault(); e.preventDefault();
@@ -60,10 +68,23 @@ function load_confirm_modal(route){
$('#confirm').foundation('reveal', 'open'); $('#confirm').foundation('reveal', 'open');
} }
function save_css(){
var css = editor.getValue();
var nonce = $('#nonce').val();
$.post('/admin/css', {'css':css, 'nonce':nonce}, function(){
console.log('saved');
});
}
$('.fa-times').click(function(){ $('.fa-times').click(function(){
var elem = $(this).parent().parent(); var elem = $(this).parent().parent();
var name = elem.find('.route-name').text().trim(); var name = elem.find('.route-name').text().trim();
load_confirm_modal(name) load_confirm_modal(name)
}); });
var editor = ace.edit("admin-css-editor");
editor.setTheme("ace/theme/github");
editor.getSession().setMode("ace/mode/css");
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -11,6 +11,7 @@
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" /> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/railscasts.min.css"> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/railscasts.min.css">
<link rel="stylesheet" type="text/css" href="/static/css/style.css"> <link rel="stylesheet" type="text/css" href="/static/css/style.css">
<link rel="stylesheet" type="text/css" href="/static/user.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
</head> </head>
<body> <body>
@@ -40,29 +41,9 @@
{%else %} {%else %}
<li class="has-form"> <li class="has-form">
{% if can_register() %} {% if can_register() %}
<li class="has-dropdown"> <li><a href="/register">Register</a></li>
<a href="/register">Register</a>
<ul class="dropdown">
<form method="POST" action="/register">
<li><input type='text' name='name' placeholder='Name'></li>
<li><input type='text' name='email' placeholder='Email'></li>
<li><input type='password' name='password' placeholder='Password'></li>
<li><button type='submit'>Register</button></li>
</form>
</ul>
</li>
{% endif %} {% endif %}
<li class="has-dropdown"> <li><a href="/login">Login</a></li>
<a href="/login">Login</a>
<ul class="dropdown">
<form method="POST" action="/login">
<li><input type='text' name='name' placeholder='Name'></li>
<li><input type='password' name='password' placeholder='Password'></li>
<li><button type='submit'>Login</button></li>
<li><a href="/reset_password" class="text-center">Forgot?</a></li>
</form>
</ul>
</li>
{% endif %} {% endif %}
</li> </li>
</ul> </ul>

View File

@@ -19,6 +19,7 @@
<input class="radius" type='text' name='name' placeholder='Name'><br/> <input class="radius" type='text' name='name' placeholder='Name'><br/>
<input class="radius" type='password' name='password' placeholder='Password'><br/> <input class="radius" type='password' name='password' placeholder='Password'><br/>
<p><a href="/reset_password">Forgot your password?</a></p> <p><a href="/reset_password">Forgot your password?</a></p>
<input type="hidden" name="nonce" value="{{nonce}}">
<button class="radius" type='submit'>Login</button> <button class="radius" type='submit'>Login</button>
</form> </form>
</div> </div>

View File

@@ -37,6 +37,7 @@
<h4 class="text-center"><a href="/admin">Click here</a> to login and setup your CTF</h4> <h4 class="text-center"><a href="/admin">Click here</a> to login and setup your CTF</h4>
</div> </div>
</textarea><br> </textarea><br>
<input type="hidden" name="nonce" value="{{nonce}}"> {# This nonce is implemented specially in the route itself #}
<button class="radius" type='submit'>Login</button> <button class="radius" type='submit'>Login</button>
</form> </form>
</div> </div>

View File

@@ -3,7 +3,7 @@ from CTFd import mail
from urlparse import urlparse, urljoin from urlparse import urlparse, urljoin
from functools import wraps from functools import wraps
from flask import current_app as app, g, request, redirect, url_for, session from flask import current_app as app, g, request, redirect, url_for, session, render_template
from flask.ext.mail import Message from flask.ext.mail import Message
from socket import inet_aton, inet_ntoa from socket import inet_aton, inet_ntoa
from struct import unpack, pack from struct import unpack, pack
@@ -11,11 +11,71 @@ from struct import unpack, pack
import time import time
import datetime import datetime
import hashlib import hashlib
import digitalocean
import shutil import shutil
import requests import requests
import logging
import os
import sys
def init_logs(app):
logger_keys = logging.getLogger('keys')
logger_logins = logging.getLogger('logins')
logger_regs = logging.getLogger('regs')
logger_keys.setLevel(logging.INFO)
logger_logins.setLevel(logging.INFO)
logger_regs.setLevel(logging.INFO)
try:
parent = os.path.dirname(__file__)
except:
parent = os.path.dirname(os.path.realpath(sys.argv[0]))
log_dir = os.path.join(parent, 'logs')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
logs = [
os.path.join(parent, 'logs', 'keys.log'),
os.path.join(parent, 'logs', 'logins.log'),
os.path.join(parent, 'logs', 'registers.log')
]
for log in logs:
if not os.path.exists(log):
open(log, 'a').close()
key_log = logging.handlers.RotatingFileHandler(os.path.join(parent, 'logs', 'keys.log'), maxBytes=10000)
login_log = logging.handlers.RotatingFileHandler(os.path.join(parent, 'logs', 'logins.log'), maxBytes=10000)
register_log = logging.handlers.RotatingFileHandler(os.path.join(parent, 'logs', 'registers.log'), maxBytes=10000)
logger_keys.addHandler(key_log)
logger_logins.addHandler(login_log)
logger_regs.addHandler(register_log)
logger_keys.propagate = 0
logger_logins.propagate = 0
logger_regs.propagate = 0
def init_errors(app):
@app.errorhandler(404)
def page_not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(error):
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def general_error(error):
return render_template('errors/500.html'), 500
@app.errorhandler(502)
def gateway_error(error):
return render_template('errors/502.html'), 502
def init_utils(app): def init_utils(app):
app.jinja_env.filters['unix_time'] = unix_time app.jinja_env.filters['unix_time'] = unix_time
app.jinja_env.filters['unix_time_millis'] = unix_time_millis app.jinja_env.filters['unix_time_millis'] = unix_time_millis
@@ -25,6 +85,19 @@ def init_utils(app):
app.jinja_env.globals.update(mailserver=mailserver) app.jinja_env.globals.update(mailserver=mailserver)
app.jinja_env.globals.update(ctf_name=ctf_name) app.jinja_env.globals.update(ctf_name=ctf_name)
@app.context_processor
def inject_user():
if authed():
return dict(session)
return dict()
@app.before_request
def needs_setup():
if request.path == '/setup' or request.path.startswith('/static'):
return
if not is_setup():
return redirect('/setup')
def ctf_name(): def ctf_name():
name = get_config('ctf_name') name = get_config('ctf_name')
@@ -155,8 +228,8 @@ def set_config(key, value):
else: else:
config = Config(key, value) config = Config(key, value)
db.session.add(config) db.session.add(config)
db.session.commit() db.session.commit()
return config return config
def mailserver(): def mailserver():
@@ -205,10 +278,3 @@ def validate_url(url):
def sha512(string): def sha512(string):
return hashlib.sha512(string).hexdigest() return hashlib.sha512(string).hexdigest()
def get_digitalocean():
token = get_config('do_api_key')
if token:
return digitalocean.Manager(token=token)
else:
return False

View File

@@ -1,5 +1,5 @@
from flask import current_app as app, render_template, render_template_string, request, redirect, abort, jsonify, json as json_mod, url_for, session from flask import current_app as app, render_template, render_template_string, request, redirect, abort, jsonify, json as json_mod, url_for, session, Blueprint, Response
from CTFd.utils import authed, ip2long, long2ip, is_setup, validate_url, get_config from CTFd.utils import authed, ip2long, long2ip, is_setup, validate_url, get_config, sha512
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
@@ -13,183 +13,196 @@ import sys
import json import json
import os import os
def init_views(app): views = Blueprint('views', __name__)
@app.before_request
def tracker():
if authed(): @views.before_request
if not Tracking.query.filter_by(ip=ip2long(request.remote_addr)).first(): def tracker():
visit = Tracking(request.remote_addr, session['id']) if authed():
db.session.add(visit) if not Tracking.query.filter_by(ip=ip2long(request.remote_addr)).first():
visit = Tracking(request.remote_addr, session['id'])
db.session.add(visit)
db.session.commit()
db.session.close()
@views.before_request
def csrf():
if request.method == "POST":
if session['nonce'] != request.form.get('nonce'):
abort(403)
@views.before_request
def redirect_setup():
if request.path == "/static/css/style.css":
return
if not is_setup() and request.path != "/setup":
return redirect('/setup')
@views.route('/setup', methods=['GET', 'POST'])
def setup():
# with app.app_context():
# admin = Teams.query.filter_by(admin=True).first()
if not is_setup():
if request.method == 'POST':
ctf_name = request.form['ctf_name']
ctf_name = Config('ctf_name', ctf_name)
## CSS
css = Config('start', '')
## Admin user
name = request.form['name']
email = request.form['email']
password = request.form['password']
admin = Teams(name, email, password)
admin.admin = True
## Index page
html = request.form['html']
page = Pages('index', html)
#max attempts per challenge
max_tries = Config("max_tries",0)
## Start time
start = Config('start', None)
end = Config('end', None)
## Challenges cannot be viewed by unregistered users
view_challenges_unregistered = Config('view_challenges_unregistered', None)
## Allow/Disallow registration
prevent_registration = Config('prevent_registration', None)
setup = Config('setup', True)
db.session.add(ctf_name)
db.session.add(admin)
db.session.add(page)
db.session.add(max_tries)
db.session.add(start)
db.session.add(end)
db.session.add(view_challenges_unregistered)
db.session.add(prevent_registration)
db.session.add(css)
db.session.add(setup)
db.session.commit()
app.setup = False
return redirect('/')
return render_template('setup.html', nonce=session.get('nonce'))
return redirect('/')
# Custom CSS handler
@views.route('/static/user.css')
def custom_css():
return Response(get_config("css"), mimetype='text/css')
# Static HTML files
@views.route("/", defaults={'template': 'index'})
@views.route("/<template>")
def static_html(template):
try:
return render_template('%s.html' % template)
except TemplateNotFound:
page = Pages.query.filter_by(route=template).first()
if page:
return render_template_string('{% extends "base.html" %}{% block content %}' + page.html + '{% endblock %}')
else:
abort(404)
@views.route('/teams')
def teams():
teams = Teams.query.all()
return render_template('teams.html', teams=teams)
@views.route('/team/<teamid>', methods=['GET', 'POST'])
def team(teamid):
user = Teams.query.filter_by(id=teamid).first()
solves = Solves.query.filter_by(teamid=teamid).all()
score = user.score()
place = user.place()
db.session.close()
if request.method == 'GET':
return render_template('team.html', solves=solves, team=user, score=score, place=place)
elif request.method == 'POST':
json = {'solves':[]}
for x in solves:
json['solves'].append({'id':x.id, 'chal':x.chalid, 'team':x.teamid})
return jsonify(json)
@views.route('/profile', methods=['POST', 'GET'])
def profile():
if authed():
if request.method == "POST":
errors = []
name = request.form.get('name')
email = request.form.get('email')
website = request.form.get('website')
affiliation = request.form.get('affiliation')
country = request.form.get('country')
user = Teams.query.filter_by(id=session['id']).first()
if not get_config('prevent_name_change'):
names = Teams.query.filter_by(name=name).first()
name_len = len(request.form['name']) == 0
emails = Teams.query.filter_by(email=email).first()
valid_email = re.match("[^@]+@[^@]+\.[^@]+", email)
if ('password' in request.form.keys() and not len(request.form['password']) == 0) and \
(not bcrypt_sha256.verify(request.form.get('confirm').strip(), user.password)):
errors.append("Your old password doesn't match what we have.")
if not valid_email:
errors.append("That email doesn't look right")
if not get_config('prevent_name_change') and names and name!=session['username']:
errors.append('That team name is already taken')
if emails and emails.id != session['id']:
errors.append('That email has already been used')
if not get_config('prevent_name_change') and name_len:
errors.append('Pick a longer team name')
if website.strip() and not validate_url(website):
errors.append("That doesn't look like a valid URL")
if len(errors) > 0:
return render_template('profile.html', name=name, email=email, website=website,
affiliation=affiliation, country=country, errors=errors)
else:
team = Teams.query.filter_by(id=session['id']).first()
if not get_config('prevent_name_change'):
team.name = name
team.email = email
session['username'] = team.name
if 'password' in request.form.keys() and not len(request.form['password']) == 0:
team.password = bcrypt_sha256.encrypt(request.form.get('password'))
team.website = website
team.affiliation = affiliation
team.country = country
db.session.commit() db.session.commit()
db.session.close() db.session.close()
return redirect('/profile')
@app.before_request
def csrf():
if authed() and request.method == "POST":
if session['nonce'] != request.form.get('nonce'):
abort(403)
@app.before_request
def redirect_setup():
if request.path == "/static/css/style.css":
return
if not is_setup() and request.path != "/setup":
return redirect('/setup')
@app.before_first_request
def needs_setup():
if not is_setup():
return redirect('/setup')
@app.route('/setup', methods=['GET', 'POST'])
def setup():
# with app.app_context():
# admin = Teams.query.filter_by(admin=True).first()
if not is_setup():
if request.method == 'POST':
ctf_name = request.form['ctf_name']
ctf_name = Config('ctf_name', ctf_name)
## Admin user
name = request.form['name']
email = request.form['email']
password = request.form['password']
admin = Teams(name, email, password)
admin.admin = True
## Index page
html = request.form['html']
page = Pages('index', html)
#max attempts per challenge
max_tries = Config("max_tries",0)
## Start time
start = Config('start', None)
end = Config('end', None)
## Challenges cannot be viewed by unregistered users
view_challenges_unregistered = Config('view_challenges_unregistered', None)
## Allow/Disallow registration
prevent_registration = Config('prevent_registration', None)
setup = Config('setup', True)
db.session.add(ctf_name)
db.session.add(admin)
db.session.add(page)
db.session.add(max_tries)
db.session.add(start)
db.session.add(end)
db.session.add(view_challenges_unregistered)
db.session.add(prevent_registration)
db.session.add(setup)
db.session.commit()
app.setup = False
return redirect('/')
return render_template('setup.html')
return redirect('/')
# Static HTML files
@app.route("/", defaults={'template': 'index'})
@app.route("/<template>")
def static_html(template):
try:
return render_template('%s.html' % template)
except TemplateNotFound:
page = Pages.query.filter_by(route=template).first()
if page:
return render_template_string('{% extends "base.html" %}{% block content %}' + page.html + '{% endblock %}')
else:
abort(404)
@app.route('/teams')
def teams():
teams = Teams.query.all()
return render_template('teams.html', teams=teams)
@app.route('/team/<teamid>', methods=['GET', 'POST'])
def team(teamid):
user = Teams.query.filter_by(id=teamid).first()
solves = Solves.query.filter_by(teamid=teamid).all()
score = user.score()
place = user.place()
db.session.close()
if request.method == 'GET':
return render_template('team.html', solves=solves, team=user, score=score, place=place)
elif request.method == 'POST':
json = {'solves':[]}
for x in solves:
json['solves'].append({'id':x.id, 'chal':x.chalid, 'team':x.teamid})
return jsonify(json)
@app.route('/profile', methods=['POST', 'GET'])
def profile():
if authed():
if request.method == "POST":
errors = []
name = request.form.get('name')
email = request.form.get('email')
website = request.form.get('website')
affiliation = request.form.get('affiliation')
country = request.form.get('country')
user = Teams.query.filter_by(id=session['id']).first()
if not get_config('prevent_name_change'):
names = Teams.query.filter_by(name=name).first()
name_len = len(request.form['name']) == 0
emails = Teams.query.filter_by(email=email).first()
valid_email = re.match("[^@]+@[^@]+\.[^@]+", email)
if ('password' in request.form.keys() and not len(request.form['password']) == 0) and \
(not bcrypt_sha256.verify(request.form.get('confirm').strip(), user.password)):
errors.append("Your old password doesn't match what we have.")
if not valid_email:
errors.append("That email doesn't look right")
if not get_config('prevent_name_change') and names and name!=session['username']:
errors.append('That team name is already taken')
if emails and emails.id != session['id']:
errors.append('That email has already been used')
if not get_config('prevent_name_change') and name_len:
errors.append('Pick a longer team name')
if website.strip() and not validate_url(website):
errors.append("That doesn't look like a valid URL")
if len(errors) > 0:
return render_template('profile.html', name=name, email=email, website=website,
affiliation=affiliation, country=country, errors=errors)
else:
team = Teams.query.filter_by(id=session['id']).first()
if not get_config('prevent_name_change'):
team.name = name
team.email = email
session['username'] = team.name
if 'password' in request.form.keys() and not len(request.form['password']) == 0:
team.password = bcrypt_sha256.encrypt(request.form.get('password'))
team.website = website
team.affiliation = affiliation
team.country = country
db.session.commit()
db.session.close()
return redirect('/profile')
else:
user = Teams.query.filter_by(id=session['id']).first()
name = user.name
email = user.email
website = user.website
affiliation = user.affiliation
country = user.country
prevent_name_change = get_config('prevent_name_change')
return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation,
country=country, prevent_name_change=prevent_name_change)
else: else:
return redirect('/login') user = Teams.query.filter_by(id=session['id']).first()
name = user.name
email = user.email
website = user.website
affiliation = user.affiliation
country = user.country
prevent_name_change = get_config('prevent_name_change')
return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation,
country=country, prevent_name_change=prevent_name_change)
else:
return redirect('/login')

View File

@@ -8,5 +8,4 @@ passlib==1.6.2
bcrypt bcrypt
six==1.8.0 six==1.8.0
itsdangerous==0.24 itsdangerous==0.24
python-digitalocean==1.4.2
requests==2.3.0 requests==2.3.0