Repository: CTFd/CTFd
Pull request #333 (2017-08-06 08:18:48)

Users should always be able to get their score, even if banned
I noticed that admins were unable to unlock hints. This is because admins effectively got a zero score in terms of the game because admins are banned by default. 

This raises an interesting question that has been raised before, should bans have any effects on users besides hiding from the scoreboard? 

Instead of not getting score if banned, get score for any user, even if they are banned. 

This should come with tests for unlocking hints. 


Patch:
diff --git a/CTFd/models.py b/CTFd/models.py
index 70a9ca5d..f56e91c4 100644
--- a/CTFd/models.py
+++ b/CTFd/models.py
@@ -178,7 +178,7 @@ class Teams(db.Model):
 
     def score(self, admin=False):
         score = db.func.sum(Challenges.value).label('score')
-        team = db.session.query(Solves.teamid, score).join(Teams).join(Challenges).filter(Teams.banned == False, Teams.id == self.id)
+        team = db.session.query(Solves.teamid, score).join(Teams).join(Challenges).filter(Teams.id == self.id)
         award_score = db.func.sum(Awards.value).label('award_score')
         award = db.session.query(award_score).filter_by(teamid=self.id)
 
@@ -193,8 +193,12 @@ class Teams(db.Model):
         team = team.group_by(Solves.teamid).first()
         award = award.first()
 
-        if team:
+        if team and award:
             return int(team.score or 0) + int(award.award_score or 0)
+        elif team:
+            return int(team.score or 0)
+        elif award:
+            return int(award.award_score or 0)
         else:
             return 0
 
diff --git a/CTFd/themes/original/templates/team.html b/CTFd/themes/original/templates/team.html
index 0c90dbaf..b9614c61 100644
--- a/CTFd/themes/original/templates/team.html
+++ b/CTFd/themes/original/templates/team.html
@@ -61,7 +61,7 @@
 				{% endif %}
 						<p class="text-center"><strong>{{ award.name }}</strong></p>
 						{% if award.category %}<p class="text-center">{{ award.category }}</p>{% endif %}
-						<p class="text-center">{{ award.description }}</p>
+						{% if award.description %}<p class="text-center">{{ award.description }}</p>{% endif %}
 						<p class="text-center">{{ award.value }}</p>
 					</div>
 			{% endfor %}


Test patch:
diff --git a/tests/helpers.py b/tests/helpers.py
index 864fc606..abc0defb 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -101,6 +101,13 @@ def gen_team(db, name='name', email='user@ctfd.io', password='password'):
     return team
 
 
+def gen_hint(db, chal, hint="This is a hint", cost=0, type=0):
+    hint = Hints(chal, hint, cost, type)
+    db.session.add(hint)
+    db.session.commit()
+    return hint
+
+
 def gen_solve(db, teamid, chalid, ip='127.0.0.1', flag='rightkey'):
     solve = Solves(teamid, chalid, ip, flag)
     db.session.add(solve)
diff --git a/tests/test_user_facing.py b/tests/test_user_facing.py
index 1140a915..ca590097 100644
--- a/tests/test_user_facing.py
+++ b/tests/test_user_facing.py
@@ -495,3 +495,61 @@ def test_ctfd_setup_redirect():
             r = client.get('/themes/original/static/css/style.css')
             assert r.status_code == 200
     destroy_ctfd(app)
+
+
+def test_user_cannot_unlock_hint():
+    """Test that a user can't unlock a hint if they don't have enough points"""
+    app = create_ctfd()
+    with app.app_context():
+        with app.test_client() as client:
+            register_user(app, name="user1", email="user1@ctfd.io")
+
+            chal = gen_challenge(app.db, value=100)
+            chal_id = chal.id
+
+            flag = gen_flag(app.db, chal=chal.id, flag='flag')
+
+            hint = gen_hint(db, chal_id, cost=10)
+            hint_id = hint.id
+
+            client = login_as_user(app, name="user1", password="password")
+
+            with client.session_transaction() as sess:
+                data = {
+                    "nonce": sess.get('nonce')
+                }
+                r = client.post('/hints/{}'.format(hint_id), data=data)
+                resp = json.loads(r.data.decode('utf8'))
+                assert resp.get('errors') == 'Not enough points'
+    destroy_ctfd(app)
+
+
+def test_user_can_unlock_hint():
+    """Test that a user can unlock a hint if they have enough points"""
+    app = create_ctfd()
+    with app.app_context():
+        with app.test_client() as client:
+            register_user(app, name="user1", email="user1@ctfd.io")
+
+            chal = gen_challenge(app.db, value=100)
+            chal_id = chal.id
+
+            flag = gen_flag(app.db, chal=chal.id, flag='flag')
+
+            hint = gen_hint(app.db, chal_id, cost=10)
+            hint_id = hint.id
+
+            award = gen_award(app.db, teamid=2, value=15)
+
+            client = login_as_user(app, name="user1", password="password")
+
+            with client.session_transaction() as sess:
+                data = {
+                    "nonce": sess.get('nonce')
+                }
+                r = client.post('/hints/{}'.format(hint_id), data=data)
+                resp = json.loads(r.data.decode('utf8'))
+                assert resp.get('errors') is None
+                assert resp.get('hint')
+                assert resp.get('chal') == chal_id
+    destroy_ctfd(app)