diff --git a/CTFd/admin/__init__.py b/CTFd/admin/__init__.py index 33d74ae..06f822a 100644 --- a/CTFd/admin/__init__.py +++ b/CTFd/admin/__init__.py @@ -62,6 +62,7 @@ def admin_config(): try: view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', 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_name_change = bool(request.form.get('prevent_name_change', None)) view_after_ctf = bool(request.form.get('view_after_ctf', None)) @@ -71,6 +72,7 @@ def admin_config(): except (ValueError, TypeError): view_challenges_unregistered = None view_scoreboard_if_authed = None + hide_scores = None prevent_registration = None prevent_name_change = None view_after_ctf = None @@ -80,6 +82,7 @@ def admin_config(): finally: 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) + hide_scores = set_config('hide_scores', hide_scores) prevent_registration = set_config('prevent_registration', prevent_registration) prevent_name_change = set_config('prevent_name_change', prevent_name_change) 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_theme = get_config('ctf_theme') max_tries = get_config('max_tries') + hide_scores = get_config('hide_scores') mail_server = get_config('mail_server') mail_port = get_config('mail_port') @@ -159,6 +163,7 @@ def admin_config(): ctf_theme_config=ctf_theme, start=start, end=end, + hide_scores=hide_scores, max_tries=max_tries, mail_server=mail_server, mail_port=mail_port, diff --git a/CTFd/challenges.py b/CTFd/challenges.py index 0de1952..06fc08e 100644 --- a/CTFd/challenges.py +++ b/CTFd/challenges.py @@ -6,7 +6,7 @@ import time from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint 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.plugins.keys import get_key_class from CTFd.plugins.challenges import get_chal_class @@ -79,12 +79,17 @@ def chals(): def solves_per_chal(): if not user_can_view_challenges(): 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 = 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, name in solves: - json[chal] = count + if hide_scores(): + for chal, count, name in solves: + json[chal] = -1 + else: + for chal, count, name in solves: + json[chal] = count db.session.close() return jsonify(json) @@ -158,8 +163,11 @@ def fails(teamid): def who_solved(chalid): if not user_can_view_challenges(): 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': []} + 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: json['teams'].append({'id': solve.team.id, 'name': solve.team.name, 'date': solve.date}) return jsonify(json) diff --git a/CTFd/plugins/challenges/config.html b/CTFd/plugins/challenges/config.html new file mode 100644 index 0000000..e69de29 diff --git a/CTFd/scoreboard.py b/CTFd/scoreboard.py index 494f6bf..e351949 100644 --- a/CTFd/scoreboard.py +++ b/CTFd/scoreboard.py @@ -1,7 +1,7 @@ from flask import render_template, jsonify, Blueprint, redirect, url_for, request 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 scoreboard = Blueprint('scoreboard', __name__) @@ -37,16 +37,22 @@ def get_standings(admin=False, count=None): def scoreboard_view(): if get_config('view_scoreboard_if_authed') and not authed(): 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() return render_template('scoreboard.html', teams=standings) @scoreboard.route('/scores') def scores(): + json = {'standings': []} if get_config('view_scoreboard_if_authed') and not authed(): return redirect(url_for('auth.login', next=request.path)) + if hide_scores(): + return jsonify(json) + 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)}) return jsonify(json) @@ -54,16 +60,15 @@ def scores(): @scoreboard.route('/top/') def topteams(count): + json = {'scores': {}} if get_config('view_scoreboard_if_authed') and not authed(): return redirect(url_for('auth.login', next=request.path)) - try: - count = int(count) - except ValueError: - count = 10 + if hide_scores(): + return jsonify(json) + if count > 20 or count < 0: count = 10 - json = {'scores': {}} standings = get_standings(count=count) for team in standings: diff --git a/CTFd/static/admin/js/utils.js b/CTFd/static/admin/js/utils.js index 5908ad4..c5581d1 100644 --- a/CTFd/static/admin/js/utils.js +++ b/CTFd/static/admin/js/utils.js @@ -53,3 +53,11 @@ function colorhash (x) { function htmlentities(string) { return $('
').text(string).html(); } + +Handlebars.registerHelper('if_eq', function(a, b, opts) { + if (a == b) { + return opts.fn(this); + } else { + return opts.inverse(this); + } +}); \ No newline at end of file diff --git a/CTFd/static/original/js/templates/challenges/standard/standard-challenge-modal.hbs b/CTFd/static/original/js/templates/challenges/standard/standard-challenge-modal.hbs index 63ca388..7f27b9b 100644 --- a/CTFd/static/original/js/templates/challenges/standard/standard-challenge-modal.hbs +++ b/CTFd/static/original/js/templates/challenges/standard/standard-challenge-modal.hbs @@ -3,7 +3,10 @@
+ {% if errors %} +
+ {% for error in errors %} +

{{ error }}

+ {% endfor %} +
+ {% else %}

{%if place %} @@ -76,6 +83,7 @@

+ {% endif %}
{% endblock %} diff --git a/CTFd/templates/original/teams.html b/CTFd/templates/original/teams.html index d06cc53..0e92d5e 100644 --- a/CTFd/templates/original/teams.html +++ b/CTFd/templates/original/teams.html @@ -22,7 +22,13 @@ {% for team in teams %} - {{ team.name }} + + {% if hide_scores() %} + {{ team.name }} + {% else %} + {{ team.name }} + {% endif %} + {% if team.website and (team.website.startswith('http://') or team.website.startswith('https://')) %}{{ team.website }}{% endif %} {% if team.affiliation %}{{ team.affiliation }}{% endif %} {% if team.country %}{{ team.country }}{% endif %} diff --git a/CTFd/utils.py b/CTFd/utils.py index 7bb17f5..2b517be 100644 --- a/CTFd/utils.py +++ b/CTFd/utils.py @@ -102,6 +102,7 @@ def init_utils(app): 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_config=get_config) + app.jinja_env.globals.update(hide_scores=hide_scores) @app.context_processor def inject_user(): @@ -150,6 +151,11 @@ def ctf_theme(): return theme if theme else '' +@cache.memoize() +def hide_scores(): + return get_config('hide_scores') + + def pages(): pages = Pages.query.filter(Pages.route != "index").all() return pages diff --git a/CTFd/views.py b/CTFd/views.py index 3e9a596..9f90d47 100644 --- a/CTFd/views.py +++ b/CTFd/views.py @@ -6,7 +6,7 @@ from jinja2.exceptions import TemplateNotFound 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, \ - is_admin + is_admin, hide_scores from CTFd.models import db, Teams, Solves, Awards, Files, Pages views = Blueprint('views', __name__) @@ -131,6 +131,7 @@ def teams(page): def team(teamid): if get_config('view_scoreboard_if_authed') and not authed(): return redirect(url_for('auth.login', next=request.path)) + errors = [] user = Teams.query.filter_by(id=teamid).first_or_404() solves = Solves.query.filter_by(teamid=teamid) awards = Awards.query.filter_by(teamid=teamid).all() @@ -138,6 +139,12 @@ def team(teamid): place = user.place() 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': return render_template('team.html', solves=solves, awards=awards, team=user, score=score, place=place) elif request.method == 'POST':