Repository: CTFd/CTFd

Issue:
Hiding scores config needs to be more expansive
Score hiding needs to cover a few more routes. 


Discussion:

Resolving patch:
diff --git a/CTFd/challenges.py b/CTFd/challenges.py
--- a/CTFd/challenges.py
+++ b/CTFd/challenges.py
@@ -144,9 +144,21 @@ def solves_per_chal():
     if not utils.user_can_view_challenges():
         return redirect(url_for('auth.login', next=request.path))
 
-    solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves')).join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).group_by(Solves.chalid).subquery()
-    solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \
-                       .join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
+    solves_sub = db.session.query(
+        Solves.chalid,
+        db.func.count(Solves.chalid).label('solves')
+    )\
+        .join(Teams, Solves.teamid == Teams.id) \
+        .filter(Teams.banned == False) \
+        .group_by(Solves.chalid).subquery()
+
+    solves = db.session.query(
+        solves_sub.columns.chalid,
+        solves_sub.columns.solves,
+        Challenges.name
+    ) \
+        .join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
+
     json = {}
     if utils.hide_scores():
         for chal, count, name in solves:
@@ -174,18 +186,23 @@ def solves(teamid=None):
         else:
             return redirect(url_for('auth.login', next='solves'))
     else:
-        solves = Solves.query.filter_by(teamid=teamid)
-        awards = Awards.query.filter_by(teamid=teamid)
-
-        freeze = utils.get_config('freeze')
-        if freeze:
-            freeze = utils.unix_time_to_utc(freeze)
-            if teamid != session.get('id'):
-                solves = solves.filter(Solves.date < freeze)
-                awards = awards.filter(Awards.date < freeze)
-
-        solves = solves.all()
-        awards = awards.all()
+        if utils.hide_scores():
+            # Use empty values to hide scores
+            solves = []
+            awards = []
+        else:
+            solves = Solves.query.filter_by(teamid=teamid)
+            awards = Awards.query.filter_by(teamid=teamid)
+
+            freeze = utils.get_config('freeze')
+            if freeze:
+                freeze = utils.unix_time_to_utc(freeze)
+                if teamid != session.get('id'):
+                    solves = solves.filter(Solves.date < freeze)
+                    awards = awards.filter(Awards.date < freeze)
+
+            solves = solves.all()
+            awards = awards.all()
     db.session.close()
     json = {'solves': []}
     for solve in solves:
@@ -224,10 +241,19 @@ def attempts():
     return jsonify(json)
 
 
-@challenges.route('/fails/<int:teamid>', methods=['GET'])
-def fails(teamid):
-    fails = WrongKeys.query.filter_by(teamid=teamid).count()
-    solves = Solves.query.filter_by(teamid=teamid).count()
+@challenges.route('/fails')
+@challenges.route('/fails/<int:teamid>')
+def fails(teamid=None):
+    if teamid is None:
+        fails = WrongKeys.query.filter_by(teamid=session['id']).count()
+        solves = Solves.query.filter_by(teamid=session['id']).count()
+    else:
+        if utils.hide_scores():
+            fails = 0
+            solves = 0
+        else:
+            fails = WrongKeys.query.filter_by(teamid=teamid).count()
+            solves = Solves.query.filter_by(teamid=teamid).count()
     db.session.close()
     json = {'fails': str(fails), 'solves': str(solves)}
     return jsonify(json)


Test patch:
diff --git a/tests/user/test_challenges.py b/tests/user/test_challenges.py
--- a/tests/user/test_challenges.py
+++ b/tests/user/test_challenges.py
@@ -45,6 +45,44 @@ def test_viewing_challenges():
     destroy_ctfd(app)
 
 
+def test_chals_solves():
+    '''Test that the /chals/solves endpoint works properly'''
+    app = create_ctfd()
+    with app.app_context():
+        # Generate 5 users
+        for c in range(1, 6):
+            name = "user{}".format(c)
+            email = "user{}@ctfd.io".format(c)
+            register_user(app, name=name, email=email, password="password")
+
+        # Generate 5 challenges
+        for c in range(5):
+            chal1 = gen_challenge(app.db, value=100)
+
+        user_ids = list(range(2, 7))
+        chal_ids = list(range(1, 6))
+        for u in user_ids:
+            for c in chal_ids:
+                gen_solve(app.db, teamid=u, chalid=c)
+            chal_ids.pop()
+
+        client = login_as_user(app, name="user1")
+
+        with client.session_transaction() as sess:
+            r = client.get('/chals/solves')
+            output = r.get_data(as_text=True)
+            saved = json.loads('''{
+              "1": 5,
+              "2": 4,
+              "3": 3,
+              "4": 2,
+              "5": 1
+            }
+            ''')
+            received = json.loads(output)
+            assert saved == received
+
+
 def test_submitting_correct_flag():
     """Test that correct flags are correct"""
     app = create_ctfd()