From ed5d68700f58d2cd5f0ae10b8cc2cbcde9261b43 Mon Sep 17 00:00:00 2001
From: Mattia Verga <mattia.verga@tiscali.it>
Date: Jan 21 2024 23:51:13 +0000
Subject: Add verify_token API endpoint


Signed-off-by: Mattia Verga <mattia.verga@tiscali.it>

---

diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py
index 14486f0..c711be6 100644
--- a/pagure/api/__init__.py
+++ b/pagure/api/__init__.py
@@ -557,6 +557,7 @@ def api():
         project.api_project_block_user,
         project.api_get_project_webhook_token,
         project.api_project_create_api_token,
+        project.api_project_verify_api_token,
         project.api_commit_info,
         project.api_view_file,
         project.delete_project,
diff --git a/pagure/api/project.py b/pagure/api/project.py
index 52bf413..785ebb1 100644
--- a/pagure/api/project.py
+++ b/pagure/api/project.py
@@ -3214,6 +3214,51 @@ def api_project_create_api_token(repo, namespace=None, username=None):
     return jsonout
 
 
+@API.route("/<repo>/token/verify", methods=["POST"])
+@API.route("/<namespace>/<repo>/token/verify", methods=["POST"])
+@API.route("/fork/<username>/<repo>/token/verify", methods=["POST"])
+@API.route(
+    "/fork/<username>/<namespace>/<repo>/token/verify", methods=["POST"]
+)
+@api_method
+@api_login_required(acls=["modify_project"])
+def api_project_verify_api_token(repo, namespace=None, username=None):
+    """
+    Verify API project Token
+    ------------------------
+    Verify validity of a token API for the specified project
+
+    It does not verify token scope, but can be used to check if the
+    token exists or is expired.
+
+    ::
+
+        POST /api/0/<repo>/token/verify
+        POST /api/0/<namespace>/<repo>/token/verify
+
+    ::
+
+        POST /api/0/fork/<username>/<repo>/token/verify
+        POST /api/0/fork/<username>/<namespace>/<repo>/token/verify
+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {"message": "Token is valid"}
+
+    """
+    output = {"message": "Token is valid"}
+
+    project = _get_repo(repo, username, namespace)
+    _check_token(project, project_token=False)
+
+    jsonout = flask.jsonify(output)
+    return jsonout
+
+
 @API.route("/<repo>/blockuser", methods=["POST"])
 @API.route("/<namespace>/<repo>/blockuser", methods=["POST"])
 @API.route("/fork/<username>/<repo>/blockuser", methods=["POST"])
diff --git a/pagure/api/utils.py b/pagure/api/utils.py
index 2df505a..da590ee 100644
--- a/pagure/api/utils.py
+++ b/pagure/api/utils.py
@@ -49,15 +49,14 @@ def _check_token(repo, project_token=True):
         otherwise any token can be used
     :raises pagure.exceptions.APIError: when token is not valid for repo
     """
-    if api_authenticated():
-        # if there is a project associated with the token, check it
-        # if there is no project associated, check if it is required
-        if (
+    if (
+        not api_authenticated()
+        or (
             flask.g.token.project is not None and repo != flask.g.token.project
-        ) or (flask.g.token.project is None and project_token):
-            raise pagure.exceptions.APIError(
-                401, error_code=APIERROR.EINVALIDTOK
-            )
+        )
+        or (flask.g.token.project is None and project_token)
+    ):
+        raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)
 
 
 def _get_issue(repo, issueid, issueuid=None):
diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py
index 2227dc6..4664941 100644
--- a/tests/test_pagure_flask_api_project.py
+++ b/tests/test_pagure_flask_api_project.py
@@ -4722,6 +4722,65 @@ class PagureFlaskApiProjectCreateAPITokenTests(tests.Modeltests):
         self.assertEqual(output.status_code, 401)
 
 
+class PagureFlaskApiProjectVerifyAPITokenTests(tests.Modeltests):
+    """Tests for the flask API of pagure for verifying project API token validity"""
+
+    maxDiff = None
+
+    def setUp(self):
+        """Set up the environnment, ran before every tests."""
+        super(PagureFlaskApiProjectVerifyAPITokenTests, self).setUp()
+        tests.create_projects(self.session)
+        tests.create_tokens(self.session, project_id=None)
+        tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
+
+    def test_api_verifyapitoken_valid(self):
+        """Test accessing api_project_verify_api_token with valid token."""
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+
+        output = self.app.post("/api/0/test/token/verify", headers=headers)
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertEqual(data, {"message": "Token is valid"})
+
+    def test_api_verifyapitoken_not_valid(self):
+        """Test accessing api_project_verify_api_token with invalid token."""
+
+        headers = {"Authorization": "token dddcccbbbaaa"}
+
+        output = self.app.post("/api/0/test/token/verify", headers=headers)
+        self.assertEqual(output.status_code, 401)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or expired token. Please visit "
+                "http://localhost.localdomain/settings#nav-api-tab to get or renew "
+                "your API token.",
+                "error_code": "EINVALIDTOK",
+                "errors": "Invalid token",
+            },
+        )
+
+    def test_api_verifyapitoken_no_headers(self):
+        """Test accessing api_project_verify_api_token without passing headers."""
+
+        output = self.app.post("/api/0/test/token/verify")
+        self.assertEqual(output.status_code, 401)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or expired token. Please visit "
+                "http://localhost.localdomain/settings#nav-api-tab to get or renew "
+                "your API token.",
+                "error_code": "EINVALIDTOK",
+                "errors": "Invalid token",
+            },
+        )
+
+
 class PagureFlaskApiProjectConnectorTests(tests.Modeltests):
     """Tests for the flask API of pagure for getting connector of a project"""
 

