Browse Source

Add an API endpoint to adjust the list of blocked users on a project

Signed-off-by: Pierre-Yves Chibon <pingou@pingoured.fr>
Pierre-Yves Chibon 5 years ago
parent
commit
4b7c3efee6
3 changed files with 298 additions and 0 deletions
  1. 2 0
      pagure/api/__init__.py
  2. 88 0
      pagure/api/project.py
  3. 208 0
      tests/test_pagure_flask_api_project_blockuser.py

+ 2 - 0
pagure/api/__init__.py

@@ -531,6 +531,7 @@ def api():
     api_modify_project_options_doc = load_doc(
         project.api_modify_project_options
     )
+    api_project_block_user_doc = load_doc(project.api_project_block_user)
 
     issues = []
     if pagure_config.get("ENABLE_TICKETS", True):
@@ -620,6 +621,7 @@ def api():
             api_update_project_watchers_doc,
             api_get_project_options_doc,
             api_modify_project_options_doc,
+            api_project_block_user_doc,
         ],
         issues=issues,
         requests=[

+ 88 - 0
pagure/api/project.py

@@ -2146,3 +2146,91 @@ def api_project_create_api_token(repo, namespace=None, username=None):
 
     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"])
+@API.route("/fork/<username>/<namespace>/<repo>/blockuser", methods=["POST"])
+@api_login_required(acls=["modify_project"])
+@api_method
+def api_project_block_user(repo, namespace=None, username=None):
+    """
+    Block an user from a project
+    ----------------------------
+    Block an user from interacting with the project
+
+    This is restricted to project admins.
+
+    ::
+
+        POST /api/0/<repo>/blockuser
+        POST /api/0/<namespace>/<repo>/blockuser
+
+    ::
+
+        POST /api/0/fork/<username>/<repo>/blockuser
+        POST /api/0/fork/<username>/<namespace>/<repo>/blockuser
+
+
+    Input
+    ^^^^^
+
+    +------------------+---------+---------------+---------------------------+
+    | Key              | Type    | Optionality   | Description               |
+    +==================+=========+===============+===========================+
+    | ``username``     | String  | optional      | The username of the user  |
+    |                  |         |               | to block on this project  |
+    +------------------+---------+---------------+---------------------------+
+
+    Beware that this API endpoint updates **all** the users blocked in the
+    project, so if you are updating this list, do not submit just one username,
+    submit the updated list.
+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {"message": "User(s) blocked"}
+
+    """
+    output = {}
+
+    project = _get_repo(repo, username, namespace)
+    _check_token(project)
+
+    authorized_users = [project.user.username]
+    authorized_users.extend(
+        [user.user for user in project.access_users["admin"]]
+    )
+    if flask.g.fas_user.username not in authorized_users:
+        raise pagure.exceptions.APIError(
+            401, error_code=APIERROR.ENOTHIGHENOUGH
+        )
+
+    usernames = flask.request.form.getlist("username")
+
+    try:
+        users = set()
+        for user in usernames:
+            user = user.strip()
+            if user:
+                pagure.lib.query.get_user(flask.g.session, user)
+                users.add(user)
+        project.block_users = list(users)
+        flask.g.session.add(project)
+        flask.g.session.commit()
+        output = {"message": "User(s) blocked"}
+    except pagure.exceptions.PagureException as err:
+        raise pagure.exceptions.APIError(
+            400, error_code=APIERROR.ENOCODE, error=str(err)
+        )
+    except SQLAlchemyError as err:  # pragma: no cover
+        flask.g.session.rollback()
+        _log.exception(err)
+        raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
+
+    jsonout = flask.jsonify(output)
+    return jsonout

+ 208 - 0
tests/test_pagure_flask_api_project_blockuser.py

@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2019 - Copyright Red Hat Inc
+
+ Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
+
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+import arrow
+import copy
+import datetime
+import unittest
+import shutil
+import sys
+import time
+import os
+
+import flask
+import json
+import munch
+from mock import patch, MagicMock
+from sqlalchemy.exc import SQLAlchemyError
+
+sys.path.insert(
+    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
+)
+
+import pagure.lib.query
+import tests
+
+
+class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
+    """ Tests for the flask API of pagure for assigning a PR """
+
+    maxDiff = None
+
+    @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
+    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
+    def setUp(self):
+        """ Set up the environnment, ran before every tests. """
+        super(PagureFlaskApiProjectBlockuserTests, self).setUp()
+
+        tests.create_projects(self.session)
+        tests.create_tokens(self.session)
+        tests.create_tokens_acl(self.session)
+
+        item = pagure.lib.model.Token(
+            id="aaabbbcccdddeee",
+            user_id=2,
+            project_id=1,
+            expiration=datetime.datetime.utcnow()
+            + datetime.timedelta(days=30),
+        )
+        self.session.add(item)
+        self.session.commit()
+        tests.create_tokens_acl(self.session, token_id="aaabbbcccdddeee")
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        self.assertEqual(project.block_users, [])
+
+    def tearDown(self):
+        """ Tears down the environment at the end of the tests. """
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        self.assertEqual(project.block_users, [])
+
+        super(PagureFlaskApiProjectBlockuserTests, self).tearDown()
+
+    def test_api_blockuser_no_token(self):
+        """ Test api_project_block_user method when no token is provided.
+        """
+
+        # No token
+        output = self.app.post("/api/0/test/blockuser")
+        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#api-keys to "
+                "get or renew your API token.",
+                "error_code": "EINVALIDTOK",
+                "errors": "Invalid token",
+            },
+        )
+
+    def test_api_blockuser_invalid_token(self):
+        """ Test api_project_block_user method when the token provided is invalid.
+        """
+
+        headers = {"Authorization": "token aaabbbcccd"}
+
+        # Invalid token
+        output = self.app.post("/api/0/test/blockuser", 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#api-keys to "
+                "get or renew your API token.",
+                "error_code": "EINVALIDTOK",
+                "errors": "Invalid token",
+            },
+        )
+
+    def test_api_blockuser_no_data(self):
+        """ Test api_project_block_user method when no data is provided.
+        """
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+
+        # No user blocked
+        output = self.app.post("/api/0/test/blockuser", headers=headers)
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {"message": "User(s) blocked"})
+
+    def test_api_blockuser_invalid_user(self):
+        """ Test api_project_block_user method when the data provided includes
+        an invalid username.
+        """
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+        data = {"username": ["invalid"]}
+
+        # No user blocked
+        output = self.app.post(
+            "/api/0/test/blockuser", headers=headers, data=data
+        )
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data, {"error": 'No user "invalid" found', "error_code": "ENOCODE"}
+        )
+
+    def test_api_blockuser_insufficient_rights(self):
+        """ Test api_project_block_user method when the user doing the action
+        does not have admin priviledges.
+        """
+
+        headers = {"Authorization": "token aaabbbcccdddeee"}
+        data = {"username": ["invalid"]}
+
+        # No user blocked
+        output = self.app.post(
+            "/api/0/test/blockuser", headers=headers, data=data
+        )
+        self.assertEqual(output.status_code, 401)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "You do not have sufficient permissions to perform "
+                "this action",
+                "error_code": "ENOTHIGHENOUGH",
+            },
+        )
+
+
+class PagureFlaskApiProjectBlockuserFilledTests(tests.SimplePagureTest):
+    """ Tests for the flask API of pagure for assigning a PR """
+
+    maxDiff = None
+
+    @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
+    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
+    def setUp(self):
+        """ Set up the environnment, ran before every tests. """
+        super(PagureFlaskApiProjectBlockuserFilledTests, self).setUp()
+
+        tests.create_projects(self.session)
+        tests.create_tokens(self.session)
+        tests.create_tokens_acl(self.session)
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        self.assertEqual(project.block_users, [])
+
+    def tearDown(self):
+        """ Tears down the environment at the end of the tests. """
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        self.assertEqual(project.block_users, ["foo"])
+
+        super(PagureFlaskApiProjectBlockuserFilledTests, self).tearDown()
+
+    def test_api_blockuser_with_data(self):
+        """ Test api_project_block_user method to block users.
+        """
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+        data = {"username": ["foo"]}
+
+        # No user blocked
+        output = self.app.post(
+            "/api/0/test/blockuser", headers=headers, data=data
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {"message": "User(s) blocked"})
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)