Repository: CTFd/CTFd
Pull request #2393 (2023-08-29 06:22:04)

Allow user/team emails to be queried via API by admin users
* Allow user/team emails to be queried via API by admin users
* Add `email` as an acceptable `field` to query to `/api/v1/users` and `/api/v1/teams`

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/CTFd/CTFd/pull/2393?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd) Report
> Merging [#2393](https://app.codecov.io/gh/CTFd/CTFd/pull/2393?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd) (75aca22) into [master](https://app.codecov.io/gh/CTFd/CTFd/commit/af5e88de115f332427894284e681ac10bb81670e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd) (af5e88d) will **decrease** coverage by `2.57%`.
> The diff coverage is `33.33%`.

```diff
@@            Coverage Diff             @@
##           master    #2393      +/-   ##
==========================================
- Coverage   87.61%   85.05%   -2.57%     
==========================================
  Files         149      147       -2     
  Lines        9119     9073      -46     
==========================================
- Hits         7990     7717     -273     
- Misses       1129     1356     +227     
```


| [Files Changed](https://app.codecov.io/gh/CTFd/CTFd/pull/2393?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd) | Coverage Δ | |
|---|---|---|
| [CTFd/api/v1/teams.py](https://app.codecov.io/gh/CTFd/CTFd/pull/2393?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd#diff-Q1RGZC9hcGkvdjEvdGVhbXMucHk=) | `88.92% <33.33%> (-0.50%)` | :arrow_down: |
| [CTFd/api/v1/users.py](https://app.codecov.io/gh/CTFd/CTFd/pull/2393?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd#diff-Q1RGZC9hcGkvdjEvdXNlcnMucHk=) | `92.51% <33.33%> (-0.71%)` | :arrow_down: |

... and [16 files with indirect coverage changes](https://app.codecov.io/gh/CTFd/CTFd/pull/2393/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd)

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CTFd)


Patch:
diff --git a/CTFd/api/v1/teams.py b/CTFd/api/v1/teams.py
index a3a2ea37..d787c443 100644
--- a/CTFd/api/v1/teams.py
+++ b/CTFd/api/v1/teams.py
@@ -84,6 +84,7 @@ class TeamList(Resource):
                         "country": "country",
                         "bracket": "bracket",
                         "affiliation": "affiliation",
+                        "email": "email",
                     },
                 ),
                 None,
@@ -94,6 +95,14 @@ class TeamList(Resource):
     def get(self, query_args):
         q = query_args.pop("q", None)
         field = str(query_args.pop("field", None))
+
+        if field == "email":
+            if is_admin() is False:
+                return {
+                    "success": False,
+                    "errors": {"field": "Emails can only be queried by admins"},
+                }, 400
+
         filters = build_model_filters(model=Teams, query=q, field=field)
 
         if is_admin() and request.args.get("view") == "admin":
diff --git a/CTFd/api/v1/users.py b/CTFd/api/v1/users.py
index 954c8ee9..762aeb77 100644
--- a/CTFd/api/v1/users.py
+++ b/CTFd/api/v1/users.py
@@ -87,6 +87,7 @@ class UserList(Resource):
                         "country": "country",
                         "bracket": "bracket",
                         "affiliation": "affiliation",
+                        "email": "email",
                     },
                 ),
                 None,
@@ -97,6 +98,14 @@ class UserList(Resource):
     def get(self, query_args):
         q = query_args.pop("q", None)
         field = str(query_args.pop("field", None))
+
+        if field == "email":
+            if is_admin() is False:
+                return {
+                    "success": False,
+                    "errors": {"field": "Emails can only be queried by admins"},
+                }, 400
+
         filters = build_model_filters(model=Users, query=q, field=field)
 
         if is_admin() and request.args.get("view") == "admin":


Test patch:
diff --git a/tests/api/v1/teams/test_teams.py b/tests/api/v1/teams/test_teams.py
new file mode 100644
index 00000000..fe9e2614
--- /dev/null
+++ b/tests/api/v1/teams/test_teams.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from tests.helpers import (
+    create_ctfd,
+    destroy_ctfd,
+    gen_team,
+    login_as_user,
+    register_user,
+)
+
+
+def test_api_can_query_by_team_emails():
+    """Can an admin user query /api/v1/teams using a teams's email address"""
+    app = create_ctfd(user_mode="teams")
+    with app.app_context():
+        gen_team(app.db, email="team@findme.com")
+        register_user(app, name="testuser", email="user@findme.com")
+        with login_as_user(app, "testuser") as client:
+            r = client.get("/api/v1/teams?field=email&q=findme", json=True)
+            assert r.status_code == 400
+            assert r.get_json()["errors"].get("field")
+        with login_as_user(app, "admin") as client:
+            r = client.get("/api/v1/teams?field=email&q=findme", json=True)
+            assert r.status_code == 200
+            assert r.get_json()["data"][0]["id"] == 1
+            assert r.get_json()["data"][0]["name"] == "team_name"
+    destroy_ctfd(app)
diff --git a/tests/api/v1/users/test_users.py b/tests/api/v1/users/test_users.py
index 2ca65aae..bbbe4279 100644
--- a/tests/api/v1/users/test_users.py
+++ b/tests/api/v1/users/test_users.py
@@ -35,3 +35,19 @@ def test_api_modify_user_type():
             assert user_data["name"] == "user"
             assert user_data["type"] == "user"
     destroy_ctfd(app)
+
+
+def test_api_can_query_by_user_emails():
+    """Can an admin user query /api/v1/users using a user's email address"""
+    app = create_ctfd()
+    with app.app_context():
+        register_user(app, name="testuser", email="user@findme.com")
+        with login_as_user(app, "testuser") as client:
+            r = client.get("/api/v1/users?field=email&q=findme", json=True)
+            assert r.status_code == 400
+            assert r.get_json()["errors"].get("field")
+        with login_as_user(app, "admin") as client:
+            r = client.get("/api/v1/users?field=email&q=findme", json=True)
+            assert r.status_code == 200
+            assert r.get_json()["data"][0]["id"] == 2
+    destroy_ctfd(app)