Repository: CTFd/CTFd
Pull request #2422 (2023-11-15 16:21:27)

Fix issue where the API was not respecting score visibility
* Fix issue where the API was not respecting score visibility


Patch:
diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py
index e9e54a57..0518ee2f 100644
--- a/CTFd/models/__init__.py
+++ b/CTFd/models/__init__.py
@@ -421,7 +421,12 @@ class Users(db.Model):
 
     @property
     def score(self):
-        return self.get_score(admin=False)
+        from CTFd.utils.config.visibility import scores_visible
+
+        if scores_visible():
+            return self.get_score(admin=False)
+        else:
+            return None
 
     @property
     def place(self):
@@ -611,7 +616,12 @@ class Teams(db.Model):
 
     @property
     def score(self):
-        return self.get_score(admin=False)
+        from CTFd.utils.config.visibility import scores_visible
+
+        if scores_visible():
+            return self.get_score(admin=False)
+        else:
+            return None
 
     @property
     def place(self):


Test patch:
diff --git a/tests/api/v1/teams/test_scoring.py b/tests/api/v1/teams/test_scoring.py
index 7379c921..6f39880a 100644
--- a/tests/api/v1/teams/test_scoring.py
+++ b/tests/api/v1/teams/test_scoring.py
@@ -6,7 +6,7 @@ from CTFd.utils import set_config
 from tests.helpers import create_ctfd, destroy_ctfd, gen_award, gen_team, login_as_user
 
 
-def test_api_team_place_hidden_if_scores_hidden():
+def test_api_team_place_score_hidden_if_scores_hidden():
     """/api/v1/teams/me should not reveal team place if scores aren't visible"""
     app = create_ctfd(user_mode="teams")
     with app.app_context():
@@ -21,22 +21,65 @@ def test_api_team_place_hidden_if_scores_hidden():
             r = client.get("/api/v1/teams/me", json="")
             resp = r.get_json()
             assert resp["data"]["place"] == "1st"
+            assert resp["data"]["score"] is not None
 
         set_config("score_visibility", "hidden")
         with login_as_user(app, name=u.name) as client:
             r = client.get("/api/v1/teams/me", json="")
             resp = r.get_json()
             assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
 
         set_config("score_visibility", "admins")
         with login_as_user(app, name=u.name) as client:
             r = client.get("/api/v1/teams/me", json="")
             resp = r.get_json()
             assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
 
         with login_as_user(app, name="admin") as client:
             r = client.get("/api/v1/teams/1", json="")
             resp = r.get_json()
             print(resp)
             assert resp["data"]["place"] == "1st"
+            assert resp["data"]["score"] is not None
+    destroy_ctfd(app)
+
+
+def test_api_public_team_place_score_hidden_if_scores_hidden():
+    """/api/v1/teams/<team_id> should not reveal team place if scores aren't visible"""
+    app = create_ctfd(user_mode="teams")
+    with app.app_context():
+        gen_team(app.db)
+        app.db.session.commit()
+
+        gen_award(app.db, user_id=2, team_id=1)
+
+        u = Users.query.filter_by(id=2).first()
+
+        with login_as_user(app, name=u.name) as client:
+            r = client.get("/api/v1/teams/1", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] == "1st"
+            assert resp["data"]["place"] is not None
+
+        set_config("score_visibility", "hidden")
+        with login_as_user(app, name=u.name) as client:
+            r = client.get("/api/v1/teams/1", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
+
+        set_config("score_visibility", "admins")
+        with login_as_user(app, name=u.name) as client:
+            r = client.get("/api/v1/teams/1", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
+
+        with login_as_user(app, name="admin") as client:
+            r = client.get("/api/v1/teams/1", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] == "1st"
+            assert resp["data"]["score"] is not None
     destroy_ctfd(app)
diff --git a/tests/api/v1/users/test_scoring.py b/tests/api/v1/users/test_scoring.py
index 485fbe96..fdfea7ec 100644
--- a/tests/api/v1/users/test_scoring.py
+++ b/tests/api/v1/users/test_scoring.py
@@ -12,7 +12,7 @@ from tests.helpers import (
 )
 
 
-def test_api_user_place_hidden_if_scores_hidden():
+def test_api_user_place_score_hidden_if_scores_hidden():
     """/api/v1/users/me should not reveal user place if scores aren't visible"""
     app = create_ctfd()
     with app.app_context():
@@ -24,21 +24,61 @@ def test_api_user_place_hidden_if_scores_hidden():
             r = client.get("/api/v1/users/me", json="")
             resp = r.get_json()
             assert resp["data"]["place"] == "1st"
+            assert resp["data"]["score"] is not None
 
         set_config("score_visibility", "hidden")
         with login_as_user(app, name="user") as client:
             r = client.get("/api/v1/users/me", json="")
             resp = r.get_json()
             assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
 
         set_config("score_visibility", "admins")
         with login_as_user(app, name="user") as client:
             r = client.get("/api/v1/users/me", json="")
             resp = r.get_json()
             assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
 
         with login_as_user(app, name="admin") as client:
             r = client.get("/api/v1/users/2", json="")
             resp = r.get_json()
             assert resp["data"]["place"] == "1st"
+            assert resp["data"]["score"] is not None
+    destroy_ctfd(app)
+
+
+def test_api_public_user_place_score_hidden_if_scores_hidden():
+    """/api/v1/users/<user_id> should not reveal user place if scores aren't visible"""
+    app = create_ctfd()
+    with app.app_context():
+        register_user(app)
+        user = Users.query.filter_by(id=2).first()
+        simulate_user_activity(app.db, user=user)
+
+        with login_as_user(app, name="user") as client:
+            r = client.get("/api/v1/users/2", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] == "1st"
+            assert resp["data"]["score"] is not None
+
+        set_config("score_visibility", "hidden")
+        with login_as_user(app, name="user") as client:
+            r = client.get("/api/v1/users/2", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
+
+        set_config("score_visibility", "admins")
+        with login_as_user(app, name="user") as client:
+            r = client.get("/api/v1/users/2", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] is None
+            assert resp["data"]["score"] is None
+
+        with login_as_user(app, name="admin") as client:
+            r = client.get("/api/v1/users/2", json="")
+            resp = r.get_json()
+            assert resp["data"]["place"] == "1st"
+            assert resp["data"]["score"] is not None
     destroy_ctfd(app)