Skip to content

Commit 6b22572

Browse files
rbmjColdHeat
authored andcommitted
Allow CTFd to run with script_root != '/' and PostgreSQL (CTFd#125)
Also, Add WSGI config example
1 parent a9b7977 commit 6b22572

37 files changed

+291
-264
lines changed

CTFd/admin.py

+33-31
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from itsdangerous import TimedSerializer, BadTimeSignature
55
from sqlalchemy.sql import and_, or_, not_
66
from sqlalchemy.sql.expression import union_all
7+
from sqlalchemy.sql.functions import coalesce
78
from werkzeug.utils import secure_filename
89
from socket import inet_aton, inet_ntoa
910
from passlib.hash import bcrypt_sha256
@@ -18,6 +19,8 @@
1819
import datetime
1920
import calendar
2021

22+
from scoreboard import get_standings
23+
2124
admin = Blueprint('admin', __name__)
2225

2326

@@ -276,12 +279,12 @@ def delete_container(container_id):
276279
def new_container():
277280
name = request.form.get('name')
278281
if set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
279-
return redirect('/admin/containers')
282+
return redirect(url_for('admin.list_container'))
280283
buildfile = request.form.get('buildfile')
281284
files = request.files.getlist('files[]')
282285
create_image(name=name, buildfile=buildfile, files=files)
283286
run_image(name)
284-
return redirect('/admin/containers')
287+
return redirect(url_for('admin.list_container'))
285288

286289

287290

@@ -291,7 +294,7 @@ def admin_chals():
291294
if request.method == 'POST':
292295
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category', 'hidden').order_by(Challenges.value).all()
293296

294-
teams_with_points = db.session.query(Solves.teamid, Teams.name).join(Teams).filter(
297+
teams_with_points = db.session.query(Solves.teamid).join(Teams).filter(
295298
Teams.banned == False).group_by(
296299
Solves.teamid).count()
297300

@@ -427,7 +430,7 @@ def admin_teams(page):
427430
page_start = results_per_page * ( page - 1 )
428431
page_end = results_per_page * ( page - 1 ) + results_per_page
429432

430-
teams = Teams.query.slice(page_start, page_end).all()
433+
teams = Teams.query.order_by(Teams.id.asc()).slice(page_start, page_end).all()
431434
count = db.session.query(db.func.count(Teams.id)).first()[0]
432435
pages = int(count / results_per_page) + (count % results_per_page > 0)
433436
return render_template('admin/teams.html', teams=teams, pages=pages, curr_page=page)
@@ -442,7 +445,9 @@ def admin_team(teamid):
442445
solves = Solves.query.filter_by(teamid=teamid).all()
443446
solve_ids = [s.chalid for s in solves]
444447
missing = Challenges.query.filter( not_(Challenges.id.in_(solve_ids) ) ).all()
445-
addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all()
448+
addrs = db.session.query(Tracking.ip, db.func.max(Tracking.date)) \
449+
.filter_by(team=teamid) \
450+
.group_by(Tracking.ip).all()
446451
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.asc()).all()
447452
awards = Awards.query.filter_by(teamid=teamid).order_by(Awards.date.asc()).all()
448453
score = user.score()
@@ -452,10 +457,12 @@ def admin_team(teamid):
452457
elif request.method == 'POST':
453458
admin_user = request.form.get('admin', None)
454459
if admin_user:
455-
admin_user = 1 if admin_user == "true" else 0
460+
admin_user = True if admin_user == 'true' else False
456461
user.admin = admin_user
462+
# Set user.banned to hide admins from scoreboard
457463
user.banned = admin_user
458464
db.session.commit()
465+
db.session.close()
459466
return jsonify({'data': ['success']})
460467

461468
name = request.form.get('name', None)
@@ -545,35 +552,21 @@ def admin_graph(graph_type):
545552
json_data['categories'].append({'category':category, 'count':count})
546553
return jsonify(json_data)
547554
elif graph_type == "solves":
548-
solves = Solves.query.join(Teams).filter(Teams.banned == False).add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
555+
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \
556+
.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \
557+
.group_by(Solves.chalid).subquery()
558+
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \
559+
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
549560
json_data = {}
550-
for chal, count in solves:
551-
json_data[chal.chal.name] = count
561+
for chal, count, name in solves:
562+
json_data[name] = count
552563
return jsonify(json_data)
553564

554565

555566
@admin.route('/admin/scoreboard')
556567
@admins_only
557568
def admin_scoreboard():
558-
score = db.func.sum(Challenges.value).label('score')
559-
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), Teams.banned.label('banned'), score, Solves.date.label('date')) \
560-
.join(Teams) \
561-
.join(Challenges) \
562-
.group_by(Solves.teamid)
563-
564-
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), Teams.banned.label('banned'),
565-
db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \
566-
.filter(Teams.id == Awards.teamid) \
567-
.group_by(Teams.id)
568-
569-
results = union_all(scores, awards).alias('results')
570-
571-
standings = db.session.query(results.columns.teamid, results.columns.name, results.columns.banned,
572-
db.func.sum(results.columns.score).label('score')) \
573-
.group_by(results.columns.teamid) \
574-
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \
575-
.all()
576-
db.session.close()
569+
standings = get_standings(admin=True)
577570
return render_template('admin/scoreboard.html', teams=standings)
578571

