From e79f32a5c134a52cfdbdd5da6f6361deea3914ae Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Tue, 27 Nov 2018 03:04:51 -0500 Subject: [PATCH] Fix dynamic challenges to work in user mode and team mode (#760) * Fix dynamic challenges to work in user mode and team mode (Closes #759) * Add test for solving dynamic challenges and challenge de-valuation * Add missing oauth_login link to team_enrollment.html --- CTFd/plugins/dynamic_challenges/__init__.py | 15 +++-- .../core/templates/teams/team_enrollment.html | 2 +- tests/challenges/test_dynamic.py | 55 +++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/CTFd/plugins/dynamic_challenges/__init__.py b/CTFd/plugins/dynamic_challenges/__init__.py index 238f32bb..05c76914 100644 --- a/CTFd/plugins/dynamic_challenges/__init__.py +++ b/CTFd/plugins/dynamic_challenges/__init__.py @@ -7,6 +7,7 @@ from CTFd import utils from CTFd.utils.migrations import upgrade from CTFd.utils.user import get_ip from CTFd.utils.uploads import upload_file, delete_file +from CTFd.utils.modes import get_model from flask import Blueprint import math @@ -92,9 +93,11 @@ class DynamicValueChallenge(BaseChallenge): for attr, value in data.items(): setattr(challenge, attr, value) + Model = get_model() + solve_count = Solves.query \ - .join(Teams, Solves.team_id == Teams.id) \ - .filter(Solves.challenge_id == challenge.id, Teams.banned == False) \ + .join(Model, Solves.account_id == Model.id) \ + .filter(Solves.challenge_id == challenge.id, Model.hidden == False, Model.banned == False) \ .count() # It is important that this calculation takes into account floats. @@ -165,9 +168,11 @@ class DynamicValueChallenge(BaseChallenge): data = request.form or request.get_json() submission = data['submission'].strip() - solve_count = Solves.query\ - .join(Teams, Solves.team_id == Teams.id)\ - .filter(Solves.challenge_id == chal.id, Teams.banned == False)\ + Model = get_model() + + solve_count = Solves.query \ + .join(Model, Solves.account_id == Model.id) \ + .filter(Solves.challenge_id == challenge.id, Model.hidden == False, Model.banned == False) \ .count() # It is important that this calculation takes into account floats. diff --git a/CTFd/themes/core/templates/teams/team_enrollment.html b/CTFd/themes/core/templates/teams/team_enrollment.html index 1875f974..f3b2e03b 100644 --- a/CTFd/themes/core/templates/teams/team_enrollment.html +++ b/CTFd/themes/core/templates/teams/team_enrollment.html @@ -20,7 +20,7 @@
diff --git a/tests/challenges/test_dynamic.py b/tests/challenges/test_dynamic.py index 0f1a8852..f3135fec 100644 --- a/tests/challenges/test_dynamic.py +++ b/tests/challenges/test_dynamic.py @@ -114,3 +114,58 @@ def test_can_delete_dynamic_challenge(): challenges = DynamicChallenge.query.all() assert len(challenges) == 0 destroy_ctfd(app) + + +def test_dynamic_challenge_loses_value_properly(): + app = create_ctfd(enable_plugins=True) + with app.app_context(): + register_user(app) + client = login_as_user(app, name="admin", password="password") + + challenge_data = { + "name": "name", + "category": "category", + "description": "description", + "value": 100, + "decay": 20, + "minimum": 1, + "state": "visible", + "type": "dynamic" + } + + r = client.post('/api/v1/challenges', json=challenge_data) + assert r.get_json().get('data')['id'] == 1 + + flag = gen_flag(app.db, challenge_id=1, content='flag') + + for i, team_id in enumerate(range(2, 26)): + name = "user{}".format(team_id) + email = "user{}@ctfd.io".format(team_id) + # We need to bypass rate-limiting so gen_user instead of register_user + gen_user(app.db, name=name, email=email) + + with app.test_client() as client: + # We need to bypass rate-limiting so creating a fake user instead of logging in + with client.session_transaction() as sess: + sess['id'] = team_id + sess['name'] = name + sess['type'] = 'user' + sess['email'] = email + sess['nonce'] = 'fake-nonce' + + data = { + "submission": 'flag', + "challenge_id": 1 + } + + r = client.post('/api/v1/challenges/attempt', json=data) + resp = r.get_json()['data'] + assert resp['status'] == 'correct' + + chal = DynamicChallenge.query.filter_by(id=1).first() + if i >= 20: + assert chal.value == chal.minimum + else: + assert chal.initial >= chal.value + assert chal.value > chal.minimum + destroy_ctfd(app)