mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-19 06:54:20 +01:00
Hide scores (#224)
* Starting work on hide_scores functionality * Hide teams in more views * Starting work on hide_scores functionality * Hide teams in more views
This commit is contained in:
@@ -62,6 +62,7 @@ def admin_config():
|
|||||||
try:
|
try:
|
||||||
view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
|
view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
|
||||||
view_scoreboard_if_authed = bool(request.form.get('view_scoreboard_if_authed', None))
|
view_scoreboard_if_authed = bool(request.form.get('view_scoreboard_if_authed', None))
|
||||||
|
hide_scores = bool(request.form.get('hide_scores', None))
|
||||||
prevent_registration = bool(request.form.get('prevent_registration', None))
|
prevent_registration = bool(request.form.get('prevent_registration', None))
|
||||||
prevent_name_change = bool(request.form.get('prevent_name_change', None))
|
prevent_name_change = bool(request.form.get('prevent_name_change', None))
|
||||||
view_after_ctf = bool(request.form.get('view_after_ctf', None))
|
view_after_ctf = bool(request.form.get('view_after_ctf', None))
|
||||||
@@ -71,6 +72,7 @@ def admin_config():
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
view_challenges_unregistered = None
|
view_challenges_unregistered = None
|
||||||
view_scoreboard_if_authed = None
|
view_scoreboard_if_authed = None
|
||||||
|
hide_scores = None
|
||||||
prevent_registration = None
|
prevent_registration = None
|
||||||
prevent_name_change = None
|
prevent_name_change = None
|
||||||
view_after_ctf = None
|
view_after_ctf = None
|
||||||
@@ -80,6 +82,7 @@ def admin_config():
|
|||||||
finally:
|
finally:
|
||||||
view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
|
view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
|
||||||
view_scoreboard_if_authed = set_config('view_scoreboard_if_authed', view_scoreboard_if_authed)
|
view_scoreboard_if_authed = set_config('view_scoreboard_if_authed', view_scoreboard_if_authed)
|
||||||
|
hide_scores = set_config('hide_scores', hide_scores)
|
||||||
prevent_registration = set_config('prevent_registration', prevent_registration)
|
prevent_registration = set_config('prevent_registration', prevent_registration)
|
||||||
prevent_name_change = set_config('prevent_name_change', prevent_name_change)
|
prevent_name_change = set_config('prevent_name_change', prevent_name_change)
|
||||||
view_after_ctf = set_config('view_after_ctf', view_after_ctf)
|
view_after_ctf = set_config('view_after_ctf', view_after_ctf)
|
||||||
@@ -122,6 +125,7 @@ def admin_config():
|
|||||||
ctf_name = get_config('ctf_name')
|
ctf_name = get_config('ctf_name')
|
||||||
ctf_theme = get_config('ctf_theme')
|
ctf_theme = get_config('ctf_theme')
|
||||||
max_tries = get_config('max_tries')
|
max_tries = get_config('max_tries')
|
||||||
|
hide_scores = get_config('hide_scores')
|
||||||
|
|
||||||
mail_server = get_config('mail_server')
|
mail_server = get_config('mail_server')
|
||||||
mail_port = get_config('mail_port')
|
mail_port = get_config('mail_port')
|
||||||
@@ -159,6 +163,7 @@ def admin_config():
|
|||||||
ctf_theme_config=ctf_theme,
|
ctf_theme_config=ctf_theme,
|
||||||
start=start,
|
start=start,
|
||||||
end=end,
|
end=end,
|
||||||
|
hide_scores=hide_scores,
|
||||||
max_tries=max_tries,
|
max_tries=max_tries,
|
||||||
mail_server=mail_server,
|
mail_server=mail_server,
|
||||||
mail_port=mail_port,
|
mail_port=mail_port,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import time
|
|||||||
from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint
|
from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint
|
||||||
from sqlalchemy.sql import or_
|
from sqlalchemy.sql import or_
|
||||||
|
|
||||||
from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, user_can_view_challenges, is_admin, get_config, get_ip, is_verified, ctf_started, ctf_ended, ctf_name
|
from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, user_can_view_challenges, is_admin, get_config, get_ip, is_verified, ctf_started, ctf_ended, ctf_name, hide_scores
|
||||||
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards
|
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards
|
||||||
from CTFd.plugins.keys import get_key_class
|
from CTFd.plugins.keys import get_key_class
|
||||||
from CTFd.plugins.challenges import get_chal_class
|
from CTFd.plugins.challenges import get_chal_class
|
||||||
@@ -79,12 +79,17 @@ def chals():
|
|||||||
def solves_per_chal():
|
def solves_per_chal():
|
||||||
if not user_can_view_challenges():
|
if not user_can_view_challenges():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
|
|
||||||
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_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) \
|
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \
|
||||||
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
||||||
json = {}
|
json = {}
|
||||||
for chal, count, name in solves:
|
if hide_scores():
|
||||||
json[chal] = count
|
for chal, count, name in solves:
|
||||||
|
json[chal] = -1
|
||||||
|
else:
|
||||||
|
for chal, count, name in solves:
|
||||||
|
json[chal] = count
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
|
|
||||||
@@ -158,8 +163,11 @@ def fails(teamid):
|
|||||||
def who_solved(chalid):
|
def who_solved(chalid):
|
||||||
if not user_can_view_challenges():
|
if not user_can_view_challenges():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid, Teams.banned == False).order_by(Solves.date.asc())
|
|
||||||
json = {'teams': []}
|
json = {'teams': []}
|
||||||
|
if hide_scores():
|
||||||
|
return jsonify(json)
|
||||||
|
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid, Teams.banned == False).order_by(Solves.date.asc())
|
||||||
for solve in solves:
|
for solve in solves:
|
||||||
json['teams'].append({'id': solve.team.id, 'name': solve.team.name, 'date': solve.date})
|
json['teams'].append({'id': solve.team.id, 'name': solve.team.name, 'date': solve.date})
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
|
|||||||
0
CTFd/plugins/challenges/config.html
Normal file
0
CTFd/plugins/challenges/config.html
Normal file
@@ -1,7 +1,7 @@
|
|||||||
from flask import render_template, jsonify, Blueprint, redirect, url_for, request
|
from flask import render_template, jsonify, Blueprint, redirect, url_for, request
|
||||||
from sqlalchemy.sql.expression import union_all
|
from sqlalchemy.sql.expression import union_all
|
||||||
|
|
||||||
from CTFd.utils import unix_time, authed, get_config
|
from CTFd.utils import unix_time, authed, get_config, hide_scores
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Challenges
|
from CTFd.models import db, Teams, Solves, Awards, Challenges
|
||||||
|
|
||||||
scoreboard = Blueprint('scoreboard', __name__)
|
scoreboard = Blueprint('scoreboard', __name__)
|
||||||
@@ -37,16 +37,22 @@ def get_standings(admin=False, count=None):
|
|||||||
def scoreboard_view():
|
def scoreboard_view():
|
||||||
if get_config('view_scoreboard_if_authed') and not authed():
|
if get_config('view_scoreboard_if_authed') and not authed():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
|
if hide_scores():
|
||||||
|
return render_template('scoreboard.html', errors=['Scores are currently hidden'])
|
||||||
standings = get_standings()
|
standings = get_standings()
|
||||||
return render_template('scoreboard.html', teams=standings)
|
return render_template('scoreboard.html', teams=standings)
|
||||||
|
|
||||||
|
|
||||||
@scoreboard.route('/scores')
|
@scoreboard.route('/scores')
|
||||||
def scores():
|
def scores():
|
||||||
|
json = {'standings': []}
|
||||||
if get_config('view_scoreboard_if_authed') and not authed():
|
if get_config('view_scoreboard_if_authed') and not authed():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
|
if hide_scores():
|
||||||
|
return jsonify(json)
|
||||||
|
|
||||||
standings = get_standings()
|
standings = get_standings()
|
||||||
json = {'standings': []}
|
|
||||||
for i, x in enumerate(standings):
|
for i, x in enumerate(standings):
|
||||||
json['standings'].append({'pos': i + 1, 'id': x.teamid, 'team': x.name, 'score': int(x.score)})
|
json['standings'].append({'pos': i + 1, 'id': x.teamid, 'team': x.name, 'score': int(x.score)})
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
@@ -54,16 +60,15 @@ def scores():
|
|||||||
|
|
||||||
@scoreboard.route('/top/<int:count>')
|
@scoreboard.route('/top/<int:count>')
|
||||||
def topteams(count):
|
def topteams(count):
|
||||||
|
json = {'scores': {}}
|
||||||
if get_config('view_scoreboard_if_authed') and not authed():
|
if get_config('view_scoreboard_if_authed') and not authed():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
try:
|
if hide_scores():
|
||||||
count = int(count)
|
return jsonify(json)
|
||||||
except ValueError:
|
|
||||||
count = 10
|
|
||||||
if count > 20 or count < 0:
|
if count > 20 or count < 0:
|
||||||
count = 10
|
count = 10
|
||||||
|
|
||||||
json = {'scores': {}}
|
|
||||||
standings = get_standings(count=count)
|
standings = get_standings(count=count)
|
||||||
|
|
||||||
for team in standings:
|
for team in standings:
|
||||||
|
|||||||
@@ -53,3 +53,11 @@ function colorhash (x) {
|
|||||||
function htmlentities(string) {
|
function htmlentities(string) {
|
||||||
return $('<div/>').text(string).html();
|
return $('<div/>').text(string).html();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Handlebars.registerHelper('if_eq', function(a, b, opts) {
|
||||||
|
if (a == b) {
|
||||||
|
return opts.fn(this);
|
||||||
|
} else {
|
||||||
|
return opts.inverse(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -3,7 +3,10 @@
|
|||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li role="presentation" class="active"><a href="#challenge" aria-controls="challenge" role="tab">Challenge</a></li>
|
<li role="presentation" class="active"><a href="#challenge" aria-controls="challenge" role="tab">Challenge</a></li>
|
||||||
|
{{#if_eq solves '-1 Solves'}}
|
||||||
|
{{else}}
|
||||||
<li role="presentation"><a href="#solves" aria-controls="solves" class="chal-solves" role="tab">{{solves}}</a></li>
|
<li role="presentation"><a href="#solves" aria-controls="solves" class="chal-solves" role="tab">{{solves}}</a></li>
|
||||||
|
{{/if_eq}}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div role="tabpanel">
|
<div role="tabpanel">
|
||||||
|
|||||||
@@ -53,3 +53,11 @@ function colorhash (x) {
|
|||||||
function htmlentities(string) {
|
function htmlentities(string) {
|
||||||
return $('<div/>').text(string).html();
|
return $('<div/>').text(string).html();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Handlebars.registerHelper('if_eq', function(a, b, opts) {
|
||||||
|
if (a == b) {
|
||||||
|
return opts.fn(this);
|
||||||
|
} else {
|
||||||
|
return opts.inverse(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -50,6 +50,13 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input id="hide_scores" name="hide_scores" type="checkbox" {% if hide_scores %}checked{% endif %}>
|
||||||
|
Hide Scores from public view
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div role="tabpanel" class="tab-pane" id="accounts-section">
|
<div role="tabpanel" class="tab-pane" id="accounts-section">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -37,7 +37,9 @@
|
|||||||
<li><a href="{{ request.script_root }}/{{ page.route }}">{{ page.route|title }}</a></li>
|
<li><a href="{{ request.script_root }}/{{ page.route }}">{{ page.route|title }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li><a href="{{ request.script_root }}/teams">Teams</a></li>
|
<li><a href="{{ request.script_root }}/teams">Teams</a></li>
|
||||||
|
{% if not hide_scores() %}
|
||||||
<li><a href="{{ request.script_root }}/scoreboard">Scoreboard</a></li>
|
<li><a href="{{ request.script_root }}/scoreboard">Scoreboard</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><a href="{{ request.script_root }}/challenges">Challenges</a></li>
|
<li><a href="{{ request.script_root }}/challenges">Challenges</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container main-container">
|
<div class="container main-container">
|
||||||
|
{% if errors %}
|
||||||
|
<div class="container main-container">
|
||||||
|
<div id='errors' class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<h1>{{ error }}</h1>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
<div id="score-graph"></div>
|
<div id="score-graph"></div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@@ -27,6 +36,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{% if errors %}
|
||||||
|
<div id='errors' class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<h1>{{ error }}</h1>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
<div class="team-info">
|
<div class="team-info">
|
||||||
<h2 id="team-place" class="text-center">
|
<h2 id="team-place" class="text-center">
|
||||||
{%if place %}
|
{%if place %}
|
||||||
@@ -76,6 +83,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for team in teams %}
|
{% for team in teams %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ request.script_root }}/team/{{ team.id }}">{{ team.name }}</a></td>
|
<td>
|
||||||
|
{% if hide_scores() %}
|
||||||
|
<span>{{ team.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ request.script_root }}/team/{{ team.id }}">{{ team.name }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{% if team.website and (team.website.startswith('http://') or team.website.startswith('https://')) %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %}</td>
|
<td>{% if team.website and (team.website.startswith('http://') or team.website.startswith('https://')) %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %}</td>
|
||||||
<td><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span></td>
|
<td><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span></td>
|
||||||
<td class="hidden-xs"><span>{% if team.country %}{{ team.country }}{% endif %}</span></td>
|
<td class="hidden-xs"><span>{% if team.country %}{{ team.country }}{% endif %}</span></td>
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ def init_utils(app):
|
|||||||
app.jinja_env.globals.update(can_create_container=can_create_container)
|
app.jinja_env.globals.update(can_create_container=can_create_container)
|
||||||
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
|
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
|
||||||
app.jinja_env.globals.update(get_config=get_config)
|
app.jinja_env.globals.update(get_config=get_config)
|
||||||
|
app.jinja_env.globals.update(hide_scores=hide_scores)
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_user():
|
def inject_user():
|
||||||
@@ -150,6 +151,11 @@ def ctf_theme():
|
|||||||
return theme if theme else ''
|
return theme if theme else ''
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
|
def hide_scores():
|
||||||
|
return get_config('hide_scores')
|
||||||
|
|
||||||
|
|
||||||
def pages():
|
def pages():
|
||||||
pages = Pages.query.filter(Pages.route != "index").all()
|
pages = Pages.query.filter(Pages.route != "index").all()
|
||||||
return pages
|
return pages
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from jinja2.exceptions import TemplateNotFound
|
|||||||
from passlib.hash import bcrypt_sha256
|
from passlib.hash import bcrypt_sha256
|
||||||
|
|
||||||
from CTFd.utils import authed, is_setup, validate_url, get_config, set_config, sha512, cache, ctftime, view_after_ctf, ctf_started, \
|
from CTFd.utils import authed, is_setup, validate_url, get_config, set_config, sha512, cache, ctftime, view_after_ctf, ctf_started, \
|
||||||
is_admin
|
is_admin, hide_scores
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Files, Pages
|
from CTFd.models import db, Teams, Solves, Awards, Files, Pages
|
||||||
|
|
||||||
views = Blueprint('views', __name__)
|
views = Blueprint('views', __name__)
|
||||||
@@ -131,6 +131,7 @@ def teams(page):
|
|||||||
def team(teamid):
|
def team(teamid):
|
||||||
if get_config('view_scoreboard_if_authed') and not authed():
|
if get_config('view_scoreboard_if_authed') and not authed():
|
||||||
return redirect(url_for('auth.login', next=request.path))
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
|
errors = []
|
||||||
user = Teams.query.filter_by(id=teamid).first_or_404()
|
user = Teams.query.filter_by(id=teamid).first_or_404()
|
||||||
solves = Solves.query.filter_by(teamid=teamid)
|
solves = Solves.query.filter_by(teamid=teamid)
|
||||||
awards = Awards.query.filter_by(teamid=teamid).all()
|
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||||
@@ -138,6 +139,12 @@ def team(teamid):
|
|||||||
place = user.place()
|
place = user.place()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
|
||||||
|
if hide_scores() and teamid != session.get('id'):
|
||||||
|
errors.append('Scores are currently hidden')
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
return render_template('team.html', team=user, errors=errors)
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template('team.html', solves=solves, awards=awards, team=user, score=score, place=place)
|
return render_template('team.html', solves=solves, awards=awards, team=user, score=score, place=place)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
|
|||||||
Reference in New Issue
Block a user