From 50f75be5ebf1ad6f7427e89bd2496c48fa83b520 Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Fri, 29 May 2020 11:06:04 -0400 Subject: [PATCH] 1423 model filter bypass (#1451) * Add `view=admin` GET param to `/api/v1/users`, `/api/v1/teams`, and `/api/v1/challenges` to bypass filtering for admins * Closes #1423 #1445 * Related to #1165 --- CTFd/api/v1/challenges.py | 47 ++++++++++++++++++--------------- CTFd/api/v1/teams.py | 6 ++++- CTFd/api/v1/users.py | 6 ++++- tests/api/v1/test_challenges.py | 19 +++++++++++++ tests/api/v1/test_teams.py | 7 +++++ tests/api/v1/test_users.py | 14 ++++++++++ 6 files changed, 76 insertions(+), 23 deletions(-) diff --git a/CTFd/api/v1/challenges.py b/CTFd/api/v1/challenges.py index 9868476e..411c0079 100644 --- a/CTFd/api/v1/challenges.py +++ b/CTFd/api/v1/challenges.py @@ -47,31 +47,36 @@ class ChallengeList(Resource): # This can return None (unauth) if visibility is set to public user = get_current_user() - challenges = ( - Challenges.query.filter( - and_(Challenges.state != "hidden", Challenges.state != "locked") - ) - .order_by(Challenges.value) - .all() - ) - - if user: - solve_ids = ( - Solves.query.with_entities(Solves.challenge_id) - .filter_by(account_id=user.account_id) - .order_by(Solves.challenge_id.asc()) + # Admins can request to see everything + if is_admin() and request.args.get("view") == "admin": + challenges = Challenges.query.order_by(Challenges.value).all() + solve_ids = set([challenge.id for challenge in challenges]) + else: + challenges = ( + Challenges.query.filter( + and_(Challenges.state != "hidden", Challenges.state != "locked") + ) + .order_by(Challenges.value) .all() ) - solve_ids = set([value for value, in solve_ids]) - # TODO: Convert this into a re-useable decorator - if is_admin(): - pass + if user: + solve_ids = ( + Solves.query.with_entities(Solves.challenge_id) + .filter_by(account_id=user.account_id) + .order_by(Solves.challenge_id.asc()) + .all() + ) + solve_ids = set([value for value, in solve_ids]) + + # TODO: Convert this into a re-useable decorator + if is_admin(): + pass + else: + if config.is_teams_mode() and get_current_team() is None: + abort(403) else: - if config.is_teams_mode() and get_current_team() is None: - abort(403) - else: - solve_ids = set() + solve_ids = set() response = [] tag_schema = TagSchema(view="user", many=True) diff --git a/CTFd/api/v1/teams.py b/CTFd/api/v1/teams.py index d387f81a..cba196b1 100644 --- a/CTFd/api/v1/teams.py +++ b/CTFd/api/v1/teams.py @@ -22,7 +22,11 @@ teams_namespace = Namespace("teams", description="Endpoint to retrieve Teams") class TeamList(Resource): @check_account_visibility def get(self): - teams = Teams.query.filter_by(hidden=False, banned=False) + if is_admin() and request.args.get("view") == "admin": + teams = Teams.query.filter_by() + else: + teams = Teams.query.filter_by(hidden=False, banned=False) + user_type = get_current_user_type(fallback="user") view = copy.deepcopy(TeamSchema.views.get(user_type)) view.remove("members") diff --git a/CTFd/api/v1/users.py b/CTFd/api/v1/users.py index 20e39980..bab9fdc4 100644 --- a/CTFd/api/v1/users.py +++ b/CTFd/api/v1/users.py @@ -31,7 +31,11 @@ users_namespace = Namespace("users", description="Endpoint to retrieve Users") class UserList(Resource): @check_account_visibility def get(self): - users = Users.query.filter_by(banned=False, hidden=False) + if is_admin() and request.args.get("view") == "admin": + users = Users.query.filter_by() + else: + users = Users.query.filter_by(banned=False, hidden=False) + response = UserSchema(view="user", many=True).dump(users) if response.errors: diff --git a/tests/api/v1/test_challenges.py b/tests/api/v1/test_challenges.py index 4987f5c3..31dbed8e 100644 --- a/tests/api/v1/test_challenges.py +++ b/tests/api/v1/test_challenges.py @@ -135,6 +135,25 @@ def test_api_challenges_get_admin(): destroy_ctfd(app) +def test_api_challenges_get_hidden_admin(): + """Can an admin see hidden challenges in API list response""" + app = create_ctfd() + with app.app_context(): + gen_challenge(app.db, state="hidden") + gen_challenge(app.db) + + with login_as_user(app, "admin") as admin: + challenges_list = admin.get("/api/v1/challenges", json="").get_json()[ + "data" + ] + assert len(challenges_list) == 1 + challenges_list = admin.get( + "/api/v1/challenges?view=admin", json="" + ).get_json()["data"] + assert len(challenges_list) == 2 + destroy_ctfd(app) + + def test_api_challenges_post_admin(): """Can a user post /api/v1/challenges if admin""" app = create_ctfd() diff --git a/tests/api/v1/test_teams.py b/tests/api/v1/test_teams.py index a67e6db5..a701a469 100644 --- a/tests/api/v1/test_teams.py +++ b/tests/api/v1/test_teams.py @@ -696,6 +696,9 @@ def test_api_accessing_hidden_banned_users(): app.db.session.commit() with login_as_user(app, name="visible_user") as client: + list_teams = client.get("/api/v1/teams").get_json()["data"] + assert len(list_teams) == 0 + assert client.get("/api/v1/teams/1").status_code == 404 assert client.get("/api/v1/teams/1/solves").status_code == 404 assert client.get("/api/v1/teams/1/fails").status_code == 404 @@ -707,6 +710,10 @@ def test_api_accessing_hidden_banned_users(): assert client.get("/api/v1/teams/2/awards").status_code == 404 with login_as_user(app, name="admin") as client: + # Admins see hidden teams in lists + list_users = client.get("/api/v1/teams?view=admin").get_json()["data"] + assert len(list_users) == 2 + assert client.get("/api/v1/teams/1").status_code == 200 assert client.get("/api/v1/teams/1/solves").status_code == 200 assert client.get("/api/v1/teams/1/fails").status_code == 200 diff --git a/tests/api/v1/test_users.py b/tests/api/v1/test_users.py index f05c83b0..da2a8bc5 100644 --- a/tests/api/v1/test_users.py +++ b/tests/api/v1/test_users.py @@ -764,12 +764,19 @@ def test_api_accessing_hidden_users(): app.db.session.commit() with login_as_user(app, name="visible_user") as client: + list_users = client.get("/api/v1/users").get_json()["data"] + assert len(list_users) == 1 + assert client.get("/api/v1/users/3").status_code == 404 assert client.get("/api/v1/users/3/solves").status_code == 404 assert client.get("/api/v1/users/3/fails").status_code == 404 assert client.get("/api/v1/users/3/awards").status_code == 404 with login_as_user(app, name="admin") as client: + # Admins see the user in lists + list_users = client.get("/api/v1/users?view=admin").get_json()["data"] + assert len(list_users) == 3 + assert client.get("/api/v1/users/3").status_code == 200 assert client.get("/api/v1/users/3/solves").status_code == 200 assert client.get("/api/v1/users/3/fails").status_code == 200 @@ -788,12 +795,19 @@ def test_api_accessing_banned_users(): app.db.session.commit() with login_as_user(app, name="visible_user") as client: + list_users = client.get("/api/v1/users").get_json()["data"] + assert len(list_users) == 1 + assert client.get("/api/v1/users/3").status_code == 404 assert client.get("/api/v1/users/3/solves").status_code == 404 assert client.get("/api/v1/users/3/fails").status_code == 404 assert client.get("/api/v1/users/3/awards").status_code == 404 with login_as_user(app, name="admin") as client: + # Admins see the user in lists + list_users = client.get("/api/v1/users?view=admin").get_json()["data"] + assert len(list_users) == 3 + assert client.get("/api/v1/users/3").status_code == 200 assert client.get("/api/v1/users/3/solves").status_code == 200 assert client.get("/api/v1/users/3/fails").status_code == 200