diff --git a/CTFd/scoreboard.py b/CTFd/scoreboard.py index 444c7ded..d509b3c6 100644 --- a/CTFd/scoreboard.py +++ b/CTFd/scoreboard.py @@ -87,7 +87,7 @@ def scores(): @scoreboard.route('/top/') def topteams(count): - json = {'scores': {}} + json = {'places': {}} if utils.get_config('view_scoreboard_if_authed') and not utils.authed(): return redirect(url_for('auth.login', next=request.path)) if utils.hide_scores(): @@ -98,7 +98,7 @@ def topteams(count): standings = get_standings(count=count) - for team in standings: + for i, team in enumerate(standings): solves = Solves.query.filter_by(teamid=team.teamid) awards = Awards.query.filter_by(teamid=team.teamid) @@ -111,20 +111,24 @@ def topteams(count): solves = solves.all() awards = awards.all() - json['scores'][team.name] = [] + json['places'][i + 1] = { + 'id': team.teamid, + 'name': team.name, + 'solves': [] + } for x in solves: - json['scores'][team.name].append({ + json['places'][i + 1]['solves'].append({ 'chal': x.chalid, 'team': x.teamid, 'value': x.chal.value, 'time': utils.unix_time(x.date) }) for award in awards: - json['scores'][team.name].append({ + json['places'][i + 1]['solves'].append({ 'chal': None, 'team': award.teamid, 'value': award.value, 'time': utils.unix_time(award.date) }) - json['scores'][team.name] = sorted(json['scores'][team.name], key=lambda k: k['time']) + json['places'][i + 1]['solves'] = sorted(json['places'][i + 1]['solves'], key=lambda k: k['time']) return jsonify(json) diff --git a/CTFd/themes/original/static/js/scoreboard.js b/CTFd/themes/original/static/js/scoreboard.js index e8b8c976..3c704983 100644 --- a/CTFd/themes/original/static/js/scoreboard.js +++ b/CTFd/themes/original/static/js/scoreboard.js @@ -24,20 +24,20 @@ function UTCtoDate(utc){ } function scoregraph () { $.get(script_root + '/top/10', function( data ) { - var scores = $.parseJSON(JSON.stringify(data)); - scores = scores['scores']; - if (Object.keys(scores).length == 0 ){ + var places = $.parseJSON(JSON.stringify(data)); + places = places['places']; + if (Object.keys(places).length == 0 ){ return; } - var teams = Object.keys(scores); + var teams = Object.keys(places); var traces = []; for(var i = 0; i < teams.length; i++){ var team_score = []; var times = []; - for(var j = 0; j < scores[teams[i]].length; j++){ - team_score.push(scores[teams[i]][j].value); - var date = moment(scores[teams[i]][j].time * 1000); + for(var j = 0; j < places[teams[i]]['solves'].length; j++){ + team_score.push(places[teams[i]]['solves'][j].value); + var date = moment(places[teams[i]]['solves'][j].time * 1000); times.push(date.toDate()); } team_score = cumulativesum(team_score); @@ -45,7 +45,7 @@ function scoregraph () { x: times, y: team_score, mode: 'lines+markers', - name: teams[i] + name: places[teams[i]]['name'] } traces.push(trace); } diff --git a/tests/helpers.py b/tests/helpers.py index abc0defb..01e8222c 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -2,6 +2,7 @@ from CTFd import create_app from CTFd.models import * from sqlalchemy_utils import database_exists, create_database, drop_database from sqlalchemy.engine.url import make_url +import datetime def create_ctfd(ctf_name="CTFd", name="admin", email="admin@ctfd.io", password="password", setup=True): @@ -110,6 +111,7 @@ def gen_hint(db, chal, hint="This is a hint", cost=0, type=0): def gen_solve(db, teamid, chalid, ip='127.0.0.1', flag='rightkey'): solve = Solves(teamid, chalid, ip, flag) + solve.date = datetime.datetime.utcnow() db.session.add(solve) db.session.commit() return solve @@ -117,6 +119,7 @@ def gen_solve(db, teamid, chalid, ip='127.0.0.1', flag='rightkey'): def gen_wrongkey(db, teamid, chalid, ip='127.0.0.1', flag='wrongkey'): wrongkey = WrongKeys(teamid, chalid, ip, flag) + wrongkey.date = datetime.datetime.utcnow() db.session.add(wrongkey) db.session.commit() return wrongkey diff --git a/tests/user/test_scoreboard.py b/tests/user/test_scoreboard.py new file mode 100644 index 00000000..61d55a1d --- /dev/null +++ b/tests/user/test_scoreboard.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from CTFd.models import Teams, Solves, WrongKeys +from CTFd.utils import get_config, set_config +from CTFd import utils +from tests.helpers import * +from freezegun import freeze_time +from mock import patch +import json + + +def test_top_10(): + '''Make sure top10 returns correct information''' + app = create_ctfd() + with app.app_context(): + register_user(app, name="user1", email="user1@ctfd.io") + register_user(app, name="user2", email="user2@ctfd.io") + + chal1 = gen_challenge(app.db) + flag1 = gen_flag(app.db, chal=chal1.id, flag='flag') + chal1_id = chal1.id + + chal2 = gen_challenge(app.db) + flag2 = gen_flag(app.db, chal=chal2.id, flag='flag') + chal2_id = chal2.id + + # Generates solve for user1 + with freeze_time("2017-10-3 03:21:34"): + gen_solve(app.db, teamid=2, chalid=chal1_id) + + with freeze_time("2017-10-4 03:25:45"): + gen_solve(app.db, teamid=2, chalid=chal2_id) + + # Generate solve for user2 + with freeze_time("2017-10-3 03:21:34"): + gen_solve(app.db, teamid=3, chalid=chal1_id) + + client = login_as_user(app) + r = client.get('/top/10') + response = r.get_data(as_text=True) + + saved = '''{ + "places": { + "1": { + "id": 2, + "name": "user1", + "solves": [ + { + "chal": 1, + "team": 2, + "time": 1507000894, + "value": 100 + }, + { + "chal": 2, + "team": 2, + "time": 1507087545, + "value": 100 + } + ] + }, + "2": { + "id": 3, + "name": "user2", + "solves": [ + { + "chal": 1, + "team": 3, + "time": 1507000894, + "value": 100 + } + ] + } + } + }''' + saved = json.loads(saved) + received = json.loads(response) + assert saved == received + destroy_ctfd(app)