579572

@@ -711,9 +704,18 @@ def admin_stats():
711704
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
712705
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
713706
challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0]
714-
most_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves DESC').first()
715-
least_solved_chal = Challenges.query.add_columns(db.func.count(Solves.chalid).label('solves')).outerjoin(Solves).group_by(Challenges.id).order_by('solves ASC').first()
716-
707+
708+
solves_raw = db.func.count(Solves.chalid).label('solves_raw')
709+
solves_sub = db.session.query(Solves.chalid, solves_raw) \
710+
.group_by(Solves.chalid).subquery()
711+
solves_cnt = coalesce(solves_sub.columns.solves_raw, 0).label('solves_cnt')
712+
most_solved_chal = Challenges.query.add_columns(solves_cnt) \
713+
.outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \
714+
.order_by(solves_cnt.desc()).first()
715+
least_solved_chal = Challenges.query.add_columns(solves_cnt) \
716+
.outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \
717+
.order_by(solves_cnt.asc()).first()
718+
717719
db.session.close()
718720

719721
return render_template('admin/statistics.html', team_count=teams_registered,

CTFd/auth.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,4 @@ def login():
172172
def logout():
173173
if authed():
174174
session.clear()
175-
return redirect('/')
175+
return redirect(url_for('views.static_html'))

CTFd/challenges.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def challenges_view():
2020
if view_after_ctf():
2121
pass
2222
else:
23-
return redirect('/')
23+
return redirect(url_for('views.static_html'))
2424
if get_config('verify_emails') and not is_verified():
2525
return redirect(url_for('auth.confirm_user'))
2626
if can_view_challenges():
@@ -36,7 +36,7 @@ def chals():
3636
if view_after_ctf():
3737
pass
3838
else:
39-
return redirect('/')
39+
return redirect(url_for('views.static_html'))
4040
if can_view_challenges():
4141
chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
4242

@@ -56,10 +56,13 @@ def chals():
5656
@challenges.route('/chals/solves')
5757
def chals_per_solves():
5858
if can_view_challenges():
59-
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
59+
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves')).join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).group_by(Solves.chalid).subquery()
60+
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \
61+
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
6062
json = {}
61-
for chal, count in solves:
62-
json[chal.chal.name] = count
63+
for chal, count, name in solves:
64+
json[name] = count
65+
db.session.close()
6366
return jsonify(json)
6467
return redirect(url_for('auth.login', next='chals/solves'))
6568

CTFd/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class Solves(db.Model):
180180
id = db.Column(db.Integer, primary_key=True)
181181
chalid = db.Column(db.Integer, db.ForeignKey('challenges.id'))
182182
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
183-
ip = db.Column(db.Integer)
183+
ip = db.Column(db.BigInteger)
184184
flag = db.Column(db.Text)
185185
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
186186
team = db.relationship('Teams', foreign_keys="Solves.teamid", lazy='joined')

CTFd/scoreboard.py

+28-59
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,45 @@
55

66
scoreboard = Blueprint('scoreboard', __name__)
77

8+
def get_standings(admin=False, count=None):
9+
score = db.func.sum(Challenges.value).label('score')
10+
date = db.func.max(Solves.date).label('date')
11+
scores = db.session.query(Solves.teamid.label('teamid'), score, date).join(Challenges).group_by(Solves.teamid)
12+
awards = db.session.query(Awards.teamid.label('teamid'), db.func.sum(Awards.value).label('score'), db.func.max(Awards.date).label('date')) \
13+
.group_by(Awards.teamid)
14+
results = union_all(scores, awards).alias('results')
15+
sumscores = db.session.query(results.columns.teamid, db.func.sum(results.columns.score).label('score'), db.func.max(results.columns.date).label('date')) \
16+
.group_by(results.columns.teamid).subquery()
17+
if admin:
18+
standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), Teams.banned, sumscores.columns.score) \
19+
.join(sumscores, Teams.id == sumscores.columns.teamid) \
20+
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
21+
else:
22+
standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), sumscores.columns.score) \
23+
.join(sumscores, Teams.id == sumscores.columns.teamid) \
24+
.filter(Teams.banned == False) \
25+
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
26+
if count is not None:
27+
standings = standings_query.all()
28+
else:
29+
standings = standings_query.limit(count).all()
30+
db.session.close()
31+
return standings
32+
833

