Allow CTFd to run with script_root != '/' and PostgreSQL (#125)

Also, Add WSGI config example
selenium-screenshot-testing
Robert Blair Mason Jr 2016-06-22 00:18:09 -04:00 committed by Kevin Chung
parent a9b79770f8
commit 6b2257236f
37 changed files with 291 additions and 264 deletions

View File

@ -4,6 +4,7 @@ from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, Wrong
from itsdangerous import TimedSerializer, BadTimeSignature
from sqlalchemy.sql import and_, or_, not_
from sqlalchemy.sql.expression import union_all
from sqlalchemy.sql.functions import coalesce
from werkzeug.utils import secure_filename
from socket import inet_aton, inet_ntoa
from passlib.hash import bcrypt_sha256
@ -18,6 +19,8 @@ import json
import datetime
import calendar
from scoreboard import get_standings
admin = Blueprint('admin', __name__)
@ -276,12 +279,12 @@ def delete_container(container_id):
def new_container():
name = request.form.get('name')
if set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
return redirect('/admin/containers')
return redirect(url_for('admin.list_container'))
buildfile = request.form.get('buildfile')
files = request.files.getlist('files[]')
create_image(name=name, buildfile=buildfile, files=files)
run_image(name)
return redirect('/admin/containers')
return redirect(url_for('admin.list_container'))
@ -291,7 +294,7 @@ def admin_chals():
if request.method == 'POST':
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category', 'hidden').order_by(Challenges.value).all()
teams_with_points = db.session.query(Solves.teamid, Teams.name).join(Teams).filter(
teams_with_points = db.session.query(Solves.teamid).join(Teams).filter(
Teams.banned == False).group_by(
Solves.teamid).count()
@ -427,7 +430,7 @@ def admin_teams(page):
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()
teams = Teams.query.order_by(Teams.id.asc()).slice(page_start, page_end).all()
count = db.session.query(db.func.count(Teams.id)).first()[0]
pages = int(count / results_per_page) + (count % results_per_page > 0)
return render_template('admin/teams.html', teams=teams, pages=pages, curr_page=page)
@ -442,7 +445,9 @@ def admin_team(teamid):
solves = Solves.query.filter_by(teamid=teamid).all()
solve_ids = [s.chalid for s in solves]
missing = Challenges.query.filter( not_(Challenges.id.in_(solve_ids) ) ).all()
addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all()
addrs = db.session.query(Tracking.ip, db.func.max(Tracking.date)) \
.filter_by(team=teamid) \
.group_by(Tracking.ip).all()
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.asc()).all()
awards = Awards.query.filter_by(teamid=teamid).order_by(Awards.date.asc()).all()
score = user.score()
@ -452,10 +457,12 @@ def admin_team(teamid):
elif request.method == 'POST':
admin_user = request.form.get('admin', None)
if admin_user:
admin_user = 1 if admin_user == "true" else 0
admin_user = True if admin_user == 'true' else False
user.admin = admin_user
# Set user.banned to hide admins from scoreboard
user.banned = admin_user
db.session.commit()
db.session.close()
return jsonify({'data': ['success']})
name = request.form.get('name', None)
@ -545,35 +552,21 @@ def admin_graph(graph_type):
json_data['categories'].append({'category':category, 'count':count})
return jsonify(json_data)
elif graph_type == "solves":
solves = Solves.query.join(Teams).filter(Teams.banned == False).add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \
.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \
.group_by(Solves.chalid).subquery()
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
json_data = {}
for chal, count in solves:
json_data[chal.chal.name] = count
for chal, count, name in solves:
json_data[name] = count
return jsonify(json_data)
@admin.route('/admin/scoreboard')
@admins_only
def admin_scoreboard():
score = db.func.sum(Challenges.value).label('score')
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), Teams.banned.label('banned'), score, Solves.date.label('date')) \
.join(Teams) \
.join(Challenges) \
.group_by(Solves.teamid)
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), Teams.banned.label('banned'),
db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \
.filter(Teams.id == Awards.teamid) \
.group_by(Teams.id)
results = union_all(scores, awards).alias('results')
standings = db.session.query(results.columns.teamid, results.columns.name, results.columns.banned,
db.func.sum(results.columns.score).label('score')) \
.group_by(results.columns.teamid) \
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \
.all()
db.session.close()
standings = get_standings(admin=True)
return render_template('admin/scoreboard.html', teams=standings)
@ -711,9 +704,18 @@ def admin_stats():
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0]
most_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves DESC').first()
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()
solves_raw = db.func.count(Solves.chalid).label('solves_raw')
solves_sub = db.session.query(Solves.chalid, solves_raw) \
.group_by(Solves.chalid).subquery()
solves_cnt = coalesce(solves_sub.columns.solves_raw, 0).label('solves_cnt')
most_solved_chal = Challenges.query.add_columns(solves_cnt) \
.outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \
.order_by(solves_cnt.desc()).first()
least_solved_chal = Challenges.query.add_columns(solves_cnt) \
.outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \
.order_by(solves_cnt.asc()).first()
db.session.close()
return render_template('admin/statistics.html', team_count=teams_registered,

View File

@ -172,4 +172,4 @@ def login():
def logout():
if authed():
session.clear()
return redirect('/')
return redirect(url_for('views.static_html'))

View File

@ -20,7 +20,7 @@ def challenges_view():
if view_after_ctf():
pass
else:
return redirect('/')
return redirect(url_for('views.static_html'))
if get_config('verify_emails') and not is_verified():
return redirect(url_for('auth.confirm_user'))
if can_view_challenges():
@ -36,7 +36,7 @@ def chals():
if view_after_ctf():
pass
else:
return redirect('/')
return redirect(url_for('views.static_html'))
if can_view_challenges():
chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
@ -56,10 +56,13 @@ def chals():
@challenges.route('/chals/solves')
def chals_per_solves():
if can_view_challenges():
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()
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()
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
json = {}
for chal, count in solves:
json[chal.chal.name] = count
for chal, count, name in solves:
json[name] = count
db.session.close()
return jsonify(json)
return redirect(url_for('auth.login', next='chals/solves'))

View File

@ -180,7 +180,7 @@ class Solves(db.Model):
id = db.Column(db.Integer, primary_key=True)
chalid = db.Column(db.Integer, db.ForeignKey('challenges.id'))
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
ip = db.Column(db.Integer)
ip = db.Column(db.BigInteger)
flag = db.Column(db.Text)
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
team = db.relationship('Teams', foreign_keys="Solves.teamid", lazy='joined')

View File

@ -5,31 +5,37 @@ from sqlalchemy.sql.expression import union_all
scoreboard = Blueprint('scoreboard', __name__)
def get_standings(admin=False, count=None):
score = db.func.sum(Challenges.value).label('score')
date = db.func.max(Solves.date).label('date')
scores = db.session.query(Solves.teamid.label('teamid'), score, date).join(Challenges).group_by(Solves.teamid)
awards = db.session.query(Awards.teamid.label('teamid'), db.func.sum(Awards.value).label('score'), db.func.max(Awards.date).label('date')) \
.group_by(Awards.teamid)
results = union_all(scores, awards).alias('results')
sumscores = db.session.query(results.columns.teamid, db.func.sum(results.columns.score).label('score'), db.func.max(results.columns.date).label('date')) \
.group_by(results.columns.teamid).subquery()
if admin:
standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), Teams.banned, sumscores.columns.score) \
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
else:
standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), sumscores.columns.score) \
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.filter(Teams.banned == False) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
if count is not None:
standings = standings_query.all()
else:
standings = standings_query.limit(count).all()
db.session.close()
return standings
@scoreboard.route('/scoreboard')
def scoreboard_view():
if get_config('view_scoreboard_if_authed') and not authed():
return redirect(url_for('auth.login', next=request.path))
score = db.func.sum(Challenges.value).label('score')
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \
.join(Teams) \
.join(Challenges) \
.filter(Teams.banned == False) \
.group_by(Solves.teamid)
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'),
db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \
.filter(Teams.id == Awards.teamid) \
.group_by(Teams.id)
results = union_all(scores, awards).alias('results')
standings = db.session.query(results.columns.teamid, results.columns.name,
db.func.sum(results.columns.score).label('score')) \
.group_by(results.columns.teamid) \
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \
.all()
db.session.close()
standings = get_standings()
return render_template('scoreboard.html', teams=standings)
@ -37,25 +43,7 @@ def scoreboard_view():
def scores():
if get_config('view_scoreboard_if_authed') and not authed():
return redirect(url_for('auth.login', next=request.path))
score = db.func.sum(Challenges.value).label('score')
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \
.join(Teams) \
.join(Challenges) \
.filter(Teams.banned == False) \
.group_by(Solves.teamid)
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), db.func.sum(Awards.value).label('score'), Awards.date.label('date'))\
.filter(Teams.id==Awards.teamid)\
.group_by(Teams.id)
results = union_all(scores, awards).alias('results')
standings = db.session.query(results.columns.teamid, results.columns.name, db.func.sum(results.columns.score).label('score'))\
.group_by(results.columns.teamid)\
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date))\
.all()
db.session.close()
standings = get_standings()
json = {'standings':[]}
for i, x in enumerate(standings):
json['standings'].append({'pos':i+1, 'id':x.teamid, 'team':x.name,'score':int(x.score)})
@ -74,26 +62,7 @@ def topteams(count):
count = 10
json = {'scores':{}}
score = db.func.sum(Challenges.value).label('score')
scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \
.join(Teams) \
.join(Challenges) \
.filter(Teams.banned == False) \
.group_by(Solves.teamid)
awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'),
db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \
.filter(Teams.id == Awards.teamid) \
.group_by(Teams.id)
results = union_all(scores, awards).alias('results')
standings = db.session.query(results.columns.teamid, results.columns.name,
db.func.sum(results.columns.score).label('score')) \
.group_by(results.columns.teamid) \
.order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \
.limit(count).all()
standings = get_standings(count=count)
for team in standings:
solves = Solves.query.filter_by(teamid=team.teamid).all()

