diff --git a/CTFd/api/v1/challenges.py b/CTFd/api/v1/challenges.py index 521f83fd..d9eb6f2b 100644 --- a/CTFd/api/v1/challenges.py +++ b/CTFd/api/v1/challenges.py @@ -1,4 +1,4 @@ -from flask import session, request, abort +from flask import session, request, abort, url_for from flask_restplus import Namespace, Resource from CTFd.models import ( db, @@ -29,11 +29,11 @@ from CTFd.cache import cache, clear_standings from CTFd.utils.scores import get_standings from CTFd.utils.config.visibility import scores_visible, accounts_visible, challenges_visible from CTFd.utils.user import get_current_user, is_admin, authed -from CTFd.utils.modes import get_model +from CTFd.utils.modes import get_model, USERS_MODE, TEAMS_MODE from CTFd.schemas.tags import TagSchema from CTFd.schemas.hints import HintSchema from CTFd.schemas.flags import FlagSchema -from CTFd.utils import config +from CTFd.utils import config, get_config from CTFd.utils import user as current_user from CTFd.utils.user import get_current_team from CTFd.utils.user import get_current_user @@ -490,11 +490,20 @@ class ChallengeSolves(Resource): .filter(Solves.challenge_id == challenge_id, Model.banned == False, Model.hidden == False)\ .order_by(Solves.date.asc()) + endpoint = None + if get_config('user_mode') == TEAMS_MODE: + endpoint = 'teams.public' + arg = 'team_id' + elif get_config('user_mode') == USERS_MODE: + endpoint = 'users.public' + arg = 'user_id' + for solve in solves: response.append({ 'account_id': solve.account_id, 'name': solve.account.name, - 'date': isoformat(solve.date) + 'date': isoformat(solve.date), + 'account_url': url_for(endpoint, **{arg: solve.account_id}) }) return { diff --git a/CTFd/themes/core/static/js/challenges.js b/CTFd/themes/core/static/js/challenges.js index ad39a326..c46a41f0 100644 --- a/CTFd/themes/core/static/js/challenges.js +++ b/CTFd/themes/core/static/js/challenges.js @@ -220,7 +220,8 @@ function getsolves(id) { var id = data[i].account_id; var name = data[i].name; var date = moment(data[i].date).local().fromNow(); - box.append('{1}{2}'.format(id, htmlentities(name), date)); + var account_url = data[i].account_url + box.append('{2}{3}'.format(account_url, id, htmlentities(name), date)); } }); } diff --git a/tests/api/v1/test_challenges.py b/tests/api/v1/test_challenges.py index b644e366..623340c1 100644 --- a/tests/api/v1/test_challenges.py +++ b/tests/api/v1/test_challenges.py @@ -431,6 +431,61 @@ def test_api_challenge_get_solves_404(): destroy_ctfd(app) +def test_api_challenge_solves_returns_correct_data(): + """Test that /api/v1//solves returns expected data""" + app = create_ctfd() + with app.app_context(): + register_user(app) + client = login_as_user(app) + chal = gen_challenge(app.db) + gen_solve(app.db, user_id=2, challenge_id=chal.id) + r = client.get('/api/v1/challenges/1/solves') + resp = r.get_json()['data'] + solve = resp[0] + assert r.status_code == 200 + assert solve.get('account_id') == 2 + assert solve.get('name') == 'user' + assert solve.get('date') is not None + assert solve.get('account_url') == '/users/2' + destroy_ctfd(app) + + app = create_ctfd(user_mode="teams") + with app.app_context(): + register_user(app) + client = login_as_user(app) + team = gen_team(app.db) + user = Users.query.filter_by(id=2).first() + user.team_id = team.id + app.db.session.commit() + chal = gen_challenge(app.db) + gen_solve(app.db, user_id=2, team_id=1, challenge_id=chal.id) + r = client.get('/api/v1/challenges/1/solves') + resp = r.get_json()['data'] + solve = resp[0] + assert r.status_code == 200 + assert solve.get('account_id') == 1 + assert solve.get('name') == 'team_name' + assert solve.get('date') is not None + assert solve.get('account_url') == '/teams/1' + destroy_ctfd(app) + + app = create_ctfd(application_root='/ctf') + with app.app_context(): + register_user(app) + client = login_as_user(app) + chal = gen_challenge(app.db) + gen_solve(app.db, user_id=2, challenge_id=chal.id) + r = client.get('/api/v1/challenges/1/solves') + resp = r.get_json()['data'] + solve = resp[0] + assert r.status_code == 200 + assert solve.get('account_id') == 2 + assert solve.get('name') == 'user' + assert solve.get('date') is not None + assert solve.get('account_url') == '/ctf/users/2' + destroy_ctfd(app) + + def test_api_challenge_get_files_non_admin(): """Can a user get /api/v1/challenges//files if not admin""" app = create_ctfd()