diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index 47d08fa..1608d61 100644 --- a/pagure/api/__init__.py +++ b/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=[ diff --git a/pagure/api/project.py b/pagure/api/project.py index f2bbf92..469baf7 100644 --- a/pagure/api/project.py +++ b/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("//blockuser", methods=["POST"]) +@API.route("///blockuser", methods=["POST"]) +@API.route("/fork///blockuser", methods=["POST"]) +@API.route("/fork////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//blockuser + POST /api/0///blockuser + + :: + + POST /api/0/fork///blockuser + POST /api/0/fork////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 diff --git a/tests/test_pagure_flask_api_project_blockuser.py b/tests/test_pagure_flask_api_project_blockuser.py new file mode 100644 index 0000000..da67892 --- /dev/null +++ b/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 + +""" + +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)