mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 22:14:25 +01:00
Supports PY3, refinements to chal editor and viewer, model changes to resolve issues
This commit is contained in:
@@ -22,7 +22,7 @@ def create_app(config='CTFd.config'):
|
||||
global mail
|
||||
mail = Mail(app)
|
||||
|
||||
Session(app)
|
||||
#Session(app)
|
||||
|
||||
from CTFd.views import views
|
||||
from CTFd.challenges import challenges
|
||||
|
||||
114
CTFd/admin.py
114
CTFd/admin.py
@@ -147,7 +147,6 @@ def admin_css():
|
||||
if request.method == 'POST':
|
||||
css = request.form['css']
|
||||
css = set_config('css', css)
|
||||
print css
|
||||
return "1"
|
||||
return "0"
|
||||
|
||||
@@ -198,12 +197,12 @@ def admin_chals():
|
||||
if request.method == 'POST':
|
||||
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
|
||||
|
||||
json = {'game':[]}
|
||||
json_data = {'game':[]}
|
||||
for x in chals:
|
||||
json['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5]})
|
||||
json_data['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5]})
|
||||
|
||||
db.session.close()
|
||||
return jsonify(json)
|
||||
return jsonify(json_data)
|
||||
else:
|
||||
return render_template('admin/chals.html')
|
||||
|
||||
@@ -212,21 +211,25 @@ def admin_chals():
|
||||
@admins_only
|
||||
def admin_keys(chalid):
|
||||
if request.method == 'GET':
|
||||
keys = Keys.query.filter_by(chal=chalid).all()
|
||||
json = {'keys':[]}
|
||||
for x in keys:
|
||||
json['keys'].append({'id':x.id, 'key':x.flag, 'type':x.key_type})
|
||||
return jsonify(json)
|
||||
chal = Challenges.query.filter_by(id=chalid).first_or_404()
|
||||
json_data = {'keys':[]}
|
||||
flags = json.loads(chal.flags)
|
||||
for i, x in enumerate(flags):
|
||||
json_data['keys'].append({'id':i, 'key':x['flag'], 'type':x['type']})
|
||||
return jsonify(json_data)
|
||||
elif request.method == 'POST':
|
||||
keys = Keys.query.filter_by(chal=chalid).all()
|
||||
for x in keys:
|
||||
db.session.delete(x)
|
||||
chal = Challenges.query.filter_by(id=chalid).first()
|
||||
|
||||
newkeys = request.form.getlist('keys[]')
|
||||
newvals = request.form.getlist('vals[]')
|
||||
print(list(zip(newkeys, newvals)))
|
||||
flags = []
|
||||
for flag, val in zip(newkeys, newvals):
|
||||
key = Keys(chalid, flag, val)
|
||||
db.session.add(key)
|
||||
flag_dict = {'flag':flag, 'type':int(val)}
|
||||
flags.append(flag_dict)
|
||||
json_data = json.dumps(flags)
|
||||
|
||||
chal.flags = json_data
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
@@ -238,10 +241,10 @@ def admin_keys(chalid):
|
||||
def admin_tags(chalid):
|
||||
if request.method == 'GET':
|
||||
tags = Tags.query.filter_by(chal=chalid).all()
|
||||
json = {'tags':[]}
|
||||
json_data = {'tags':[]}
|
||||
for x in tags:
|
||||
json['tags'].append({'id':x.id, 'chal':x.chal, 'tag':x.tag})
|
||||
return jsonify(json)
|
||||
json_data['tags'].append({'id':x.id, 'chal':x.chal, 'tag':x.tag})
|
||||
return jsonify(json_data)
|
||||
|
||||
elif request.method == 'POST':
|
||||
newtags = request.form.getlist('tags[]')
|
||||
@@ -269,10 +272,10 @@ def admin_delete_tags(tagid):
|
||||
def admin_files(chalid):
|
||||
if request.method == 'GET':
|
||||
files = Files.query.filter_by(chal=chalid).all()
|
||||
json = {'files':[]}
|
||||
json_data = {'files':[]}
|
||||
for x in files:
|
||||
json['files'].append({'id':x.id, 'file':x.location})
|
||||
return jsonify(json)
|
||||
json_data['files'].append({'id':x.id, 'file':x.location})
|
||||
return jsonify(json_data)
|
||||
if request.method == 'POST':
|
||||
if request.form['method'] == "delete":
|
||||
f = Files.query.filter_by(id=request.form['file']).first_or_404()
|
||||
@@ -305,11 +308,20 @@ def admin_files(chalid):
|
||||
return redirect('/admin/chals')
|
||||
|
||||
|
||||
@admin.route('/admin/teams')
|
||||
@admin.route('/admin/teams', defaults={'page':'1'})
|
||||
@admin.route('/admin/teams/<page>')
|
||||
@admins_only
|
||||
def admin_teams():
|
||||
teams = Teams.query.all()
|
||||
return render_template('admin/teams.html', teams=teams)
|
||||
def admin_teams(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * ( page - 1 )
|
||||
page_end = results_per_page * ( page - 1 ) + results_per_page
|
||||
|
||||
teams = Teams.query.slice(page_start, page_end).all()
|
||||
count = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
print(count)
|
||||
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
||||
return render_template('admin/teams.html', teams=teams, pages=pages)
|
||||
|
||||
|
||||
@admin.route('/admin/team/<teamid>', methods=['GET', 'POST'])
|
||||
@@ -317,17 +329,19 @@ def admin_teams():
|
||||
def admin_team(teamid):
|
||||
user = Teams.query.filter_by(id=teamid).first()
|
||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||
addrs = Tracking.query.filter_by(team=teamid).group_by(Tracking.ip).all()
|
||||
addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all()
|
||||
wrong_keys = WrongKeys.query.filter_by(team=teamid).order_by(WrongKeys.date.desc()).all()
|
||||
score = user.score()
|
||||
place = user.place()
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place)
|
||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place, wrong_keys=wrong_keys)
|
||||
elif request.method == 'POST':
|
||||
admin_user = request.form.get('admin', "false")
|
||||
admin_user = 1 if admin_user == "true" else 0
|
||||
if admin:
|
||||
user.admin = 1
|
||||
user.banned = 1
|
||||
db.session.commit()
|
||||
return jsonify({'data': ['success']})
|
||||
|
||||
@@ -407,16 +421,16 @@ def delete_team(teamid):
|
||||
def admin_graph(graph_type):
|
||||
if graph_type == 'categories':
|
||||
categories = db.session.query(Challenges.category, db.func.count(Challenges.category)).group_by(Challenges.category).all()
|
||||
json = {'categories':[]}
|
||||
json_data = {'categories':[]}
|
||||
for category, count in categories:
|
||||
json['categories'].append({'category':category, 'count':count})
|
||||
return jsonify(json)
|
||||
json_data['categories'].append({'category':category, 'count':count})
|
||||
return jsonify(json_data)
|
||||
elif graph_type == "solves":
|
||||
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
|
||||
json = {}
|
||||
json_data = {}
|
||||
for chal, count in solves:
|
||||
json[chal.chal.name] = count
|
||||
return jsonify(json)
|
||||
json_data[chal.chal.name] = count
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/scoreboard')
|
||||
@@ -436,10 +450,10 @@ def admin_scores():
|
||||
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':[]}
|
||||
json_data = {'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)
|
||||
json_data['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)})
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/solves/<teamid>', methods=['GET'])
|
||||
@@ -450,10 +464,10 @@ def admin_solves(teamid="all"):
|
||||
else:
|
||||
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||
db.session.close()
|
||||
json = {'solves':[]}
|
||||
json_data = {'solves':[]}
|
||||
for x in solves:
|
||||
json['solves'].append({'id':x.id, '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)
|
||||
json_data['solves'].append({'id':x.id, '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_data)
|
||||
|
||||
|
||||
@admin.route('/admin/solves/<teamid>/<chalid>/delete', methods=['POST'])
|
||||
@@ -531,14 +545,14 @@ def admin_fails(teamid='all'):
|
||||
fails = WrongKeys.query.count()
|
||||
solves = Solves.query.count()
|
||||
db.session.close()
|
||||
json = {'fails':str(fails), 'solves': str(solves)}
|
||||
return jsonify(json)
|
||||
json_data = {'fails':str(fails), 'solves': str(solves)}
|
||||
return jsonify(json_data)
|
||||
else:
|
||||
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)
|
||||
json_data = {'fails':str(fails), 'solves': str(solves)}
|
||||
return jsonify(json_data)
|
||||
|
||||
|
||||
@admin.route('/admin/chal/new', methods=['POST'])
|
||||
@@ -546,14 +560,12 @@ def admin_fails(teamid='all'):
|
||||
def admin_create_chal():
|
||||
files = request.files.getlist('files[]')
|
||||
|
||||
# Create challenge
|
||||
chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category'])
|
||||
db.session.add(chal)
|
||||
db.session.commit()
|
||||
## TODO: Expand to support multiple flags
|
||||
flags = [{'flag':request.form['key'], 'type':int(request.form['key_type[0]'])}]
|
||||
|
||||
# Add keys
|
||||
key = Keys(chal.id, request.form['key'], request.form['key_type[0]'])
|
||||
db.session.add(key)
|
||||
# Create challenge
|
||||
chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category'], flags)
|
||||
db.session.add(chal)
|
||||
db.session.commit()
|
||||
|
||||
for f in files:
|
||||
@@ -562,7 +574,7 @@ def admin_create_chal():
|
||||
if len(filename) <= 0:
|
||||
continue
|
||||
|
||||
md5hash = hashlib.md5(filename).hexdigest()
|
||||
md5hash = hashlib.md5(os.urandom(64)).hexdigest()
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.normpath(app.static_folder), 'uploads', md5hash)):
|
||||
os.makedirs(os.path.join(os.path.normpath(app.static_folder), 'uploads', md5hash))
|
||||
@@ -581,7 +593,7 @@ def admin_create_chal():
|
||||
def admin_delete_chal():
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||
if challenge:
|
||||
WrongKeys.query.filter_by(chal=challenge.id).delete()
|
||||
WrongKeys.query.filter_by(chalid=challenge.id).delete()
|
||||
Solves.query.filter_by(chalid=challenge.id).delete()
|
||||
Keys.query.filter_by(chal=challenge.id).delete()
|
||||
files = Files.query.filter_by(chal=challenge.id).all()
|
||||
|
||||
@@ -6,6 +6,7 @@ from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys
|
||||
import time
|
||||
import re
|
||||
import logging
|
||||
import json
|
||||
|
||||
challenges = Blueprint('challenges', __name__)
|
||||
|
||||
@@ -21,7 +22,7 @@ def challenges_view():
|
||||
if can_view_challenges():
|
||||
return render_template('chals.html', ctftime=ctftime())
|
||||
else:
|
||||
return redirect(url_for('login', next="challenges"))
|
||||
return redirect('/login')
|
||||
|
||||
|
||||
@challenges.route('/chals', methods=['GET'])
|
||||
@@ -55,7 +56,7 @@ def chals_per_solves():
|
||||
for chal, count in solves:
|
||||
json[chal.chal.name] = count
|
||||
return jsonify(json)
|
||||
return redirect(url_for('login', next="/chals/solves"))
|
||||
return redirect('/login')
|
||||
|
||||
|
||||
@challenges.route('/solves')
|
||||
@@ -80,7 +81,7 @@ 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()
|
||||
fails = WrongKeys.query.filter_by(team=session['id'], chalid=chalid).count()
|
||||
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
|
||||
json['maxattempts'].append({'chalid':chalid})
|
||||
return jsonify(json)
|
||||
@@ -97,7 +98,7 @@ def fails(teamid):
|
||||
|
||||
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
|
||||
def who_solved(chalid):
|
||||
solves = Solves.query.filter_by(chalid=chalid)
|
||||
solves = Solves.query.filter_by(chalid=chalid).order_by(Solves.date.asc())
|
||||
json = {'teams':[]}
|
||||
for solve in solves:
|
||||
json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date})
|
||||
@@ -109,12 +110,16 @@ def chal(chalid):
|
||||
if not ctftime():
|
||||
return redirect('/challenges')
|
||||
if authed():
|
||||
fails = WrongKeys.query.filter_by(team=session['id'],chal=chalid).count()
|
||||
fails = WrongKeys.query.filter_by(team=session['id'], chalid=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)
|
||||
print("[{0}] {1} submitted {2} with kpm {3}".format(*data))
|
||||
|
||||
# Hit max attempts
|
||||
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
|
||||
return "4" #too many tries on this challenge
|
||||
|
||||
# Anti-bruteforce / submitting keys too quickly
|
||||
if get_kpm(session['id']) > 10:
|
||||
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||
db.session.add(wrong)
|
||||
@@ -122,21 +127,26 @@ def chal(chalid):
|
||||
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()
|
||||
|
||||
# Challange not solved yet
|
||||
if not solves:
|
||||
keys = Keys.query.filter_by(chal=chalid).all()
|
||||
key = request.form['key'].strip().lower()
|
||||
chal = Challenges.query.filter_by(id=chalid).first()
|
||||
key = str(request.form['key'].strip().lower())
|
||||
keys = json.loads(chal.flags)
|
||||
for x in keys:
|
||||
if x.key_type == 0: #static key
|
||||
if x.flag.strip().lower() == key:
|
||||
if x['type'] == 0: #static key
|
||||
print(x['flag'], key.strip().lower())
|
||||
if x['flag'] == key.strip().lower():
|
||||
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)
|
||||
elif x['type'] == 1: #regex
|
||||
res = re.match(str(x['flag']), 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)
|
||||
@@ -151,6 +161,8 @@ def chal(chalid):
|
||||
db.session.close()
|
||||
logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data))
|
||||
return '0' # key was wrong
|
||||
|
||||
# Challenge already solved
|
||||
else:
|
||||
logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data))
|
||||
return "2" # challenge was already solved
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
##### SERVER SETTINGS #####
|
||||
SECRET_KEY = os.urandom(64)
|
||||
#SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost/ctfd'
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db'
|
||||
SESSION_TYPE = "filesystem"
|
||||
SESSION_FILE_DIR = "/tmp/flask_session"
|
||||
|
||||
@@ -6,6 +6,7 @@ from passlib.hash import bcrypt_sha256
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
def sha512(string):
|
||||
return hashlib.sha512(string).hexdigest()
|
||||
@@ -37,13 +38,15 @@ class Challenges(db.Model):
|
||||
description = db.Column(db.Text)
|
||||
value = db.Column(db.Integer)
|
||||
category = db.Column(db.String(80))
|
||||
# add open category
|
||||
flags = db.Column(db.Text)
|
||||
hidden = db.Column(db.Boolean)
|
||||
|
||||
def __init__(self, name, description, value, category):
|
||||
def __init__(self, name, description, value, category, flags):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.value = value
|
||||
self.category = category
|
||||
self.flags = json.dumps(flags)
|
||||
|
||||
def __repr__(self):
|
||||
return '<chal %r>' % self.name
|
||||
@@ -102,7 +105,7 @@ class Teams(db.Model):
|
||||
def __init__(self, name, email, password):
|
||||
self.name = name
|
||||
self.email = email
|
||||
self.password = bcrypt_sha256.encrypt(password)
|
||||
self.password = bcrypt_sha256.encrypt(str(password))
|
||||
|
||||
def __repr__(self):
|
||||
return '<team %r>' % self.name
|
||||
@@ -152,14 +155,15 @@ class Solves(db.Model):
|
||||
|
||||
class WrongKeys(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
chal = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||
chalid = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||
team = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
||||
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||
flag = db.Column(db.Text)
|
||||
chal = db.relationship('Challenges', foreign_keys="WrongKeys.chalid", lazy='joined')
|
||||
|
||||
def __init__(self, team, chal, flag):
|
||||
def __init__(self, team, chalid, flag):
|
||||
self.team = team
|
||||
self.chal = chal
|
||||
self.chalid = chalid
|
||||
self.flag = flag
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
@@ -69,6 +69,7 @@ table{
|
||||
|
||||
#score-graph{
|
||||
max-height: 400px;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
#keys-pie-graph{
|
||||
@@ -82,3 +83,25 @@ table{
|
||||
float: left;
|
||||
max-height: 330px;
|
||||
}
|
||||
|
||||
.chal-button{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.chal-button > p{
|
||||
font-family: monospace;
|
||||
margin-bottom: 0px;
|
||||
height: 20px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chal-button > span{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.remove-key{
|
||||
float:right;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
//http://stackoverflow.com/a/2648463 - wizardry!
|
||||
String.prototype.format = String.prototype.f = function() {
|
||||
var s = this,
|
||||
i = arguments.length;
|
||||
|
||||
while (i--) {
|
||||
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
//http://stackoverflow.com/a/7616484
|
||||
String.prototype.hashCode = function() {
|
||||
var hash = 0, i, chr, len;
|
||||
@@ -16,8 +27,9 @@ function loadchal(id) {
|
||||
obj = $.grep(challenges['game'], function (e) {
|
||||
return e.id == id;
|
||||
})[0]
|
||||
$('.chal-title').text(obj.name);
|
||||
$('.chal-name').val(obj.name);
|
||||
$('.chal-desc').html(obj.description);
|
||||
$('.chal-desc').val(obj.description);
|
||||
$('.chal-value').val(obj.value);
|
||||
$('.chal-category').val(obj.category);
|
||||
$('.chal-id').val(obj.id);
|
||||
@@ -44,9 +56,13 @@ function loadkeys(chal){
|
||||
keys = keys['keys'];
|
||||
$('#current-keys').empty();
|
||||
for(x=0; x<keys.length; x++){
|
||||
$('#current-keys').append($("<input class='current-key' type='text'>").val(keys[x].key));
|
||||
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="0">Static');
|
||||
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="1">Regex');
|
||||
var elem = $('<div>');
|
||||
elem.append($("<input class='current-key' type='text'>").val(keys[x].key));
|
||||
elem.append('<input type="radio" name="key_type['+x+']" value="0">Static');
|
||||
elem.append('<input type="radio" name="key_type['+x+']" value="1">Regex');
|
||||
elem.append('<a href="#" onclick="$(this).parent().remove()" class="remove-key">Remove</a>');
|
||||
|
||||
$('#current-keys').append(elem);
|
||||
$('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true);
|
||||
}
|
||||
});
|
||||
@@ -59,7 +75,7 @@ function updatekeys(){
|
||||
$('.current-key').each(function(){
|
||||
keys.push($(this).val());
|
||||
})
|
||||
$('#current-keys > input[name*="key_type"]:checked').each(function(){
|
||||
$('#current-keys input[name*="key_type"]:checked').each(function(){
|
||||
vals.push($(this).val());
|
||||
})
|
||||
$.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
|
||||
@@ -146,13 +162,10 @@ function loadchals(){
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = categories.length - 1; i >= 0; i--) {
|
||||
$('#new-challenge select').append('<option value="' + categories[i] + '">' + categories[i] + '</option>');
|
||||
$('#update-challenge select').append('<option value="' + categories[i] + '">' + categories[i] + '</option>');
|
||||
};
|
||||
|
||||
for (var i = 0; i <= challenges['game'].length - 1; i++) {
|
||||
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append($('<button class="radius" value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>'));
|
||||
var chal = challenges['game'][i]
|
||||
var chal_button = $('<button class="chal-button" value="{0}"><p>{1}</p><span>{2}</span></button>'.format(chal.id, chal.name, chal.value))
|
||||
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button);
|
||||
};
|
||||
|
||||
$('#challenges button').click(function (e) {
|
||||
@@ -162,8 +175,6 @@ function loadchals(){
|
||||
loadfiles(this.value);
|
||||
});
|
||||
|
||||
$('tr').append('<button class="radius create-challenge"><i class="fa fa-plus"></i></button>');
|
||||
|
||||
$('.create-challenge').click(function (e) {
|
||||
$('#new-chal-category').val($($(this).siblings()[0]).text().trim())
|
||||
$('#new-chal-title').text($($(this).siblings()[0]).text().trim())
|
||||
@@ -177,9 +188,7 @@ $('#submit-key').click(function (e) {
|
||||
});
|
||||
|
||||
$('#submit-keys').click(function (e) {
|
||||
if (confirm('Updating keys. Are you sure?')){
|
||||
updatekeys()
|
||||
}
|
||||
});
|
||||
|
||||
$('#submit-tags').click(function (e) {
|
||||
@@ -212,15 +221,38 @@ $(".tag-insert").keyup(function (e) {
|
||||
}
|
||||
});
|
||||
|
||||
$('.create-category').click(function (e) {
|
||||
$('#new-category').foundation('reveal', 'open');
|
||||
|
||||
|
||||
// Markdown Preview
|
||||
$('#desc-edit').on('toggled', function (event, tab) {
|
||||
if (tab[0].id == 'desc-preview'){
|
||||
$(tab[0]).html(marked($('#desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
||||
count = 1;
|
||||
$('#create-key').click(function (e) {
|
||||
$('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>");
|
||||
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="0">Static');
|
||||
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="1">Regex');
|
||||
count++;
|
||||
$('#new-desc-edit').on('toggled', function (event, tab) {
|
||||
if (tab[0].id == 'new-desc-preview'){
|
||||
$(tab[0]).html(marked($('#new-desc-editor').val(), {'gfm':true, 'breaks':true}))
|
||||
}
|
||||
});
|
||||
|
||||
// Open New Challenge modal when New Challenge button is clicked
|
||||
$('.create-challenge').click(function (e) {
|
||||
$('#create-challenge').foundation('reveal', 'open');
|
||||
});
|
||||
|
||||
|
||||
$('#create-key').click(function(e){
|
||||
var amt = $('#current-keys input[type=text]').length
|
||||
// $('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>");
|
||||
// $('#current-keys').append('<input type="radio" name="key_type[{0}]" value="0">Static'.format(amt));
|
||||
// $('#current-keys').append('<input type="radio" name="key_type[{0}]" value="1">Regex'.format(amt));
|
||||
|
||||
var elem = $('<div>');
|
||||
elem.append("<input class='current-key' type='text' placeholder='Blank Key'>");
|
||||
elem.append('<input type="radio" name="key_type[{0}]" value="0">Static'.format(amt));
|
||||
elem.append('<input type="radio" name="key_type[{0}]" value="1">Regex'.format(amt));
|
||||
elem.append('<a href="#" onclick="$(this).parent().remove()" class="remove-key">Remove</a>');
|
||||
$('#current-keys').append(elem);
|
||||
});
|
||||
|
||||
$(function(){
|
||||
|
||||
@@ -56,6 +56,7 @@ table{
|
||||
|
||||
#score-graph{
|
||||
max-height: 400px;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
.dropdown{
|
||||
@@ -91,6 +92,24 @@ table{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chal-button{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.chal-button > p{
|
||||
font-family: monospace;
|
||||
margin-bottom: 0px;
|
||||
height: 20px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chal-button > span{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 40.063em){
|
||||
.top-bar .dropdown{
|
||||
display: block;
|
||||
@@ -103,3 +122,8 @@ table{
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.scroll-wrap{
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@@ -206,7 +206,9 @@ function loadchals() {
|
||||
};
|
||||
|
||||
for (var i = 0; i <= challenges['game'].length - 1; i++) {
|
||||
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append($('<button value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>'));
|
||||
var chal = challenges['game'][i]
|
||||
var chal_button = $('<button class="chal-button" value="{0}"><p>{1}</p><span>{2}</span></button>'.format(chal.id, chal.name, chal.value))
|
||||
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button);
|
||||
};
|
||||
updatesolves()
|
||||
marktoomanyattempts()
|
||||
|
||||
@@ -9,6 +9,16 @@ String.prototype.format = String.prototype.f = function() {
|
||||
return s;
|
||||
};
|
||||
|
||||
function colorhash (x) {
|
||||
color = ""
|
||||
for (var i = 20; i <= 60; i+=20){
|
||||
x += i
|
||||
x *= i
|
||||
color += x.toString(16)
|
||||
};
|
||||
return "#" + color.substring(0, 6)
|
||||
}
|
||||
|
||||
function htmlentities(string) {
|
||||
return $('<div/>').text(string).html();
|
||||
}
|
||||
@@ -37,7 +47,6 @@ function UTCtoDate(utc){
|
||||
d.setUTCSeconds(utc)
|
||||
return d;
|
||||
}
|
||||
|
||||
function scoregraph () {
|
||||
var times = []
|
||||
var scores = []
|
||||
@@ -52,12 +61,15 @@ function scoregraph () {
|
||||
|
||||
xs_data = {}
|
||||
column_data = []
|
||||
colors = {}
|
||||
for (var i = 0; i < teams.length; i++) {
|
||||
times = []
|
||||
team_scores = []
|
||||
for (var x = 0; x < scores[teams[i]].length; x++) {
|
||||
times.push(scores[teams[i]][x].time)
|
||||
team_scores.push(scores[teams[i]][x].value)
|
||||
console.log(scores[teams[i]])
|
||||
colors[teams[i]] = colorhash(scores[teams[i]][x].id)
|
||||
};
|
||||
team_scores = cumulativesum(team_scores)
|
||||
|
||||
@@ -79,7 +91,8 @@ function scoregraph () {
|
||||
data: {
|
||||
xs: xs_data,
|
||||
columns: column_data,
|
||||
type: "step"
|
||||
type: "step",
|
||||
colors: colors
|
||||
// labels: true
|
||||
},
|
||||
axis : {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
function teamid (){
|
||||
loc = window.location.pathname
|
||||
return loc.substring(loc.lastIndexOf('/')+1, loc.length);
|
||||
return parseInt(loc.substring(loc.lastIndexOf('/')+1, loc.length));
|
||||
}
|
||||
|
||||
function colorhash (x) {
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
{% block content %} {% endblock %}
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/vendor/jquery.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/vendor/modernizr.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/pagedown/1.0/Markdown.Converter.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/foundation.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/foundation/foundation.topbar.min.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -2,47 +2,31 @@
|
||||
|
||||
{% block content %}
|
||||
<input type="hidden" value="{{ nonce }}" id="nonce">
|
||||
<div id="new-category" class="reveal-modal" data-reveal>
|
||||
<form method="POST" action="/admin/chal/new" enctype="multipart/form-data">
|
||||
<h3>New Category</h3>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input type='text' name='category' placeholder='Category'><br/>
|
||||
<a href="#" data-reveal-id="create-challenge" class="button">Create</a>
|
||||
<a class="close-reveal-modal">×</a>
|
||||
</div>
|
||||
|
||||
<div id="create-challenge" class="reveal-modal" data-reveal>
|
||||
<h3>New Challenge</h3>
|
||||
<form method="POST" action="/admin/chal/new" enctype="multipart/form-data">
|
||||
<input type='text' name='name' placeholder='Name'><br/>
|
||||
<textarea name='desc' placeholder='Description'></textarea><br/>
|
||||
<input type='number' name='value' placeholder='Value'><br/>
|
||||
<input type='text' name='key' placeholder='Key'><br/>
|
||||
<input type="radio" name="key_type[0]" value="0">Static
|
||||
<input type="radio" name="key_type[0]" value="1">Regex
|
||||
<br/>
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
<input type='text' name='category' placeholder='Category'><br/>
|
||||
|
||||
<button type='submit'>Create</button>
|
||||
<a class="close-reveal-modal">×</a>
|
||||
</form>
|
||||
<ul id="new-desc-edit" class="tabs" data-tab>
|
||||
<li class="tab-title active"><a href="#new-desc-write" style="padding: 0 2rem;">Write</a></li>
|
||||
<li class="tab-title"><a href="#new-desc-preview" style="padding: 0 2rem;">Preview</a></li>
|
||||
</ul>
|
||||
<div class="tabs-content">
|
||||
<div class="content active" id="new-desc-write">
|
||||
<textarea id="new-desc-editor" class="new-chal-desc" name='desc' placeholder='Description' rows="10"></textarea><br>
|
||||
</div>
|
||||
<div class="content" id="new-desc-preview">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="new-challenge" class="reveal-modal" data-reveal>
|
||||
<form method="POST" action="/admin/chal/new" enctype="multipart/form-data">
|
||||
<h3><span id="new-chal-title"></span> challenge</h3>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input type='text' name='name' placeholder='Name'><br/>
|
||||
<textarea class="textbox" name='desc' placeholder='Description'></textarea><br/>
|
||||
<input type='number' name='value' placeholder='Value'><br/>
|
||||
<input id="new-chal-category" type="hidden" name="category">
|
||||
<input type='text' name='key' placeholder='Key'><br/>
|
||||
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
|
||||
<br/>
|
||||
<input type="radio" name="key_type[0]" value="0">Static
|
||||
<input type="radio" name="key_type[0]" value="1">Regex
|
||||
<br/>
|
||||
<input type="file" name="files[]" multiple="multiple">
|
||||
|
||||
<button type='submit'>Create</button>
|
||||
<a class="close-reveal-modal">×</a>
|
||||
@@ -111,15 +95,26 @@
|
||||
|
||||
<div id="update-challenge" class="reveal-modal" data-reveal>
|
||||
<form method="POST" action="/admin/chal/update">
|
||||
<h3>Update Challenge</h3>
|
||||
<h3 class="chal-title text-center"></h3>
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input class="chal-name" type='text' name='name' placeholder='Name'><br/>
|
||||
<textarea class="chal-desc textbox" name='desc' placeholder='Description'></textarea><br/>
|
||||
<input class="chal-value" type='number' name='value' placeholder='Value'><br/>
|
||||
<select class="chal-category" name="category">
|
||||
<option>-</option>
|
||||
</select>
|
||||
<input class="chal-id" type='hidden' name='id' placeholder='ID'><br/>
|
||||
Name: <input class="chal-name" type='text' name='name' placeholder='Name'><br>
|
||||
Category: <input class="chal-category" type="text" name="category" placeholder="Category"><br>
|
||||
Description:
|
||||
|
||||
<ul id="desc-edit" class="tabs" data-tab>
|
||||
<li class="tab-title active"><a href="#desc-write" style="padding: 0 2rem;">Write</a></li>
|
||||
<li class="tab-title"><a href="#desc-preview" style="padding: 0 2rem;">Preview</a></li>
|
||||
</ul>
|
||||
<div class="tabs-content">
|
||||
<div class="content active" id="desc-write">
|
||||
<textarea id="desc-editor" class="chal-desc" name='desc' placeholder='Description' rows="10"></textarea><br>
|
||||
</div>
|
||||
<div class="content" id="desc-preview">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Value: <input class="chal-value" type='number' name='value' placeholder='Value'><br>
|
||||
<input class="chal-id" type='hidden' name='id' placeholder='ID'>
|
||||
|
||||
<a href="#" data-reveal-id="update-tags" class="secondary button">Tags</a>
|
||||
<a href="#" data-reveal-id="update-files" class="secondary button">Files</a>
|
||||
@@ -130,14 +125,13 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<h1>CTF</h1>
|
||||
<div>
|
||||
<h1 class="text-center">Challenges</h1>
|
||||
|
||||
<div>
|
||||
<table id='challenges'>
|
||||
</table>
|
||||
<button style="width:100%;" class="radius create-category">New Category</button>
|
||||
<button style="width:100%;" class="radius create-challenge">New Challenge</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<table id="scoreboard">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Place</b></td>
|
||||
<td width="10px"><b>Place</b></td>
|
||||
<td><b>Team</b></td>
|
||||
<td><b>Score</b></td>
|
||||
<td><b>Status</b></td>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
<div class="row">
|
||||
<h1 id="team-id">{{ team.name }}</h1>
|
||||
<h2 id="team-email" class="text-center">{{ team.email }}</h2>
|
||||
<h2 id="team-place" class="text-center">
|
||||
{%if place %}
|
||||
{{ place }} <small>place</small>
|
||||
@@ -19,11 +20,14 @@
|
||||
|
||||
<div id="keys-pie-graph"></div>
|
||||
<div id="categories-pie-graph"></div>
|
||||
<div id="score-graph"></div>
|
||||
|
||||
<table>
|
||||
<h3>Solves</h3>
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Challenge</b></td>
|
||||
<td><b>Submitted</b></td>
|
||||
<td><b>Category</b></td>
|
||||
<td><b>Value</b></td>
|
||||
<td><b>Time</b></td>
|
||||
@@ -31,12 +35,19 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for solve in solves %}
|
||||
<tr><td>{{ solve.chal.name }}</td><td>{{ solve.chal.category }}</td><td>{{ solve.chal.value }}</td><td class="solve-time">{{ solve.date|unix_time_millis }}</td></tr>
|
||||
<tr>
|
||||
<td>{{ solve.chal.name }}</td>
|
||||
<td>{{ solve.flag }}</td>
|
||||
<td>{{ solve.chal.category }}</td>
|
||||
<td>{{ solve.chal.value }}</td>
|
||||
<td class="solve-time">{{ solve.date|unix_time_millis }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<h3>IP Addresses</h3>
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>IP Address</b></td>
|
||||
@@ -50,7 +61,25 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="score-graph"></div>
|
||||
<table>
|
||||
<h3>Wrong Keys</h3>
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Challenge</b></td>
|
||||
<td><b>Submitted</b></td>
|
||||
<td><b>Time</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for wrong_key in wrong_keys %}
|
||||
<tr>
|
||||
<td>{{ wrong_key.chal.name }}</td>
|
||||
<td>{{ wrong_key.flag }}</td>
|
||||
<td class="solve-time">{{ wrong_key.date|unix_time_millis }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<table id="teamsboard">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center"><b>ID</b>
|
||||
<td width="10px" class="text-center"><b>ID</b>
|
||||
</td>
|
||||
<td class="text-center"><b>Team</b>
|
||||
</td>
|
||||
@@ -98,12 +98,12 @@
|
||||
{% for team in teams %}
|
||||
<tr name="{{ team.id }}">
|
||||
<td class="team-id">{{ team.id }}</td>
|
||||
<td class="team-name"><a href="/admin/team/{{ team.id }}">{{ team.name }}</a>
|
||||
<td class="team-name"><a href="/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
|
||||
</td>
|
||||
<td class="team-email">{{ team.email }}</td>
|
||||
<td class="team-website">{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %}
|
||||
<td class="team-email">{{ team.email | truncate(32) }}</td>
|
||||
<td class="team-website">{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website | truncate(32) }}</a>{% endif %}
|
||||
</td>
|
||||
<td class="team-affiliation"><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span>
|
||||
<td class="team-affiliation"><span>{% if team.affiliation %}{{ team.affiliation | truncate(20) }}{% endif %}</span>
|
||||
</td>
|
||||
<td class="team-country"><span>{% if team.country %}{{ team.country }}{% endif %}</span>
|
||||
</td>
|
||||
@@ -122,6 +122,15 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if pages > 1 %}
|
||||
<div class="text-center">Page
|
||||
<br>
|
||||
{% for page in range(1, pages + 1) %}
|
||||
<a href="/admin/teams/{{ page }}">{{ page }}</a>
|
||||
{% endfor %}
|
||||
<a href="">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
<div id="chal-solves-window" class="reveal-modal" data-reveal>
|
||||
<h3>Solved By</h3>
|
||||
<div class="scroll-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -17,6 +18,7 @@
|
||||
<tbody id="chal-solves-names">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a class="close-reveal-modal" data-reveal-id="chal-window">×</a>
|
||||
</div>
|
||||
|
||||
@@ -36,8 +38,9 @@
|
||||
<a class="close-reveal-modal">×</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<h2 class="text-center">Challenges</h2>
|
||||
<div>
|
||||
<h1 class="text-center">Challenges</h1>
|
||||
<br>
|
||||
|
||||
<div class="large-12 columns">
|
||||
<table id='challenges'>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||
|
||||
<div class="row">
|
||||
<h1>Scoreboard</h1>
|
||||
<br>
|
||||
<div id="score-graph"></div>
|
||||
<br>
|
||||
@@ -11,7 +12,7 @@
|
||||
<table id="scoreboard">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Place</b>
|
||||
<td width="10px"><b>Place</b>
|
||||
</td>
|
||||
<td><b>Team</b>
|
||||
</td>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
<div id="keys-pie-graph"></div>
|
||||
<div id="categories-pie-graph"></div>
|
||||
<div id="score-graph"></div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
@@ -36,7 +37,6 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="score-graph"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||
|
||||
<div class="row">
|
||||
<h1>Teams</h1>
|
||||
<br>
|
||||
<table id="teamsboard">
|
||||
<thead>
|
||||
<tr>
|
||||
<td width="10px"><b>ID</b></td>
|
||||
<td><b>Team</b></td>
|
||||
<td><b>Website</b></td>
|
||||
<td><b>Affiliation</b></td>
|
||||
@@ -17,7 +19,8 @@
|
||||
<tbody>
|
||||
{% for team in teams %}
|
||||
<tr>
|
||||
<td><a href="/team/{{ team.id }}">{{ team.name }}</a></td>
|
||||
<td class="team-id">{{ team.id }}</td>
|
||||
<td><a href="/team/{{ team.id }}">{{ team.name | truncate(32) }}</a></td>
|
||||
<td>{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %}</td>
|
||||
<td><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span></td>
|
||||
<td><span>{% if team.country %}{{ team.country }}{% endif %}</span></td>
|
||||
@@ -25,6 +28,15 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if team_pages > 1 %}
|
||||
<div class="text-center">Page
|
||||
<br>
|
||||
{% for page in range(1, team_pages + 1) %}
|
||||
<a href="/teams/{{ page }}">{{ page }}</a>
|
||||
{% endfor %}
|
||||
<a href="">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from CTFd.models import db, WrongKeys, Pages, Config
|
||||
from CTFd import mail
|
||||
|
||||
from urlparse import urlparse, urljoin
|
||||
from six.moves.urllib.parse import urlparse, urljoin
|
||||
from functools import wraps
|
||||
from flask import current_app as app, g, request, redirect, url_for, session, render_template
|
||||
from flask.ext.mail import Message
|
||||
@@ -140,7 +140,7 @@ def admins_only(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if session.get('admin', None) is None:
|
||||
return redirect(url_for('login', next=request.url))
|
||||
return redirect('/login')
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
@@ -159,23 +159,27 @@ def ctftime():
|
||||
|
||||
if start:
|
||||
start = int(start)
|
||||
else:
|
||||
start = 0
|
||||
if end:
|
||||
end = int(end)
|
||||
else:
|
||||
end = 0
|
||||
|
||||
if start and end:
|
||||
if start < time.time() < end:
|
||||
# Within the two time bounds
|
||||
return True
|
||||
|
||||
if start < time.time() and end is None:
|
||||
if start < time.time() and end == 0:
|
||||
# CTF starts on a date but never ends
|
||||
return True
|
||||
|
||||
if start is None and time.time() < end:
|
||||
if start == 0 and time.time() < end:
|
||||
# CTF started but ends at a date
|
||||
return True
|
||||
|
||||
if start is None and end is None:
|
||||
if start == 0 and end == 0:
|
||||
# CTF has no time requirements
|
||||
return True
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ def tracker():
|
||||
@views.before_request
|
||||
def csrf():
|
||||
if request.method == "POST":
|
||||
print(session)
|
||||
print(request.form.get('nonce'))
|
||||
if session['nonce'] != request.form.get('nonce'):
|
||||
abort(403)
|
||||
|
||||
@@ -62,6 +64,7 @@ def setup():
|
||||
password = request.form['password']
|
||||
admin = Teams(name, email, password)
|
||||
admin.admin = True
|
||||
admin.banned = True
|
||||
|
||||
## Index page
|
||||
html = request.form['html']
|
||||
@@ -96,6 +99,7 @@ def setup():
|
||||
db.session.commit()
|
||||
app.setup = False
|
||||
return redirect('/')
|
||||
print(session.get('nonce'))
|
||||
return render_template('setup.html', nonce=session.get('nonce'))
|
||||
return redirect('/')
|
||||
|
||||
@@ -120,11 +124,19 @@ def static_html(template):
|
||||
abort(404)
|
||||
|
||||
|
||||
@views.route('/teams')
|
||||
def teams():
|
||||
teams = Teams.query.all()
|
||||
return render_template('teams.html', teams=teams)
|
||||
@views.route('/teams', defaults={'page':'1'})
|
||||
@views.route('/teams/<page>')
|
||||
def teams(page):
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * ( page - 1 )
|
||||
page_end = results_per_page * ( page - 1 ) + results_per_page
|
||||
|
||||
teams = Teams.query.slice(page_start, page_end).all()
|
||||
count = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
print(count)
|
||||
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
||||
return render_template('teams.html', teams=teams, team_pages=pages)
|
||||
|
||||
@views.route('/team/<teamid>', methods=['GET', 'POST'])
|
||||
def team(teamid):
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
APScheduler==3.0.1
|
||||
Flask==0.10.1
|
||||
Flask-Mail==0.9.1
|
||||
Flask-SQLAlchemy==2.0
|
||||
Flask-Session==0.1.1
|
||||
SQLAlchemy==0.9.8
|
||||
passlib==1.6.2
|
||||
APScheduler
|
||||
Flask
|
||||
Flask-Mail
|
||||
Flask-SQLAlchemy
|
||||
Flask-Session
|
||||
SQLAlchemy
|
||||
passlib
|
||||
bcrypt
|
||||
six==1.8.0
|
||||
itsdangerous==0.24
|
||||
requests==2.3.0
|
||||
six
|
||||
itsdangerous
|
||||
requests
|
||||
PyMySQL
|
||||
|
||||
Reference in New Issue
Block a user