Bugfixes and architectural changes
Moved some folders around, starting to remove subdomain handling, blueprints, custom css, removed digital ocean interface, fixed some bugs
@@ -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")
|
def create_app(config='CTFd.config'):
|
||||||
|
app = Flask("CTFd")
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
app.config.from_object('CTFd.config')
|
app.config.from_object(config)
|
||||||
|
|
||||||
if subdomain:
|
|
||||||
app.config.update(
|
|
||||||
SQLALCHEMY_DATABASE_URI = 'mysql://'+username+':'+password+'@localhost:3306/' + subdomain + '_ctfd',
|
|
||||||
HOST = subdomain + app.config["HOST"],
|
|
||||||
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
|
|
||||||
|
|||||||
1095
CTFd/admin.py
218
CTFd/auth.py
@@ -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('/')
|
||||||
|
|||||||
@@ -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':[]}
|
|
||||||
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()
|
@challenges.route('/challenges', methods=['GET'])
|
||||||
return jsonify(json)
|
def challenges_view():
|
||||||
else:
|
if not is_admin():
|
||||||
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"
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -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>
|
||||||
@@ -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 %}>
|
||||||
@@ -16,30 +16,38 @@
|
|||||||
</form>
|
</form>
|
||||||
<a class="close-reveal-modal">×</a>
|
<a class="close-reveal-modal">×</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 %}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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
|
|
||||||
|
|||||||
371
CTFd/views.py
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||