934
@scoreboard.route('/scoreboard')
1035
def scoreboard_view():
1136
if get_config('view_scoreboard_if_authed') and not authed():
1237
return redirect(url_for('auth.login', next=request.path))
13-
score = db.func.sum(Challenges.value).label('score')
14-
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \
15-
.join(Teams) \
16-
.join(Challenges) \
17-
.filter(Teams.banned == False) \
18-
.group_by(Solves.teamid)
19-
20-
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'),
21-
db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \
22-
.filter(Teams.id == Awards.teamid) \
23-
.group_by(Teams.id)
24-
25-
results = union_all(scores, awards).alias('results')
26-
27-
standings = db.session.query(results.columns.teamid, results.columns.name,
28-
db.func.sum(results.columns.score).label('score')) \
29-
.group_by(results.columns.teamid) \
30-
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \
31-
.all()
32-
db.session.close()
38+
standings = get_standings()
3339
return render_template('scoreboard.html', teams=standings)
3440

3541

3642
@scoreboard.route('/scores')
3743
def scores():
3844
if get_config('view_scoreboard_if_authed') and not authed():
3945
return redirect(url_for('auth.login', next=request.path))
40-
score = db.func.sum(Challenges.value).label('score')
41-
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \
42-
.join(Teams) \
43-
.join(Challenges) \
44-
.filter(Teams.banned == False) \
45-
.group_by(Solves.teamid)
46-
47-
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), db.func.sum(Awards.value).label('score'), Awards.date.label('date'))\
48-
.filter(Teams.id==Awards.teamid)\
49-
.group_by(Teams.id)
50-
51-
results = union_all(scores, awards).alias('results')
52-
53-
standings = db.session.query(results.columns.teamid, results.columns.name, db.func.sum(results.columns.score).label('score'))\
54-
.group_by(results.columns.teamid)\
55-
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date))\
56-
.all()
57-
58-
db.session.close()
46+
standings = get_standings()
5947
json = {'standings':[]}
6048
for i, x in enumerate(standings):
6149
json['standings'].append({'pos':i+1, 'id':x.teamid, 'team':x.name,'score':int(x.score)})
@@ -74,26 +62,7 @@ def topteams(count):
7462
count = 10
7563

7664
json = {'scores':{}}
77-
78-
score = db.func.sum(Challenges.value).label('score')
79-
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \
80-
.join(Teams) \
81-
.join(Challenges) \
82-
.filter(Teams.banned == False) \
83-
.group_by(Solves.teamid)
84-
85-
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'),
86-
db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \
87-
.filter(Teams.id == Awards.teamid) \
88-
.group_by(Teams.id)
89-
90-
results = union_all(scores, awards).alias('results')
91-
92-
standings = db.session.query(results.columns.teamid, results.columns.name,
93-
db.func.sum(results.columns.score).label('score')) \
94-
.group_by(results.columns.teamid) \
95-
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \
96-
.limit(count).all()
65+
standings = get_standings(count=count)
9766