View File

@ -46,7 +46,7 @@ function loadchal(id, update) {
}
function submitkey(chal, key) {
$.post("/admin/chal/" + chal, {
$.post(script_root + "/admin/chal/" + chal, {
key: key,
nonce: $('#nonce').val()
}, function (data) {
@ -55,7 +55,7 @@ function submitkey(chal, key) {
}
function loadkeys(chal){
$.get('/admin/keys/' + chal, function(data){
$.get(script_root + '/admin/keys/' + chal, function(data){
$('#keys-chal').val(chal);
keys = $.parseJSON(JSON.stringify(data));
keys = keys['keys'];
@ -84,7 +84,7 @@ function updatekeys(){
$('#current-keys input[name*="key_type"]:checked').each(function(){
vals.push($(this).val());
})
$.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
$.post(script_root + '/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
loadchal(chal, true)
$('#update-keys').modal('hide');
}
@ -93,7 +93,7 @@ function loadtags(chal){
$('#tags-chal').val(chal)
$('#current-tags').empty()
$('#chal-tags').empty()
$.get('/admin/tags/'+chal, function(data){
$.get(script_root + '/admin/tags/'+chal, function(data){
tags = $.parseJSON(JSON.stringify(data))
tags = tags['tags']
for (var i = 0; i < tags.length; i++) {
@ -108,11 +108,11 @@ function loadtags(chal){
}
function deletetag(tagid){
$.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
$.post(script_root + '/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
}
function deletechal(chalid){
$.post('/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
$.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
}
function updatetags(){
@ -121,13 +121,13 @@ function updatetags(){
$('#chal-tags > span > span').each(function(i, e){
tags.push($(e).text())
});
$.post('/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()})
$.post(script_root + '/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()})
loadchal(chal)
}
function loadfiles(chal){
$('#update-files form').attr('action', '/admin/files/'+chal)
$.get('/admin/files/' + chal, function(data){
$('#update-files form').attr('action', script_root+'/admin/files/'+chal)
$.get(script_root + '/admin/files/' + chal, function(data){
$('#files-chal').val(chal)
files = $.parseJSON(JSON.stringify(data));
files = files['files']
@ -141,7 +141,7 @@ function loadfiles(chal){
}
function deletefile(chal, file, elem){
$.post('/admin/files/' + chal,{
$.post(script_root + '/admin/files/' + chal,{
'nonce': $('#nonce').val(),
'method': 'delete',
'file': file
@ -155,7 +155,7 @@ function deletefile(chal, file, elem){
function loadchals(){
$('#challenges').empty();
$.post("/admin/chals", {
$.post(script_root + "/admin/chals", {
'nonce': $('#nonce').val()
}, function (data) {
categories = [];
@ -207,7 +207,7 @@ $('#submit-tags').click(function (e) {
$('#delete-chal form').submit(function(e){
e.preventDefault();
$.post('/admin/chal/delete', $(this).serialize(), function(data){
$.post(script_root + '/admin/chal/delete', $(this).serialize(), function(data){
console.log(data)
if (data){
loadchals();

View File

@ -16,7 +16,7 @@ function scoregraph () {
var times = []
var scores = []
var teamname = $('#team-id').text()
$.get('/admin/solves/'+teamid(), function( data ) {
$.get(script_root + '/admin/solves/'+teamid(), function( data ) {
var solves = $.parseJSON(JSON.stringify(data));
solves = solves['solves'];
@ -48,7 +48,7 @@ function scoregraph () {
function keys_percentage_graph(){
// Solves and Fails pie chart
$.get('/admin/fails/'+teamid(), function(data){
$.get(script_root + '/admin/fails/'+teamid(), function(data){
var res = $.parseJSON(JSON.stringify(data));
var solves = res['solves'];
var fails = res['fails'];
@ -75,7 +75,7 @@ function keys_percentage_graph(){
}
function category_breakdown_graph(){
$.get('/admin/solves/'+teamid(), function(data){
$.get(script_root + '/admin/solves/'+teamid(), function(data){
var solves = $.parseJSON(JSON.stringify(data));
solves = solves['solves'];
@ -126,4 +126,4 @@ window.onresize = function () {
Plotly.Plots.resize(document.getElementById('keys-pie-graph'));
Plotly.Plots.resize(document.getElementById('categories-pie-graph'));
Plotly.Plots.resize(document.getElementById('score-graph'));
};
};

View File

@ -58,7 +58,7 @@ $("#answer-input").keyup(function(event){
function submitkey(chal, key, nonce) {
$('#submit-key').addClass("disabled-button");
$('#submit-key').prop('disabled', true);
$.post("/chal/" + chal, {
$.post(script_root + "/chal/" + chal, {
key: key,
nonce: nonce
}, function (data) {
@ -103,7 +103,7 @@ function submitkey(chal, key, nonce) {
}
function marksolves() {
$.get('/solves', function (data) {
$.get(script_root + '/solves', function (data) {
solves = $.parseJSON(JSON.stringify(data));
for (var i = solves['solves'].length - 1; i >= 0; i--) {
id = solves['solves'][i].chalid;
@ -118,7 +118,7 @@ function marksolves() {
}
function updatesolves(){
$.get('/chals/solves', function (data) {
$.get(script_root + '/chals/solves', function (data) {
solves = $.parseJSON(JSON.stringify(data));
chals = Object.keys(solves);
@ -133,7 +133,7 @@ function updatesolves(){
}
function getsolves(id){
$.get('/chal/'+id+'/solves', function (data) {
$.get(script_root + '/chal/'+id+'/solves', function (data) {
var teams = data['teams'];
var box = $('#chal-solves-names');
box.empty();
@ -147,8 +147,7 @@ function getsolves(id){
}
function loadchals() {
$.get("/chals", function (data) {
$.get(script_root + "/chals", function (data) {
var categories = [];
challenges = $.parseJSON(JSON.stringify(data));

View File

@ -1,5 +1,5 @@
function updatescores () {
$.get('/scores', function( data ) {
$.get(script_root + '/scores', function( data ) {
teams = $.parseJSON(JSON.stringify(data));
$('#scoreboard > tbody').empty()
for (var i = 0; i < teams['standings'].length; i++) {
@ -23,7 +23,7 @@ function UTCtoDate(utc){
return d;
}
function scoregraph () {
$.get('/top/10', function( data ) {
$.get(script_root + '/top/10', function( data ) {
var scores = $.parseJSON(JSON.stringify(data));
scores = scores['scores'];
if (Object.keys(scores).length == 0 ){
@ -72,4 +72,4 @@ scoregraph();
window.onresize = function () {
Plotly.Plots.resize(document.getElementById('score-graph'));
};
};

View File

@ -25,7 +25,7 @@ function scoregraph() {
var times = []
var scores = []
var teamname = $('#team-id').text()
$.get('/solves/' + teamid(), function (data) {
$.get(script_root + '/solves/' + teamid(), function (data) {
var solves = $.parseJSON(JSON.stringify(data));
solves = solves['solves'];
@ -57,7 +57,7 @@ function scoregraph() {
function keys_percentage_graph() {
// Solves and Fails pie chart
$.get('/fails/' + teamid(), function (data) {
$.get(script_root + '/fails/' + teamid(), function (data) {
var res = $.parseJSON(JSON.stringify(data));
var solves = res['solves'];
var fails = res['fails'];
@ -85,7 +85,7 @@ function keys_percentage_graph() {
}
function category_breakdown_graph() {
$.get('/solves/' + teamid(), function (data) {
$.get(script_root + '/solves/' + teamid(), function (data) {
var solves = $.parseJSON(JSON.stringify(data));
solves = solves['solves'];
@ -136,4 +136,4 @@ window.onresize = function () {
Plotly.Plots.resize(document.getElementById('keys-pie-graph'));
Plotly.Plots.resize(document.getElementById('categories-pie-graph'));
Plotly.Plots.resize(document.getElementById('score-graph'));
};
};

View File

@ -52,4 +52,4 @@ function colorhash (x) {
function htmlentities(string) {
return $('<div/>').text(string).html();
}
}

View File

@ -5,15 +5,18 @@
<title>Admin Panel</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/static/img/favicon.ico" type="image/x-icon">
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/static/css/vendor/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/vendor/font-awesome/css/font-awesome.min.css" />
<link rel="stylesheet" href="/static/css/style.css">
<link href='/static/css/vendor/lato.css' rel='stylesheet' type='text/css'>
<link href='/static/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="/static/admin/css/style.css">
<script src="/static/js/vendor/moment.min.js"></script>
<link rel="shortcut icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
<link rel="icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/bootstrap.min.css">
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/font-awesome/css/font-awesome.min.css" />
<link rel="stylesheet" href="{{ request.script_root }}/static/css/style.css">
<link href='{{ request.script_root }}/static/css/vendor/lato.css' rel='stylesheet' type='text/css'>
<link href='{{ request.script_root }}/static/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/admin/css/style.css">
<script src="{{ request.script_root }}/static/js/vendor/moment.min.js"></script>
<script type="text/javascript">
var script_root = "{{ request.script_root }}";
</script>
{% block stylesheets %} {% endblock %}
</head>
@ -27,20 +30,20 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/" class="navbar-brand">CTFd</a>
<a href="{{ request.script_root }}/" class="navbar-brand">CTFd</a>
</div>
<div class="navbar-collapse collapse" aria-expanded="false" style="height: 0px">
<ul class="nav navbar-nav navbar-nav-right">
<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="{{ request.script_root }}/admin/graphs">Graphs</a></li>
<li><a href="{{ request.script_root }}/admin/pages">Pages</a></li>
<li><a href="{{ request.script_root }}/admin/teams">Teams</a></li>
<li><a href="{{ request.script_root }}/admin/scoreboard">Scoreboard</a></li>
{% if can_create_container() %}
<li><a href="/admin/containers">Containers</a></li>
<li><a href="{{ request.script_root }}/admin/containers">Containers</a></li>
{% endif %}
<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="{{ request.script_root }}/admin/chals">Challenges</a></li>
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
</ul>
</div>
</div>
@ -50,9 +53,9 @@
{% block content %} {% endblock %}
</div>
</div>
<script src="/static/js/vendor/jquery.min.js"></script>
<script src="/static/js/vendor/marked.min.js"></script>
<script src="/static/js/vendor/bootstrap.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/jquery.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/marked.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/bootstrap.min.js"></script>
{% block scripts %} {% endblock %}
</body>

View File

@ -31,7 +31,7 @@
<h3>New Challenge</h3>
</div>
<div class="modal-body">
<form method="POST" action="/admin/chal/new" enctype="multipart/form-data">
<form method="POST" action="{{ request.script_root }}/admin/chal/new" enctype="multipart/form-data">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" name="name" placeholder="Enter challenge name">
@ -105,7 +105,7 @@
<h3 class="chal-title text-center"></h3>
</div>
<div class="modal-body">
<form method="POST" action="/admin/chal/update">
<form method="POST" action="{{ request.script_root }}/admin/chal/update">
<input name='nonce' type='hidden' value="{{ nonce }}">
<div class="form-group">
@ -168,7 +168,7 @@
<h3>Delete Challenge</h3>
</div>
<div class="modal-body">
<form method="POST" action="/admin/chal/delete">
<form method="POST" action="{{ request.script_root }}/admin/chal/delete">
<input type="hidden" name="nonce" value="{{ nonce }}">
<input type="hidden" name="id" class="chal-id">
<div class="small-6 small-centered text-center columns">
@ -190,7 +190,7 @@
<h3>Keys</h3>
</div>
<div class="modal-body">
<form method="POST" action="/admin/keys" style="text-align:center">
<form method="POST" action="{{ request.script_root }}/admin/keys" style="text-align:center">
<a href="#" id="create-key" class="btn btn-primary" style="margin-bottom:15px;">New Key</a>
<input name='nonce' type='hidden' value="{{ nonce }}">
<input id="keys-chal" name='chal' type='hidden'>
@ -212,7 +212,7 @@
<h3>Files</h3>
</div>
<div class="modal-body">
<form method="POST" action="/admin/files/" enctype="multipart/form-data">
<form method="POST" action="{{ request.script_root }}/admin/files/" enctype="multipart/form-data">
<input name='nonce' type='hidden' value="{{ nonce }}">
<input id="files-chal" name='chal' type='hidden'>
<input name='method' type='hidden' value='upload'>
@ -298,7 +298,7 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/utils.js"></script>
<script src="/static/admin/js/multi-modal.js"></script>
<script src="/static/admin/js/chalboard.js"></script>
<script src="{{ request.script_root }}/static/js/utils.js"></script>
<script src="{{ request.script_root }}/static/admin/js/multi-modal.js"></script>
<script src="{{ request.script_root }}/static/admin/js/chalboard.js"></script>
{% endblock %}

View File

@ -9,7 +9,7 @@
aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="container-modal-label">Create Container</h4>
</div>
<form method="POST" action="/admin/containers/new" enctype="multipart/form-data">
<form method="POST" action="{{ request.script_root }}/admin/containers/new" enctype="multipart/form-data">
<div class="modal-body">
<div class="form-group">
<label for="name">Name</label>

View File

@ -49,7 +49,7 @@
<tbody>
{% for solve in solves %}
<tr>
<td class="text-center team" id="{{ solve.teamid }}"><a href="/admin/team/{{ solve.teamid }}">{{ solve.team_name }}</a>
<td class="text-center team" id="{{ solve.teamid }}"><a href="{{ request.script_root }}/admin/team/{{ solve.teamid }}">{{ solve.team_name }}</a>
<td class="text-center chal" id="{{ solve.chalid }}">{{ solve.chal_name }}</td>
<td class="text-center solve-time"><script>document.write( moment({{ solve.date|unix_time_millis }}).local().format('MMMM Do, h:mm:ss A'))</script></td>
<td class="text-center">{{ solve.flag }}</td>
@ -61,24 +61,24 @@
{% if pages > 1 %}
<div class="text-center">Page
<br>
{% if curr_page != 1 %}<a href="/admin/correct_keys/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% if curr_page != 1 %}<a href="{{ request.script_root }}/admin/correct_keys/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% for page in range(1, pages + 1) %}
{% if curr_page != page %}
<a href="/admin/correct_keys/{{ page }}">{{ page }}</a>
<a href="{{ request.script_root }}/admin/correct_keys/{{ page }}">{{ page }}</a>
{% else %}
<b>{{ page }}</b>
{% endif %}
{% endfor %}
{% if curr_page != pages %}<a href="/admin/correct_keys/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
<a href="">
{% if curr_page != pages %}<a href="{{ request.script_root }}/admin/correct_keys/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
<a href="{{ request.script_root }}">
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/utils.js"></script>
<script src="/static/admin/js/team.js"></script>
<script src="{{ request.script_root }}/static/js/utils.js"></script>
<script src="{{ request.script_root }}/static/admin/js/team.js"></script>
<script>
$('#delete-solve').click(function(e){
e.preventDefault();
@ -95,7 +95,7 @@
var modal = $('#confirm')
modal.find('#confirm-team-name').text(team_name)
modal.find('#confirm-chal-name').text(chal_name)
$('#confirm form').attr('action', '/admin/solves/'+team+'/'+chal+'/delete');
$('#confirm form').attr('action', '{{ request.script_root }}/admin/solves/'+team+'/'+chal+'/delete');
$('#confirm').modal('show');
}

View File

@ -1,7 +1,7 @@
{% extends "admin/base.html" %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/static/css/vendor/codemirror.min.css">
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/css/vendor/codemirror.min.css">
<style>
.row-fluid { margin: 25px; padding-bottom: 25px; }
</style>
@ -38,7 +38,7 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/vendor/codemirror.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/codemirror.min.js"></script>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("admin-pages-editor"), {
lineNumbers: true,
@ -49,7 +49,7 @@
});
$('#page-edit').submit(function (e){
$(this).attr('action', '/admin/pages/'+$('#route').val());
$(this).attr('action', '{{ request.script_root }}/admin/pages/'+$('#route').val());
});
</script>
{% endblock %}

View File

@ -29,7 +29,7 @@
display: block;
}
</style>
<script src="/static/js/vendor/plotly.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/plotly.min.js"></script>
<script type="text/javascript">
// $.distint(array)
// Unique elements in array
@ -59,7 +59,7 @@
}
function solves_graph() {
$.get('/admin/graphs/solves', function(data){
$.get('{{ request.script_root }}/admin/graphs/solves', function(data){
var solves = $.parseJSON(JSON.stringify(data));
var chals = [];
var counts = [];
@ -89,7 +89,7 @@
function keys_percentage_graph(){
// Solves and Fails pie chart
$.get('/admin/fails/all', function(data){
$.get('{{ request.script_root }}/admin/fails/all', function(data){
var res = $.parseJSON(JSON.stringify(data));
var solves = res['solves'];
var fails = res['fails'];
@ -116,7 +116,7 @@
}
function category_breakdown_graph(){
$.get('/admin/graphs/categories', function(data){
$.get('{{ request.script_root }}/admin/graphs/categories', function(data){
res = $.parseJSON(JSON.stringify(data));
res = res['categories']

View File

@ -1,7 +1,7 @@
{% extends "admin/base.html" %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/static/css/vendor/codemirror.min.css">
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/css/vendor/codemirror.min.css">
{% endblock %}
{% block content %}
@ -38,7 +38,7 @@
</div>
<div class="col-md-3">
<h3>HTML Pages <a href="/admin/pages?mode=create"><i class="fa fa-plus"></i></a></h3>
<h3>HTML Pages <a href="{{ request.script_root }}/admin/pages?mode=create"><i class="fa fa-plus"></i></a></h3>
<table id="pages" class="table table-striped">
<thead>
<tr>
@ -49,7 +49,7 @@
<tbody>
{% for route in routes %}
<tr name="{{ route.route }}">
<td class="route-name"><a href="/admin/pages/{{ route.route }}">{{ route.route }}</a></td>
<td class="route-name"><a href="{{ request.script_root }}/admin/pages/{{ route.route }}">{{ route.route }}</a></td>
<td class="text-center"><i class="fa fa-times"></i></td>
</tr>
{% endfor %}
@ -60,7 +60,7 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/vendor/codemirror.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/codemirror.min.js"></script>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("pages-editor"), {
lineNumbers: true,
@ -85,7 +85,7 @@ function load_confirm_modal(route){
var modal = $('#confirm')
modal.find('input[name=route]').val(route)
modal.find('#confirm-route-name').text(route)
$('#confirm form').attr('action', '/admin/page/'+route+'/delete');
$('#confirm form').attr('action', '{{ request.script_root }}/admin/page/'+route+'/delete');
$('#confirm').modal();
}

View File

@ -17,16 +17,16 @@
{% for team in teams %}
<tr>
<td>{{ loop.index }}</td>
<td><a href="/admin/team/{{ team.teamid }}">{{ team.name }}</a></td>
<td><a href="{{ request.script_root }}/admin/team/{{ team.teamid }}">{{ team.name }}</a></td>
<td>{{ team.score }}</td>
<td>
{% if not team.banned %}
<form method="POST" style="margin:0;" action="/admin/team/{{ team.teamid }}/ban">
<form method="POST" style="margin:0;" action="{{ request.script_root }}/admin/team/{{ team.teamid }}/ban">
<a onclick="$(this).parent().submit()">Ban</a>
<input type="hidden" value="{{ nonce }}" name="nonce">
</form>
{%else %}
<form method="POST" style="margin:0;" action="/admin/team/{{ team.teamid }}/unban">
<form method="POST" style="margin:0;" action="{{ request.script_root }}/admin/team/{{ team.teamid }}/unban">
<a onclick="$(this).parent().submit()">Unban</a>
<input type="hidden" value="{{ nonce }}" name="nonce">
</form>

View File

@ -7,11 +7,11 @@
<h1>Statistics</h1>
<h3><b>{{ team_count }}</b> teams registered</h3>
<h3><b>{{ wrong_count }}</b> <a href="wrong_keys/1">wrong keys</a> submitted</h3>
<h3><b>{{ solve_count }}</b> <a href="correct_keys/1">right keys</a> submitted</h3>
<h3><b>{{ wrong_count }}</b> <a href="{{ request.script_root }}/admin/wrong_keys/1">wrong keys</a> submitted</h3>
<h3><b>{{ solve_count }}</b> <a href="{{ request.script_root }}/admin/correct_keys/1">right keys</a> submitted</h3>
<h3><b>{{ challenge_count }}</b> challenges</h3>
{% if most_solved %}
<h3>Most solved: <b>{{ most_solved[0].chal.name }}</b> with {{ most_solved[1] }}</b> solves</h3>
<h3>Most solved: <b>{{ most_solved[0].name }}</b> with {{ most_solved[1] }}</b> solves</h3>
{% endif %}
{% if least_solved %}
<h3>Least solved: <b>{{ least_solved[0].name }}</b> with {{ least_solved[1] }}</b> solves</h3>

View File

@ -22,7 +22,7 @@
<h4 class="modal-title" id="create-award-label">Create Award</h4>
</div>
<div class="modal-body">
<form id="award-create-form" method="POST" action="/admin/awards/add">
<form id="award-create-form" method="POST" action="{{ request.script_root }}/admin/awards/add">
<div class="form-group">
<label for="award-name-input">Name</label>
<input type="text" class="form-control" id="award-name-input" name="name" placeholder="Enter award name">
@ -62,7 +62,7 @@
<h3 id="confirm-title">Delete Key</h3>
</div>
<div class="modal-body">
<form method="POST" action="/admin/chal/delete">
<form method="POST" action="{{ request.script_root }}/admin/chal/delete">
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
<div class="small-6 small-centered text-center columns">
<p id="confirm-description"></p>
@ -104,8 +104,8 @@
<tbody>
{% for addr in addrs %}
<tr>
<td class="text-center">{{ addr.ip|long2ip }}</td>
<td class="text-center solve-time"><script>document.write( moment({{ addr.date|unix_time_millis }}).local().format('MMMM Do, h:mm:ss A'))</script></td>
<td class="text-center">{{ addr[0]|long2ip }}</td>
<td class="text-center solve-time"><script>document.write( moment({{ addr[1]|unix_time_millis }}).local().format('MMMM Do, h:mm:ss A'))</script></td>
</tr>
{% endfor %}
</tbody>
@ -221,10 +221,10 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/vendor/moment.min.js"></script>
<script src="/static/js/vendor/plotly.min.js"></script>
<script src="/static/js/utils.js"></script>
<script src="/static/admin/js/team.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/moment.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/plotly.min.js"></script>
<script src="{{ request.script_root }}/static/js/utils.js"></script>
<script src="{{ request.script_root }}/static/admin/js/team.js"></script>
<script>
$('#delete-solve').click(function (e) {
e.preventDefault();
@ -270,7 +270,7 @@
description.find('#confirm-chal-name').text(chal_name);
description = description.html()
var action = '/admin/solves/' + team + '/' + chal + '/delete';
var action = '{{ request.script_root }}/admin/solves/' + team + '/' + chal + '/delete';
} else if (type == 'chal-wrong') {
var title = 'Delete Wrong Key';
var description = "<span>Are you sure you want to delete " +
@ -285,12 +285,12 @@
description.find('#confirm-chal-name').text(chal_name);
description = description.html()
var action = '/admin/wrong_keys/' + team + '/' + chal + '/delete';
var action = '{{ request.script_root }}/admin/wrong_keys/' + team + '/' + chal + '/delete';
} else if (type == 'award-row') {
var title = 'Delete Award';
var description = "<span>Are you sure you want to delete the " +
"<strong>{0}</strong> award?</span>".format(chal_name);
var action = '/admin/awards/{0}/delete'.format(chal);
var action = '{{ request.script_root }}/admin/awards/{0}/delete'.format(chal);
}
var msg = {
@ -323,7 +323,7 @@
description.find('#confirm-chal-name').text(chal_name);
description = description.html()
var action = '/admin/solves/' + team + '/' + chal + '/solve';
var action = '{{request.script_root }}/admin/solves/' + team + '/' + chal + '/solve';
var msg = {
title : title,

View File

@ -62,7 +62,7 @@ input[type="checkbox"] { margin: 0px !important; position: relative; top: 5px; }
<h2 class="text-center">Edit User</h2>
</div>
<div class="modal-body" style="padding:20px; height:525px;">
<form method="POST" action="/admin/teams/">
<form method="POST" action="{{ request.script_root }}/admin/teams/">
<input type="hidden" name="nonce" value="{{ nonce }}">
<input type="hidden" name="id">
<div class="form-group">
@ -123,7 +123,7 @@ input[type="checkbox"] { margin: 0px !important; position: relative; top: 5px; }
{% 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 | truncate(32) }}</a>
<td class="team-name"><a href="{{ request.script_root }}/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
</td>
<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 %}
@ -150,15 +150,15 @@ input[type="checkbox"] { margin: 0px !important; position: relative; top: 5px; }
{% if pages > 1 %}
<div class="text-center">Page
<br>
{% if curr_page != 1 %}<a href="/admin/teams/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% if curr_page != 1 %}<a href="{{ request.script_root }}/admin/teams/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% for page in range(1, pages + 1) %}
{% if curr_page != page %}
<a href="/admin/teams/{{ page }}">{{ page }}</a>
<a href="{{ request.script_root }}/admin/teams/{{ page }}">{{ page }}</a>
{% else %}
<b>{{ page }}</b>
{% endif %}
{% endfor %}
{% if curr_page != pages %}<a href="/admin/teams/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
{% if curr_page != pages %}<a href="{{ request.script_root }}/admin/teams/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
</div>
{% endif %}
</div>
@ -175,7 +175,7 @@ function load_update_modal(id, name, email, website, affiliation, country){
modal_form.find('input[name=website]').val(website)
modal_form.find('input[name=affiliation]').val(affiliation)
modal_form.find('input[name=country]').val(country)
$('#user form').attr('action', '/admin/team/'+id)
$('#user form').attr('action', '{{ request.script_root }}/admin/team/'+id)
$('#user').modal("show");
}
@ -215,7 +215,7 @@ $('.team-admin input').on('change', function(){
var nonce = $('#nonce').val()
console.log(admin)
$.post('/admin/team/'+id, {'admin':admin, 'nonce':nonce});
$.post('{{ request.script_root }}/admin/team/'+id, {'admin':admin, 'nonce':nonce});
})
$('#send-user-email').click(function(e){
@ -260,7 +260,7 @@ function load_confirm_modal(id, name){
var modal = $('#confirm')
modal.find('input[name=id]').val(id)
modal.find('#confirm-team-name').text(name)
$('#confirm form').attr('action', '/admin/team/'+id+'/delete');
$('#confirm form').attr('action', '{{ request.script_root }}/admin/team/'+id+'/delete');
$('#confirm').modal();
}
@ -276,7 +276,7 @@ function load_email_modal(id){
modal.find('textarea').val("")
modal.find('input[name=id]').val(id)
$('#email-user-errors').empty()
$('#email-user form').attr('action', '/admin/team/'+id+'/mail');
$('#email-user form').attr('action', '{{ request.script_root }}/admin/team/'+id+'/mail');
$('#email-user').modal();
}

View File

@ -56,7 +56,7 @@
<tbody>
{% for wrong_key in wrong_keys %}
<tr>
<td class="text-center team" id="{{ wrong_key.team }}"><a href="/admin/team/{{ wrong_key.team }}">{{ wrong_key.team_name }}</a>
<td class="text-center team" id="{{ wrong_key.team }}"><a href="{{ request.script_root }}/admin/team/{{ wrong_key.team }}">{{ wrong_key.team_name }}</a>
<td class="text-center chal" id="{{ wrong_key.chalid }}">{{ wrong_key.chal_name }}</td>
<td class="text-center solve-time"><script>document.write( moment({{ wrong_key.date|unix_time_millis }}).local().format('MMMM Do, h:mm:ss A'))</script></td>
<td class="text-center">{{ wrong_key.flag }}</td>
@ -68,24 +68,24 @@
{% if pages > 1 %}
<div class="text-center">Page
<br>
{% if curr_page != 1 %}<a href="/admin/wrong_keys/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% if curr_page != 1 %}<a href="{{ request.script_root }}/admin/wrong_keys/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% for page in range(1, pages + 1) %}
{% if curr_page != page %}
<a href="/admin/wrong_keys/{{ page }}">{{ page }}</a>
<a href="{{ request.script_root }}/admin/wrong_keys/{{ page }}">{{ page }}</a>
{% else %}
<b>{{ page }}</b>
{% endif %}
{% endfor %}
{% if curr_page != pages %}<a href="/admin/wrong_keys/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
<a href="">
{% if curr_page != pages %}<a href="{{ request.script_root }}/admin/wrong_keys/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
<a href="{{ request.script_root }}">
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/utils.js"></script>
<script src="/static/admin/js/team.js"></script>
<script src="{{ request.script_root }}/static/js/utils.js"></script>
<script src="{{ request.script_root }}/static/admin/js/team.js"></script>
<script>
$('#delete-solve').click(function (e) {
e.preventDefault();
@ -102,7 +102,7 @@
var modal = $('#confirm')
modal.find('#confirm-team-name').text(team_name);
modal.find('#confirm-chal-name').text(chal_name);
$('#confirm form').attr('action', '/admin/wrong_keys/' + team + '/' + chal + '/delete');
$('#confirm form').attr('action', '{{ request.script_root }}/admin/wrong_keys/' + team + '/' + chal + '/delete');
$('#confirm').modal('show');
}

View File

@ -4,16 +4,19 @@
<title>{{ ctf_name() }}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/static/img/favicon.ico" type="image/x-icon">
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/static/css/vendor/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/vendor/font-awesome/css/font-awesome.min.css" />
<link href='/static/css/vendor/lato.css' rel='stylesheet' type='text/css'>
<link href='/static/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" type="text/css" href="/static/user.css">
<link rel="shortcut icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
<link rel="icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/bootstrap.min.css">
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/font-awesome/css/font-awesome.min.css" />
<link href='{{ request.script_root }}/static/css/vendor/lato.css' rel='stylesheet' type='text/css'>
<link href='{{ request.script_root }}/static/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="{{ request.script_root }}/static/css/style.css">
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/user.css">
{% block stylesheets %}{% endblock %}
<script src="/static/js/vendor/moment.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/moment.min.js"></script>
<script type="text/javascript">
var script_root = "{{ request.script_root }}";
</script>
</head>
<body>
<div class="body-container">
@ -25,31 +28,31 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/" class="navbar-brand">{{ ctf_name() }}</a>
<a href="{{ request.script_root }}/" class="navbar-brand">{{ ctf_name() }}</a>
</div>
<div class="navbar-collapse collapse" aria-expanded="false" style="height: 0px">
<ul class="nav navbar-nav">
{% for page in pages() %}
<li><a href="/{{ page.route }}">{{ page.route|title }}</a></li>
<li><a href="{{ request.script_root }}/{{ page.route }}">{{ page.route|title }}</a></li>
{% endfor %}
<li><a href="/teams">Teams</a></li>
<li><a href="/scoreboard">Scoreboard</a></li>
<li><a href="/challenges">Challenges</a></li>
<li><a href="{{ request.script_root }}/teams">Teams</a></li>
<li><a href="{{ request.script_root }}/scoreboard">Scoreboard</a></li>
<li><a href="{{ request.script_root }}/challenges">Challenges</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if username is defined %}
{% if admin %}
<li><a href="/admin">Admin</a></li>
<li><a href="{{ request.script_root }}/admin">Admin</a></li>
{% endif %}
<li><a href="/team/{{ id }}">Team</a></li>
<li><a href="/profile">Profile</a></li>
<li><a href="/logout">Logout</a></li>
<li><a href="{{ request.script_root }}/team/{{ id }}">Team</a></li>
<li><a href="{{ request.script_root }}/profile">Profile</a></li>
<li><a href="{{ request.script_root }}/logout">Logout</a></li>
{% else %}
{% if can_register() %}
<li><a href="/register">Register</a></li>
<li><a href="{{ request.script_root }}/register">Register</a></li>
<li><a style="padding-left:0px;padding-right:0px;">|</a></li>
{% endif %}
<li><a href="/login">Login</a></li>
<li><a href="{{ request.script_root }}/login">Login</a></li>
{% endif %}
</ul>
</div>
@ -60,9 +63,9 @@
{% endblock %}
</div>
<script src="/static/js/vendor/jquery.min.js"></script>
<script src="/static/js/vendor/marked.min.js"></script>
<script src="/static/js/vendor/bootstrap.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/jquery.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/marked.min.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/bootstrap.min.js"></script>
{% block scripts %}
{% endblock %}
</body>

View File

@ -154,7 +154,7 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/utils.js"></script>
<script src="/static/js/chalboard.js"></script>
<script src="/static/js/style.js"></script>
<script src="{{ request.script_root }}/static/js/utils.js"></script>
<script src="{{ request.script_root }}/static/js/chalboard.js"></script>
<script src="{{ request.script_root }}/static/js/style.js"></script>
{% endblock %}

View File

@ -44,6 +44,6 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/style.js"></script>
<script src="{{ request.script_root }}/static/js/style.js"></script>
{% endblock %}

View File

@ -53,7 +53,7 @@
</div>
<div class="done-row row">
<div class="col-md-6" style="padding-left:0px">
<a class="pull-left align-text-to-button" href="/reset_password">Forgot your password?</a>
<a class="pull-left align-text-to-button" href="{{ request.script_root }}/reset_password">Forgot your password?</a>
</div>
<div class="col-md-6">
<button type="submit" id="submit" tabindex="5" class="btn btn-md btn-theme btn-outlined pull-right">Submit</button>
@ -67,6 +67,6 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/style.js"></script>
<script src="{{ request.script_root }}/static/js/style.js"></script>
{% endblock %}

View File

@ -132,5 +132,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/style.js"></script>
<script src="{{ request.script_root }}/static/js/style.js"></script>
{% endblock %}

View File

@ -63,7 +63,7 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/style.js"></script>
<script src="{{ request.script_root }}/static/js/style.js"></script>
<script>
if (window.location.hash == "#frame"){
$('.top-bar').hide()

View File

@ -55,5 +55,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/style.js"></script>
<script src="{{ request.script_root }}/static/js/style.js"></script>
{% endblock %}

View File

@ -23,7 +23,7 @@
</thead>
<tbody>
{% for team in teams %}
<tr><td>{{ loop.index }}</td><td><a href="/team/{{ team.teamid }}">{{ team.name }}</a></td><td>{{ team.score }}</td></tr>
<tr><td>{{ loop.index }}</td><td><a href="{{ request.script_root }}/team/{{ team.teamid }}">{{ team.name }}</a></td><td>{{ team.score }}</td></tr>
{% endfor %}
</tbody>
</table>
@ -31,7 +31,7 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/vendor/plotly.min.js"></script>
<script src="/static/js/utils.js"></script>
<script src="/static/js/scoreboard.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/plotly.min.js"></script>
<script src="{{ request.script_root }}/static/js/utils.js"></script>
<script src="{{ request.script_root }}/static/js/scoreboard.js"></script>
{% endblock %}

View File

@ -63,5 +63,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/style.js"></script>
<script src="{{ request.script_root }}/static/js/style.js"></script>
{% endblock %}

View File

@ -69,7 +69,7 @@
<tbody>
{% for solve in solves %}
<tr>
<td><a href="/challenges#{{ solve.chal.name }}">{{ solve.chal.name }}</a></td>
<td><a href="{{ request.script_root }}/challenges#{{ solve.chal.name }}">{{ solve.chal.name }}</a></td>
<td class="hidden-xs">{{ solve.chal.category }}</td><td>{{ solve.chal.value }}</td>
<td class="solve-time"><script>document.write( moment({{ solve.date|unix_time_millis }}).local().format('MMMM Do, h:mm:ss A'))</script></td>
</tr>
@ -81,7 +81,7 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/vendor/plotly.min.js"></script>
<script src="/static/js/utils.js"></script>
<script src="/static/js/team.js"></script>
<script src="{{ request.script_root }}/static/js/vendor/plotly.min.js"></script>
<script src="{{ request.script_root }}/static/js/utils.js"></script>
<script src="{{ request.script_root }}/static/js/team.js"></script>
{% endblock %}

View File

@ -22,7 +22,7 @@
<tbody>
{% for team in teams %}
<tr>
<td><a href="/team/{{ team.id }}">{{ team.name }}</a></td>
<td><a href="{{ request.script_root }}/team/{{ team.id }}">{{ team.name }}</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 class="hidden-xs"><span>{% if team.country %}{{ team.country }}{% endif %}</span></td>
@ -33,16 +33,16 @@
{% if team_pages > 1 %}
<div class="text-center">Page
<br>
{% if curr_page != 1 %}<a href="/teams/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% if curr_page != 1 %}<a href="{{ request.script_root }}/teams/{{ curr_page-1 }}">&lt;&lt;&lt;</a>{% endif %}
{% for page in range(1, team_pages + 1) %}
{% if curr_page != page %}
<a href="/teams/{{ page }}">{{ page }}</a>
<a href="{{ request.script_root }}/teams/{{ page }}">{{ page }}</a>
{% else %}
<b>{{page}}</b>
{% endif %}
{% endfor %}
{% if curr_page != team_pages %}<a href="/teams/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
<a href="">
{% if curr_page != team_pages %}<a href="{{ request.script_root }}/teams/{{ curr_page+1 }}">&gt;&gt;&gt;</a>{% endif %}
<a href="{{ request.script_root }}">
</div>
{% endif %}
</div>

View File

@ -19,7 +19,7 @@ views = Blueprint('views', __name__)
@views.before_request
def redirect_setup():
if request.path == "/static/css/style.css":
if request.path.startswith("/static"):
return
if not is_setup() and request.path != "/setup":
return redirect(url_for('views.setup'))
@ -50,15 +50,15 @@ def setup():
## Index page
page = Pages('index', """<div class="container main-container">
<img class="logo" src="/static/img/logo.png" />
<img class="logo" src="{0}/static/img/logo.png" />
<h3 class="text-center">
Welcome to a cool CTF framework written by <a href="https://github.com/ColdHeat">Kevin Chung</a> of <a href="https://github.com/isislab">@isislab</a>
</h3>
<h4 class="text-center">
<a href="/admin">Click here</a> to login and setup your CTF
<a href="{0}/admin">Click here</a> to login and setup your CTF
</h4>
</div>""")
</div>""".format(request.script_root))
#max attempts per challenge
max_tries = set_config("max_tries",0)
@ -89,9 +89,9 @@ def setup():
db.session.add(admin)
db.session.commit()
app.setup = False
return redirect('/')
return redirect(url_for('views.static_html'))
return render_template('setup.html', nonce=session.get('nonce'))
return redirect('/')
return redirect(url_for('views.static_html'))
# Custom CSS handler

46
ctfd.ini Normal file
View File

@ -0,0 +1,46 @@
# UWSGI Configuration File
# Install uwsgi (sudo apt-get install uwsgi), copy this file to
# /etc/uwsgi/apps-available and then link it in /etc/uwsgi/apps-enabled
# Only two lines below (commented) need to be changed for your config.
# Then, you can use something like the following in your nginx config:
#
# # SERVER_ROOT is not / (e.g. /ctf)
# location = /ctf { rewrite ^ /ctf/; }
# location /ctf {
# include uwsgi_params;
# uwsgi_pass unix:/run/uwsgi/app/ctfd/socket;
# }
#
# # SERVER_ROOT is /
# location / {
# include uwsgi_params;
# wsgi_pass unix:/run/uwsgi/app/ctfd/socket;
# }
[uwsgi]
# Where you've put CTFD
chdir = /var/www/ctfd/
# If SCRIPT_ROOT is not /
#mount = /ctf=wsgi.py
# SCRIPT_ROOT is /
mount = /=wsgi.py
# You shouldn't need to change anything past here
plugin = python
module = wsgi
master = true
processes = 1
threads = 1
vacuum = true
manage-script-name = true
wsgi-file = wsgi.py
callable = app
die-on-term = true
# If you're not on debian/ubuntu, replace with uid/gid of web user
uid = www-data
gid = www-data

2
wsgi.py Normal file
View File

@ -0,0 +1,2 @@
from CTFd import create_app
app = create_app()