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 logging.handlers import RotatingFileHandler
|
||||
from flask.ext.session import Session
|
||||
import logging
|
||||
import os
|
||||
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:
|
||||
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
|
||||
)
|
||||
def create_app(config='CTFd.config'):
|
||||
app = Flask("CTFd")
|
||||
with app.app_context():
|
||||
app.config.from_object(config)
|
||||
|
||||
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()
|
||||
|
||||
app.db = db
|
||||
# app.setup = True
|
||||
|
||||
global mail
|
||||
mail = Mail(app)
|
||||
|
||||
Session(app)
|
||||
|
||||
from CTFd.views import init_views
|
||||
init_views(app)
|
||||
from CTFd.errors import init_errors
|
||||
init_errors(app)
|
||||
from CTFd.challenges import init_challenges
|
||||
init_challenges(app)
|
||||
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
|
||||
from CTFd.views import views
|
||||
from CTFd.challenges import challenges
|
||||
from CTFd.scoreboard import scoreboard
|
||||
from CTFd.auth import auth
|
||||
from CTFd.admin import admin
|
||||
from CTFd.utils import init_utils, init_errors, init_logs
|
||||
|
||||
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
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
143
CTFd/admin.py
@@ -1,5 +1,5 @@
|
||||
from flask import render_template, request, redirect, abort, jsonify, url_for, session
|
||||
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, get_digitalocean, sendmail, rmdir
|
||||
from flask import render_template, request, redirect, abort, jsonify, url_for, session, Blueprint
|
||||
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, sendmail, rmdir
|
||||
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
||||
from itsdangerous import TimedSerializer, BadTimeSignature
|
||||
from werkzeug.utils import secure_filename
|
||||
@@ -14,15 +14,17 @@ import re
|
||||
import os
|
||||
import json
|
||||
|
||||
def init_admin(app):
|
||||
@app.route('/admin', methods=['GET', 'POST'])
|
||||
def admin():
|
||||
admin = Blueprint('admin', __name__)
|
||||
|
||||
|
||||
@admin.route('/admin', methods=['GET', 'POST'])
|
||||
def admin_view():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('name')
|
||||
password = request.form.get('password')
|
||||
|
||||
admin = Teams.query.filter_by(name=request.form['name'], admin=True).first()
|
||||
if admin and bcrypt_sha256.verify(request.form['password'], admin.password):
|
||||
admin_user= Teams.query.filter_by(name=request.form['name'], admin=True).first()
|
||||
if admin_user and bcrypt_sha256.verify(request.form['password'], admin.password):
|
||||
try:
|
||||
session.regenerate() # NO SESSION FIXATION FOR YOU
|
||||
except:
|
||||
@@ -39,12 +41,14 @@ def init_admin(app):
|
||||
|
||||
return render_template('admin/login.html')
|
||||
|
||||
@app.route('/admin/graphs')
|
||||
|
||||
@admin.route('/admin/graphs')
|
||||
@admins_only
|
||||
def admin_graphs():
|
||||
return render_template('admin/graphs.html')
|
||||
|
||||
@app.route('/admin/config', methods=['GET', 'POST'])
|
||||
|
||||
@admin.route('/admin/config', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_config():
|
||||
if request.method == "POST":
|
||||
@@ -73,7 +77,6 @@ def init_admin(app):
|
||||
|
||||
ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
|
||||
mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
|
||||
do_api_key = set_config("do_api_key", request.form.get('do_api_key', None))
|
||||
max_tries = set_config("max_tries", request.form.get('max_tries', None))
|
||||
|
||||
|
||||
@@ -91,15 +94,11 @@ def init_admin(app):
|
||||
|
||||
ctf_name = get_config('ctf_name')
|
||||
if not ctf_name:
|
||||
set_config('do_api_key', None)
|
||||
set_config('ctf_name', None)
|
||||
|
||||
mg_api_key = get_config('do_api_key')
|
||||
mg_api_key = get_config('mg_api_key')
|
||||
if not mg_api_key:
|
||||
set_config('do_api_key', None)
|
||||
|
||||
do_api_key = get_config('do_api_key')
|
||||
if not do_api_key:
|
||||
set_config('do_api_key', None)
|
||||
set_config('mg_api_key', None)
|
||||
|
||||
max_tries = get_config('max_tries')
|
||||
if not max_tries:
|
||||
@@ -137,14 +136,27 @@ def init_admin(app):
|
||||
return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end,
|
||||
max_tries=max_tries,
|
||||
view_challenges_unregistered=view_challenges_unregistered,
|
||||
prevent_registration=prevent_registration, do_api_key=do_api_key, mg_api_key=mg_api_key,
|
||||
prevent_registration=prevent_registration, mg_api_key=mg_api_key,
|
||||
prevent_name_change=prevent_name_change,
|
||||
view_after_ctf=view_after_ctf)
|
||||
|
||||
@app.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST'])
|
||||
@app.route('/admin/pages/<route>', methods=['GET', 'POST'])
|
||||
|
||||
@admin.route('/admin/css', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_css():
|
||||
if request.method == 'POST':
|
||||
css = request.form['css']
|
||||
css = set_config('css', css)
|
||||
print css
|
||||
return "1"
|
||||
return "0"
|
||||
|
||||
@admin.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST'])
|
||||
@admin.route('/admin/pages/<route>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_pages(route):
|
||||
if request.method == 'GET' and request.args.get('mode') == 'create':
|
||||
return render_template('admin/editor.html')
|
||||
if route and request.method == 'GET':
|
||||
page = Pages.query.filter_by(route=route).first()
|
||||
return render_template('admin/editor.html', page=page)
|
||||
@@ -167,12 +179,11 @@ def init_admin(app):
|
||||
db.session.add(page)
|
||||
db.session.commit()
|
||||
return redirect('/admin/pages')
|
||||
if not route and request.method == 'POST':
|
||||
return render_template('admin/editor.html')
|
||||
pages = Pages.query.all()
|
||||
return render_template('admin/pages.html', routes=pages)
|
||||
return render_template('admin/pages.html', routes=pages, css=get_config('css'))
|
||||
|
||||
@app.route('/admin/page/<pageroute>/delete', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/page/<pageroute>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_page(pageroute):
|
||||
page = Pages.query.filter_by(route=pageroute).first()
|
||||
@@ -180,23 +191,8 @@ def init_admin(app):
|
||||
db.session.commit()
|
||||
return '1'
|
||||
|
||||
@app.route('/admin/hosts', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_hosts():
|
||||
m = get_digitalocean()
|
||||
errors = []
|
||||
if not m:
|
||||
errors.append("Your Digital Ocean API key is not set")
|
||||
return render_template('admin/hosts.html', errors=errors)
|
||||
|
||||
hosts = m.get_all_droplets()
|
||||
slugs = m.get_all_sizes()
|
||||
images = m.get_all_images()
|
||||
regions = m.get_all_regions()
|
||||
|
||||
return render_template('admin/hosts.html', hosts=hosts, slugs=slugs, images=images, regions=regions)
|
||||
|
||||
@app.route('/admin/chals', methods=['POST', 'GET'])
|
||||
@admin.route('/admin/chals', methods=['POST', 'GET'])
|
||||
@admins_only
|
||||
def admin_chals():
|
||||
if request.method == 'POST':
|
||||
@@ -211,7 +207,8 @@ def init_admin(app):
|
||||
else:
|
||||
return render_template('admin/chals.html')
|
||||
|
||||
@app.route('/admin/keys/<chalid>', methods=['POST', 'GET'])
|
||||
|
||||
@admin.route('/admin/keys/<chalid>', methods=['POST', 'GET'])
|
||||
@admins_only
|
||||
def admin_keys(chalid):
|
||||
if request.method == 'GET':
|
||||
@@ -235,7 +232,8 @@ def init_admin(app):
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
@app.route('/admin/tags/<chalid>', methods=['GET', 'POST'])
|
||||
|
||||
@admin.route('/admin/tags/<chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_tags(chalid):
|
||||
if request.method == 'GET':
|
||||
@@ -254,7 +252,8 @@ def init_admin(app):
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
@app.route('/admin/tags/<tagid>/delete', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/tags/<tagid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def admin_delete_tags(tagid):
|
||||
if request.method == 'POST':
|
||||
@@ -265,7 +264,7 @@ def init_admin(app):
|
||||
return "1"
|
||||
|
||||
|
||||
@app.route('/admin/files/<chalid>', methods=['GET', 'POST'])
|
||||
@admin.route('/admin/files/<chalid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_files(chalid):
|
||||
if request.method == 'GET':
|
||||
@@ -310,13 +309,15 @@ def init_admin(app):
|
||||
db.session.close()
|
||||
return redirect('/admin/chals')
|
||||
|
||||
@app.route('/admin/teams')
|
||||
|
||||
@admin.route('/admin/teams')
|
||||
@admins_only
|
||||
def admin_teams():
|
||||
teams = Teams.query.all()
|
||||
return render_template('admin/teams.html', teams=teams)
|
||||
|
||||
@app.route('/admin/team/<teamid>', methods=['GET', 'POST'])
|
||||
|
||||
@admin.route('/admin/team/<teamid>', methods=['GET', 'POST'])
|
||||
@admins_only
|
||||
def admin_team(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
@@ -328,8 +329,8 @@ def init_admin(app):
|
||||
if request.method == 'GET':
|
||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place)
|
||||
elif request.method == 'POST':
|
||||
admin = request.form.get('admin', "false")
|
||||
admin = 1 if admin == "true" else 0
|
||||
admin_user = request.form.get('admin', "false")
|
||||
admin_user = 1 if admin_user == "true" else 0
|
||||
if admin:
|
||||
user.admin = 1
|
||||
db.session.commit()
|
||||
@@ -367,7 +368,8 @@ def init_admin(app):
|
||||
db.session.close()
|
||||
return jsonify({'data':['success']})
|
||||
|
||||
@app.route('/admin/team/<teamid>/mail', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/team/<teamid>/mail', methods=['POST'])
|
||||
@admins_only
|
||||
def email_user(teamid):
|
||||
message = request.form.get('msg', None)
|
||||
@@ -377,7 +379,8 @@ def init_admin(app):
|
||||
return "1"
|
||||
return "0"
|
||||
|
||||
@app.route('/admin/team/<teamid>/ban', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/team/<teamid>/ban', methods=['POST'])
|
||||
@admins_only
|
||||
def ban(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
@@ -385,7 +388,8 @@ def init_admin(app):
|
||||
db.session.commit()
|
||||
return redirect('/admin/scoreboard')
|
||||
|
||||
@app.route('/admin/team/<teamid>/unban', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/team/<teamid>/unban', methods=['POST'])
|
||||
@admins_only
|
||||
def unban(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
@@ -393,7 +397,8 @@ def init_admin(app):
|
||||
db.session.commit()
|
||||
return redirect('/admin/scoreboard')
|
||||
|
||||
@app.route('/admin/team/<teamid>/delete', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/team/<teamid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_team(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
@@ -402,7 +407,7 @@ def init_admin(app):
|
||||
return '1'
|
||||
|
||||
|
||||
@app.route('/admin/graphs/<graph_type>')
|
||||
@admin.route('/admin/graphs/<graph_type>')
|
||||
@admins_only
|
||||
def admin_graph(graph_type):
|
||||
if graph_type == 'categories':
|
||||
@@ -418,7 +423,8 @@ def init_admin(app):
|
||||
json[chal.chal.name] = count
|
||||
return jsonify(json)
|
||||
|
||||
@app.route('/admin/scoreboard')
|
||||
|
||||
@admin.route('/admin/scoreboard')
|
||||
@admins_only
|
||||
def admin_scoreboard():
|
||||
score = db.func.sum(Challenges.value).label('score')
|
||||
@@ -427,7 +433,8 @@ def init_admin(app):
|
||||
db.session.close()
|
||||
return render_template('admin/scoreboard.html', teams=teams)
|
||||
|
||||
@app.route('/admin/scores')
|
||||
|
||||
@admin.route('/admin/scores')
|
||||
@admins_only
|
||||
def admin_scores():
|
||||
score = db.func.sum(Challenges.value).label('score')
|
||||
@@ -439,7 +446,8 @@ def init_admin(app):
|
||||
json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)})
|
||||
return jsonify(json)
|
||||
|
||||
@app.route('/admin/solves/<teamid>', methods=['GET'])
|
||||
|
||||
@admin.route('/admin/solves/<teamid>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_solves(teamid="all"):
|
||||
if teamid == "all":
|
||||
@@ -453,7 +461,7 @@ def init_admin(app):
|
||||
return jsonify(json)
|
||||
|
||||
|
||||
@app.route('/admin/solves/<teamid>/<chalid>/delete', methods=['POST'])
|
||||
@admin.route('/admin/solves/<teamid>/<chalid>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_solve(teamid, chalid):
|
||||
solve = Solves.query.filter_by(teamid=teamid, chalid=chalid).first()
|
||||
@@ -461,7 +469,8 @@ def init_admin(app):
|
||||
db.session.commit()
|
||||
return '1'
|
||||
|
||||
@app.route('/admin/statistics', methods=['GET'])
|
||||
|
||||
@admin.route('/admin/statistics', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_stats():
|
||||
db.session.commit()
|
||||
@@ -483,7 +492,8 @@ def init_admin(app):
|
||||
least_solved=least_solved_chal
|
||||
)
|
||||
|
||||
@app.route('/admin/wrong_keys/<page>', methods=['GET'])
|
||||
|
||||
@admin.route('/admin/wrong_keys/<page>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_wrong_key(page='1'):
|
||||
page = abs(int(page))
|
||||
@@ -500,7 +510,8 @@ def init_admin(app):
|
||||
|
||||
return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages)
|
||||
|
||||
@app.route('/admin/correct_keys/<page>', methods=['GET'])
|
||||
|
||||
@admin.route('/admin/correct_keys/<page>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_correct_key(page='1'):
|
||||
page = abs(int(page))
|
||||
@@ -517,7 +528,8 @@ def init_admin(app):
|
||||
|
||||
return render_template('admin/correct_keys.html', solves=solves, pages=pages)
|
||||
|
||||
@app.route('/admin/fails/<teamid>', methods=['GET'])
|
||||
|
||||
@admin.route('/admin/fails/<teamid>', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_fails(teamid='all'):
|
||||
if teamid == "all":
|
||||
@@ -534,8 +546,7 @@ def init_admin(app):
|
||||
return jsonify(json)
|
||||
|
||||
|
||||
|
||||
@app.route('/admin/chal/new', methods=['POST'])
|
||||
@admin.route('/admin/chal/new', methods=['POST'])
|
||||
def admin_create_chal():
|
||||
files = request.files.getlist('files[]')
|
||||
|
||||
@@ -568,7 +579,8 @@ def init_admin(app):
|
||||
db.session.close()
|
||||
return redirect('/admin/chals')
|
||||
|
||||
@app.route('/admin/chal/delete', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/chal/delete', methods=['POST'])
|
||||
def admin_delete_chal():
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||
if challenge:
|
||||
@@ -586,7 +598,8 @@ def init_admin(app):
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
@app.route('/admin/chal/update', methods=['POST'])
|
||||
|
||||
@admin.route('/admin/chal/update', methods=['POST'])
|
||||
def admin_update_chal():
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||
challenge.name = request.form['name']
|
||||
|
||||
22
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.models import db, Teams
|
||||
|
||||
@@ -11,15 +11,11 @@ import time
|
||||
import re
|
||||
import os
|
||||
|
||||
def init_auth(app):
|
||||
@app.context_processor
|
||||
def inject_user():
|
||||
if authed():
|
||||
return dict(session)
|
||||
return dict()
|
||||
auth = Blueprint('auth', __name__)
|
||||
|
||||
@app.route('/reset_password', methods=['POST', 'GET'])
|
||||
@app.route('/reset_password/<data>', methods=['POST', 'GET'])
|
||||
|
||||
@auth.route('/reset_password', methods=['POST', 'GET'])
|
||||
@auth.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')
|
||||
@@ -54,7 +50,8 @@ Did you initiate a password reset?
|
||||
return render_template('reset_password.html', errors=['Check your email'])
|
||||
return render_template('reset_password.html')
|
||||
|
||||
@app.route('/register', methods=['POST', 'GET'])
|
||||
|
||||
@auth.route('/register', methods=['POST', 'GET'])
|
||||
def register():
|
||||
if not can_register():
|
||||
return redirect('/login')
|
||||
@@ -102,7 +99,8 @@ Did you initiate a password reset?
|
||||
else:
|
||||
return render_template('register.html')
|
||||
|
||||
@app.route('/login', methods=['POST', 'GET'])
|
||||
|
||||
@auth.route('/login', methods=['POST', 'GET'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
errors = []
|
||||
@@ -134,7 +132,7 @@ Did you initiate a password reset?
|
||||
return render_template('login.html')
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
@auth.route('/logout')
|
||||
def logout():
|
||||
if authed():
|
||||
session.clear()
|
||||
|
||||
@@ -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.models import db, Challenges, Files, Solves, WrongKeys, Keys
|
||||
@@ -7,9 +7,11 @@ import time
|
||||
import re
|
||||
import logging
|
||||
|
||||
def init_challenges(app):
|
||||
@app.route('/challenges', methods=['GET'])
|
||||
def challenges():
|
||||
challenges = Blueprint('challenges', __name__)
|
||||
|
||||
|
||||
@challenges.route('/challenges', methods=['GET'])
|
||||
def challenges_view():
|
||||
if not is_admin():
|
||||
if not ctftime():
|
||||
if view_after_ctf():
|
||||
@@ -21,7 +23,8 @@ def init_challenges(app):
|
||||
else:
|
||||
return redirect(url_for('login', next="challenges"))
|
||||
|
||||
@app.route('/chals', methods=['GET'])
|
||||
|
||||
@challenges.route('/chals', methods=['GET'])
|
||||
def chals():
|
||||
if not is_admin():
|
||||
if not ctftime():
|
||||
@@ -43,7 +46,8 @@ def init_challenges(app):
|
||||
db.session.close()
|
||||
return redirect('/login')
|
||||
|
||||
@app.route('/chals/solves')
|
||||
|
||||
@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()
|
||||
@@ -53,8 +57,9 @@ def init_challenges(app):
|
||||
return jsonify(json)
|
||||
return redirect(url_for('login', next="/chals/solves"))
|
||||
|
||||
@app.route('/solves')
|
||||
@app.route('/solves/<teamid>')
|
||||
|
||||
@challenges.route('/solves')
|
||||
@challenges.route('/solves/<teamid>')
|
||||
def solves(teamid=None):
|
||||
if teamid is None:
|
||||
if authed():
|
||||
@@ -69,7 +74,8 @@ def init_challenges(app):
|
||||
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')
|
||||
|
||||
@challenges.route('/maxattempts')
|
||||
def attempts():
|
||||
chals = Challenges.query.add_columns('id').all()
|
||||
json = {'maxattempts':[]}
|
||||
@@ -79,7 +85,8 @@ def init_challenges(app):
|
||||
json['maxattempts'].append({'chalid':chalid})
|
||||
return jsonify(json)
|
||||
|
||||
@app.route('/fails/<teamid>', methods=['GET'])
|
||||
|
||||
@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()
|
||||
@@ -87,7 +94,8 @@ def init_challenges(app):
|
||||
json = {'fails':str(fails), 'solves': str(solves)}
|
||||
return jsonify(json)
|
||||
|
||||
@app.route('/chal/<chalid>/solves', methods=['GET'])
|
||||
|
||||
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
|
||||
def who_solved(chalid):
|
||||
solves = Solves.query.filter_by(chalid=chalid)
|
||||
json = {'teams':[]}
|
||||
@@ -95,7 +103,8 @@ def init_challenges(app):
|
||||
json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date})
|
||||
return jsonify(json)
|
||||
|
||||
@app.route('/chal/<chalid>', methods=['POST'])
|
||||
|
||||
@challenges.route('/chal/<chalid>', methods=['POST'])
|
||||
def chal(chalid):
|
||||
if not ctftime():
|
||||
return redirect('/challenges')
|
||||
|
||||
@@ -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 socket import inet_aton, inet_ntoa
|
||||
@@ -79,7 +78,6 @@ class Keys(db.Model):
|
||||
key_type = db.Column(db.Integer)
|
||||
flag = db.Column(db.Text)
|
||||
|
||||
|
||||
def __init__(self, chal, flag, key_type):
|
||||
self.chal = chal
|
||||
self.flag = flag
|
||||
@@ -181,6 +179,7 @@ class Tracking(db.Model):
|
||||
def __repr__(self):
|
||||
return '<ip %r>' % self.team
|
||||
|
||||
|
||||
class Config(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.Text)
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
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.models import db, Teams, Solves, Challenges
|
||||
|
||||
def init_scoreboard(app):
|
||||
@app.route('/scoreboard')
|
||||
def scoreboard():
|
||||
scoreboard = Blueprint('scoreboard', __name__)
|
||||
|
||||
|
||||
@scoreboard.route('/scoreboard')
|
||||
def scoreboard_view():
|
||||
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')
|
||||
|
||||
@scoreboard.route('/scores')
|
||||
def scores():
|
||||
score = db.func.sum(Challenges.value).label('score')
|
||||
quickest = db.func.max(Solves.date).label('quickest')
|
||||
@@ -22,7 +25,8 @@ def init_scoreboard(app):
|
||||
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('/top/<count>')
|
||||
def topteams(count):
|
||||
try:
|
||||
count = int(count)
|
||||
|
||||
|
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="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="//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">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
|
||||
</head>
|
||||
@@ -32,22 +32,13 @@
|
||||
|
||||
<!-- Left Nav Section -->
|
||||
<ul class="left">
|
||||
<li><a href="/admin/graphs">Graphs</a>
|
||||
</li>
|
||||
<li><a href="/admin/pages">Pages</a>
|
||||
</li>
|
||||
<li><a href="/admin/hosts">Hosts</a>
|
||||
</li>
|
||||
<li><a href="/admin/teams">Teams</a>
|
||||
</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>
|
||||
<li><a href="/admin/graphs">Graphs</a></li>
|
||||
<li><a href="/admin/pages">Pages</a></li>
|
||||
<li><a href="/admin/teams">Teams</a></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>
|
||||
</section>
|
||||
</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 %}>
|
||||
</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">
|
||||
<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 %}>
|
||||
@@ -16,6 +16,14 @@
|
||||
</form>
|
||||
<a class="close-reveal-modal">×</a>
|
||||
</div>
|
||||
|
||||
<div class="small-6 columns">
|
||||
<h3>CSS editor <a onclick="save_css()"><i class="fa fa-floppy-o"></i></a></h3>
|
||||
<div style="height: 500px;" id="admin-css-editor" name="css">{{ css }}</div>
|
||||
</div>
|
||||
|
||||
<div class="small-6 columns">
|
||||
<h3>HTML Pages <a href="/admin/pages?mode=create"><i class="fa fa-plus"></i></a></h3>
|
||||
<table id="pages">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -32,14 +40,14 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<form method="POST">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<button class="radius" type='submit'>New Page</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% 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>
|
||||
$('#delete-route').click(function(e){
|
||||
e.preventDefault();
|
||||
@@ -60,10 +68,23 @@ function load_confirm_modal(route){
|
||||
$('#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(){
|
||||
var elem = $(this).parent().parent();
|
||||
var name = elem.find('.route-name').text().trim();
|
||||
load_confirm_modal(name)
|
||||
});
|
||||
|
||||
var editor = ace.edit("admin-css-editor");
|
||||
editor.setTheme("ace/theme/github");
|
||||
editor.getSession().setMode("ace/mode/css");
|
||||
|
||||
</script>
|
||||
{% 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="//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/user.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -40,29 +41,9 @@
|
||||
{%else %}
|
||||
<li class="has-form">
|
||||
{% if can_register() %}
|
||||
<li class="has-dropdown">
|
||||
<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>
|
||||
<li><a href="/register">Register</a></li>
|
||||
{% endif %}
|
||||
<li class="has-dropdown">
|
||||
<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>
|
||||
<li><a href="/login">Login</a></li>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -19,6 +19,7 @@
|
||||
<input class="radius" type='text' name='name' placeholder='Name'><br/>
|
||||
<input class="radius" type='password' name='password' placeholder='Password'><br/>
|
||||
<p><a href="/reset_password">Forgot your password?</a></p>
|
||||
<input type="hidden" name="nonce" value="{{nonce}}">
|
||||
<button class="radius" type='submit'>Login</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -37,6 +37,7 @@
|
||||
<h4 class="text-center"><a href="/admin">Click here</a> to login and setup your CTF</h4>
|
||||
</div>
|
||||
</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>
|
||||
</form>
|
||||
</div>
|
||||
@@ -3,7 +3,7 @@ from CTFd import mail
|
||||
|
||||
from urlparse import urlparse, urljoin
|
||||
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 socket import inet_aton, inet_ntoa
|
||||
from struct import unpack, pack
|
||||
@@ -11,11 +11,71 @@ from struct import unpack, pack
|
||||
import time
|
||||
import datetime
|
||||
import hashlib
|
||||
import digitalocean
|
||||
import shutil
|
||||
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):
|
||||
app.jinja_env.filters['unix_time'] = unix_time
|
||||
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(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():
|
||||
name = get_config('ctf_name')
|
||||
@@ -205,10 +278,3 @@ def validate_url(url):
|
||||
def sha512(string):
|
||||
return hashlib.sha512(string).hexdigest()
|
||||
|
||||
|
||||
def get_digitalocean():
|
||||
token = get_config('do_api_key')
|
||||
if token:
|
||||
return digitalocean.Manager(token=token)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -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 CTFd.utils import authed, ip2long, long2ip, is_setup, validate_url, get_config
|
||||
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, sha512
|
||||
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
||||
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
@@ -13,8 +13,10 @@ import sys
|
||||
import json
|
||||
import os
|
||||
|
||||
def init_views(app):
|
||||
@app.before_request
|
||||
views = Blueprint('views', __name__)
|
||||
|
||||
|
||||
@views.before_request
|
||||
def tracker():
|
||||
if authed():
|
||||
if not Tracking.query.filter_by(ip=ip2long(request.remote_addr)).first():
|
||||
@@ -23,25 +25,23 @@ def init_views(app):
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
@app.before_request
|
||||
|
||||
@views.before_request
|
||||
def csrf():
|
||||
if authed() and request.method == "POST":
|
||||
if request.method == "POST":
|
||||
if session['nonce'] != request.form.get('nonce'):
|
||||
abort(403)
|
||||
|
||||
@app.before_request
|
||||
|
||||
@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')
|
||||
|
||||
@app.before_first_request
|
||||
def needs_setup():
|
||||
if not is_setup():
|
||||
return redirect('/setup')
|
||||
|
||||
@app.route('/setup', methods=['GET', 'POST'])
|
||||
@views.route('/setup', methods=['GET', 'POST'])
|
||||
def setup():
|
||||
# with app.app_context():
|
||||
# admin = Teams.query.filter_by(admin=True).first()
|
||||
@@ -51,6 +51,9 @@ def init_views(app):
|
||||
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']
|
||||
@@ -86,16 +89,24 @@ def init_views(app):
|
||||
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')
|
||||
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
|
||||
@app.route("/", defaults={'template': 'index'})
|
||||
@app.route("/<template>")
|
||||
@views.route("/", defaults={'template': 'index'})
|
||||
@views.route("/<template>")
|
||||
def static_html(template):
|
||||
try:
|
||||
return render_template('%s.html' % template)
|
||||
@@ -106,12 +117,14 @@ def init_views(app):
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@app.route('/teams')
|
||||
|
||||
@views.route('/teams')
|
||||
def teams():
|
||||
teams = Teams.query.all()
|
||||
return render_template('teams.html', teams=teams)
|
||||
|
||||
@app.route('/team/<teamid>', methods=['GET', 'POST'])
|
||||
|
||||
@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()
|
||||
@@ -128,7 +141,7 @@ def init_views(app):
|
||||
return jsonify(json)
|
||||
|
||||
|
||||
@app.route('/profile', methods=['POST', 'GET'])
|
||||
@views.route('/profile', methods=['POST', 'GET'])
|
||||
def profile():
|
||||
if authed():
|
||||
if request.method == "POST":
|
||||
|
||||
@@ -8,5 +8,4 @@ passlib==1.6.2
|
||||
bcrypt
|
||||
six==1.8.0
|
||||
itsdangerous==0.24
|
||||
python-digitalocean==1.4.2
|
||||
requests==2.3.0
|
||||
|
||||