9867
for team in standings:
9968
solves = Solves.query.filter_by(teamid=team.teamid).all()

CTFd/static/admin/js/chalboard.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function loadchal(id, update) {
4646
}
4747

4848
function submitkey(chal, key) {
49-
$.post("/admin/chal/" + chal, {
49+
$.post(script_root + "/admin/chal/" + chal, {
5050
key: key,
5151
nonce: $('#nonce').val()
5252
}, function (data) {
@@ -55,7 +55,7 @@ function submitkey(chal, key) {
5555
}
5656

5757
function loadkeys(chal){
58-
$.get('/admin/keys/' + chal, function(data){
58+
$.get(script_root + '/admin/keys/' + chal, function(data){
5959
$('#keys-chal').val(chal);
6060
keys = $.parseJSON(JSON.stringify(data));
6161
keys = keys['keys'];
@@ -84,7 +84,7 @@ function updatekeys(){
8484
$('#current-keys input[name*="key_type"]:checked').each(function(){
8585
vals.push($(this).val());
8686
})
87-
$.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
87+
$.post(script_root + '/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
8888
loadchal(chal, true)
8989
$('#update-keys').modal('hide');
9090
}
@@ -93,7 +93,7 @@ function loadtags(chal){
9393
$('#tags-chal').val(chal)
9494
$('#current-tags').empty()
9595
$('#chal-tags').empty()
96-
$.get('/admin/tags/'+chal, function(data){
96+
$.get(script_root + '/admin/tags/'+chal, function(data){
9797
tags = $.parseJSON(JSON.stringify(data))
9898
tags = tags['tags']
9999
for (var i = 0; i < tags.length; i++) {
@@ -108,11 +108,11 @@ function loadtags(chal){
108108
}
109109

110110
function deletetag(tagid){
111-
$.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
111+
$.post(script_root + '/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
112112
}
113113

114114
function deletechal(chalid){
115-
$.post('/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
115+
$.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
116116
}
117117

118118
function updatetags(){
@@ -121,13 +121,13 @@ function updatetags(){
121121
$('#chal-tags > span > span').each(function(i, e){
122122
tags.push($(e).text())
123123
});
124-
$.post('/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()})
124+
$.post(script_root + '/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()})
125125
loadchal(chal)
126126
}
127127

128128
function loadfiles(chal){
129-
$('#update-files form').attr('action', '/admin/files/'+chal)
130-
$.get('/admin/files/' + chal, function(data){
129+
$('#update-files form').attr('action', script_root+'/admin/files/'+chal)
130+
$.get(script_root + '/admin/files/' + chal, function(data){
131131
$('#files-chal').val(chal)
132132
files = $.parseJSON(JSON.stringify(data));
133133
files = files['files']
@@ -141,7 +141,7 @@ function loadfiles(chal){
141141
}
142142

143143
function deletefile(chal, file, elem){
144-
$.post('/admin/files/' + chal,{
144+
$.post(script_root + '/admin/files/' + chal,{
145145
'nonce': $('#nonce').val(),
146146
'method': 'delete',
147147
'file': file
@@ -155,7 +155,7 @@ function deletefile(chal, file, elem){
155155

156156
function loadchals(){
157157
$('#challenges').empty();
158-
$.post("/admin/chals", {
158+
$.post(script_root + "/admin/chals", {
159159
'nonce': $('#nonce').val()
160160
}, function (data) {
161161
categories = [];
@@ -207,7 +207,7 @@ $('#submit-tags').click(function (e) {
207207

208208
$('#delete-chal form').submit(function(e){
209209
e.preventDefault();
210-
$.post('/admin/chal/delete', $(this).serialize(), function(data){
210+
$.post(script_root + '/admin/chal/delete', $(this).serialize(), function(data){
211211
console.log(data)
212212
if (data){
213213
loadchals();

0 commit comments

Comments
 (0)