diff --git a/CTFd/challenges.py b/CTFd/challenges.py index e27f3749..69b4c2f6 100644 --- a/CTFd/challenges.py +++ b/CTFd/challenges.py @@ -36,31 +36,27 @@ def hints_view(hintid): 'cost': hint.cost }) elif request.method == 'POST': - if not unlock and utils.ctftime(): - team = Teams.query.filter_by(id=session['id']).first() - if team.score() < hint.cost: - return jsonify({'errors': 'Not enough points'}) - unlock = Unlocks(model='hints', teamid=session['id'], itemid=hint.id) - award = Awards(teamid=session['id'], name=text_type('Hint for {}'.format(chal.name)), value=(-hint.cost)) - db.session.add(unlock) - db.session.add(award) - db.session.commit() - json_data = { - 'hint': hint.hint, - 'chal': hint.chal, - 'cost': hint.cost - } - db.session.close() - return jsonify(json_data) - elif utils.ctf_ended(): - json_data = { - 'hint': hint.hint, - 'chal': hint.chal, - 'cost': hint.cost - } - db.session.close() - return jsonify(json_data) - else: + if unlock is None: # The user does not have an unlock. + if utils.ctftime() or (utils.ctf_ended() and utils.view_after_ctf()): + # It's ctftime or the CTF has ended (but we allow views after) + team = Teams.query.filter_by(id=session['id']).first() + if team.score() < hint.cost: + return jsonify({'errors': 'Not enough points'}) + unlock = Unlocks(model='hints', teamid=session['id'], itemid=hint.id) + award = Awards(teamid=session['id'], name=text_type('Hint for {}'.format(chal.name)), value=(-hint.cost)) + db.session.add(unlock) + db.session.add(award) + db.session.commit() + json_data = { + 'hint': hint.hint, + 'chal': hint.chal, + 'cost': hint.cost + } + db.session.close() + return jsonify(json_data) + elif utils.ctf_ended(): # The CTF has ended. No views after. + abort(403) + else: # The user does have an unlock, we should give them their hint. json_data = { 'hint': hint.hint, 'chal': hint.chal, diff --git a/tests/helpers.py b/tests/helpers.py index e614363d..040c82e4 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -95,6 +95,7 @@ def gen_challenge(db, name='chal_name', description='chal_description', value=10 def gen_award(db, teamid, name="award_name", value=100): award = Awards(teamid, name, value) + award.date = datetime.datetime.utcnow() db.session.add(award) db.session.commit() return award diff --git a/tests/user/test_challenges.py b/tests/user/test_challenges.py index 7c96ba54..83ad6324 100644 --- a/tests/user/test_challenges.py +++ b/tests/user/test_challenges.py @@ -271,7 +271,7 @@ def test_submitting_flags_with_large_ips(): def test_unlocking_hints_with_no_cost(): - '''Test that hints with no cost can be unlocked''' + """Test that hints with no cost can be unlocked""" app = create_ctfd() with app.app_context(): register_user(app) @@ -291,8 +291,110 @@ def test_unlocking_hints_with_no_cost(): destroy_ctfd(app) +def test_unlocking_hints_with_cost_during_ctf_with_points(): + """Test that hints with a cost are unlocked if you have the points""" + app = create_ctfd() + with app.app_context(): + register_user(app) + chal = gen_challenge(app.db) + chal_id = chal.id + hint = gen_hint(app.db, chal_id, cost=10) + gen_award(app.db, teamid=2) + + client = login_as_user(app) + with client.session_transaction() as sess: + data = { + "nonce": sess.get('nonce') + } + r = client.post('/hints/1', data=data) + output = r.get_data(as_text=True) + output = json.loads(output) + assert output.get('hint') == 'This is a hint' + user = Teams.query.filter_by(id=2).first() + assert user.score() == 90 + destroy_ctfd(app) + + +def test_unlocking_hints_with_cost_during_ctf_without_points(): + """Test that hints with a cost are not unlocked if you don't have the points""" + app = create_ctfd() + with app.app_context(): + register_user(app) + chal = gen_challenge(app.db) + chal_id = chal.id + hint = gen_hint(app.db, chal_id, cost=10) + + client = login_as_user(app) + with client.session_transaction() as sess: + data = { + "nonce": sess.get('nonce') + } + r = client.post('/hints/1', data=data) + output = r.get_data(as_text=True) + output = json.loads(output) + assert output.get('errors') == 'Not enough points' + user = Teams.query.filter_by(id=2).first() + assert user.score() == 0 + destroy_ctfd(app) + + +def test_unlocking_hints_with_cost_during_ended_ctf(): + """Test that hints with a cost are not unlocked if the CTF has ended""" + app = create_ctfd() + with app.app_context(): + register_user(app) + chal = gen_challenge(app.db) + chal_id = chal.id + hint = gen_hint(app.db, chal_id, cost=10) + gen_award(app.db, teamid=2) + + set_config('start', '1507089600') # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST + set_config('end', '1507262400') # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST + + with freeze_time("2017-11-4"): + client = login_as_user(app) + with client.session_transaction() as sess: + data = { + "nonce": sess.get('nonce') + } + r = client.post('/hints/1', data=data) + assert r.status_code == 403 + user = Teams.query.filter_by(id=2).first() + assert user.score() == 100 + assert Unlocks.query.count() == 0 + destroy_ctfd(app) + + +def test_unlocking_hints_with_cost_during_frozen_ctf(): + """Test that hints with a cost are unlocked if the CTF is frozen.""" + app = create_ctfd() + with app.app_context(): + set_config('freeze', '1507262400') # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST + with freeze_time("2017-10-4"): + register_user(app) + chal = gen_challenge(app.db) + chal_id = chal.id + hint = gen_hint(app.db, chal_id, cost=10) + gen_award(app.db, teamid=2) + + with freeze_time("2017-10-8"): + client = login_as_user(app) + with client.session_transaction() as sess: + data = { + "nonce": sess.get('nonce') + } + + r = client.post('/hints/1', data=data) + output = r.get_data(as_text=True) + output = json.loads(output) + assert output.get('hint') == 'This is a hint' + user = Teams.query.filter_by(id=2).first() + assert user.score() == 100 + destroy_ctfd(app) + + def test_unlocking_hint_for_unicode_challenge(): - '''Test that hints for challenges with unicode names can be unlocked''' + """Test that hints for challenges with unicode names can be unlocked""" app = create_ctfd() with app.app_context(): register_user(app)