From 01d0f2961e3619bff8840f7974b5a4e869d12d5d Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 18 2019 09:59:32 +0000 Subject: [PATCH 1/5] Reduce code duplication using _get_repo and _check_token These two utility methods allow us to reduce code duplication and give us a single place where to retrieve projects and validate the API token retrieved. Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/api/project.py b/pagure/api/project.py index d82f9e7..f2bbf92 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - (c) 2015-2018 - Copyright Red Hat Inc + (c) 2015-2019 - Copyright Red Hat Inc Authors: Pierre-Yves Chibon @@ -34,6 +34,7 @@ from pagure.api import ( get_page, get_per_page, ) +from pagure.api.utils import _get_repo, _check_token from pagure.config import config as pagure_config @@ -96,11 +97,7 @@ def api_git_tags(repo, username=None, namespace=None): flask.request.values.get("with_commits", False) ) - repo = get_authorized_api_project( - flask.g.session, repo, user=username, namespace=namespace - ) - if repo is None: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) + repo = _get_repo(repo, username, namespace) tags = pagure.lib.git.get_git_tags(repo, with_commits=with_commits) @@ -144,11 +141,7 @@ def api_project_watchers(repo, username=None, namespace=None): } } """ - repo = get_authorized_api_project( - flask.g.session, repo, user=username, namespace=namespace - ) - if repo is None: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) + repo = _get_repo(repo, username, namespace) implicit_watch_users = set([repo.user.username]) for access_type in repo.access_users: @@ -235,13 +228,9 @@ def api_project_git_urls(repo, username=None, namespace=None): } } """ - repo = get_authorized_api_project( - flask.g.session, repo, user=username, namespace=namespace - ) - if repo is None: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - git_urls = {} + repo = _get_repo(repo, username, namespace) + git_urls = {} git_url_ssh = pagure_config.get("GIT_URL_SSH") if pagure.utils.api_authenticated() and git_url_ssh: try: @@ -293,11 +282,7 @@ def api_git_branches(repo, username=None, namespace=None): } """ - repo = get_authorized_api_project( - flask.g.session, repo, user=username, namespace=namespace - ) - if repo is None: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) + repo = _get_repo(repo, username, namespace) branches = pagure.lib.git.get_git_branches(repo) @@ -632,17 +617,12 @@ def api_project(repo, username=None, namespace=None): } """ - repo = get_authorized_api_project( - flask.g.session, repo, user=username, namespace=namespace - ) + repo = _get_repo(repo, username, namespace) expand_group = pagure.utils.is_true( flask.request.values.get("expand_group", False) ) - if repo is None: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - output = repo.to_json(api=True, public=True) if expand_group: @@ -904,14 +884,8 @@ def api_modify_project(repo, namespace=None): } """ - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, namespace=namespace) + _check_token(project, project_token=False) is_site_admin = pagure.utils.is_admin() admins = [u.username for u in project.get_project_users("admin")] @@ -1145,14 +1119,8 @@ def api_generate_acls(repo, username=None, namespace=None): } """ - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project, project_token=False) # Check if it's JSON or form data if flask.request.headers.get("Content-Type") == "application/json": @@ -1230,14 +1198,8 @@ def api_new_branch(repo, username=None, namespace=None): } """ - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project, project_token=False) # Check if it's JSON or form data if flask.request.headers.get("Content-Type") == "application/json": @@ -1341,11 +1303,7 @@ def api_commit_flags(repo, commit_hash, username=None, namespace=None): } """ - repo = get_authorized_api_project( - flask.g.session, repo, user=username, namespace=namespace - ) - if repo is None: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) + repo = _get_repo(repo, username, namespace) reponame = pagure.utils.get_repo_path(repo) repo_obj = Repository(reponame) @@ -1476,18 +1434,11 @@ def api_commit_add_flag(repo, commit_hash, username=None, namespace=None): """ # noqa - repo = get_authorized_api_project( - flask.g.session, repo, user=username, namespace=namespace - ) + repo = _get_repo(repo, username, namespace) + _check_token(repo, project_token=False) output = {} - if repo is None: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and repo != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) - reponame = pagure.utils.get_repo_path(repo) repo_obj = Repository(reponame) try: @@ -1618,14 +1569,8 @@ def api_update_project_watchers(repo, username=None, namespace=None): } """ - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project) # Get the input submitted data = get_request_data() @@ -1773,14 +1718,9 @@ def api_modify_acls(repo, namespace=None, username=None): """ output = {} - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project, project_token=False) form = pagure.forms.ModifyACLForm(csrf_enabled=False) if form.validate_on_submit(): @@ -1956,14 +1896,8 @@ def api_get_project_options(repo, username=None, namespace=None): } """ - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project, project_token=False) return flask.jsonify({"settings": project.settings, "status": "ok"}) @@ -2015,14 +1949,8 @@ def api_get_project_connector(repo, username=None, namespace=None): } """ - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project, project_token=False) authorized_users = [project.user.username] authorized_users.extend( @@ -2105,14 +2033,8 @@ def api_modify_project_options(repo, username=None, namespace=None): } """ - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project, project_token=False) settings = {} for key in flask.request.form: @@ -2194,14 +2116,9 @@ def api_project_create_api_token(repo, namespace=None, username=None): """ output = {} - project = get_authorized_api_project( - flask.g.session, repo, namespace=namespace - ) - if not project: - raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) - if flask.g.token.project and project != flask.g.token.project: - raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) + project = _get_repo(repo, username, namespace) + _check_token(project, project_token=False) authorized_users = [project.user.username] authorized_users.extend( From 2de085a9374fd6eef17b334f0ff10f95bd1ea549 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 18 2019 09:59:32 +0000 Subject: [PATCH 2/5] Add a DB field to store users to block on a project Signed-off-by: Pierre-Yves Chibon --- diff --git a/alembic/versions/1a510f2216c0_add_the__block_users_field.py b/alembic/versions/1a510f2216c0_add_the__block_users_field.py new file mode 100644 index 0000000..9a46c0e --- /dev/null +++ b/alembic/versions/1a510f2216c0_add_the__block_users_field.py @@ -0,0 +1,30 @@ +"""Add the _block_users field + +Revision ID: 1a510f2216c0 +Revises: 003fcd9e8860 +Create Date: 2019-03-14 12:24:32.139377 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1a510f2216c0' +down_revision = '003fcd9e8860' + + +def upgrade(): + ''' Add the column _block_users to the table projects. + ''' + op.add_column( + 'projects', + sa.Column('_block_users', sa.Text, nullable=True) + ) + + +def downgrade(): + ''' Drop the column _block_users from the table projects. + ''' + op.drop_column('projects', '_block_users') diff --git a/pagure/lib/model.py b/pagure/lib/model.py index 97de6d5..19bc5f9 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -377,6 +377,7 @@ class Project(BASE): _reports = sa.Column(sa.Text, nullable=True) _notifications = sa.Column(sa.Text, nullable=True) _close_status = sa.Column(sa.Text, nullable=True) + _block_users = sa.Column(sa.Text, nullable=True) mirrored_from = sa.Column(sa.Text, nullable=True) mirrored_from_last_log = sa.Column(sa.Text, nullable=True) @@ -721,6 +722,23 @@ class Project(BASE): self._priorities = json.dumps(priorities) @property + def block_users(self): + """ Return the dict stored as string in the database as an actual + dict object. + """ + block_users = [] + + if self._block_users: + block_users = json.loads(self._block_users) + + return block_users + + @block_users.setter + def block_users(self, block_users): + """ Ensures the block_users are properly saved. """ + self._block_users = json.dumps(block_users) + + @property def quick_replies(self): """ Return a list of quick replies available for pull requests and issues. From 4b7c3efee64cb4a03c28b8e08431d1c995c69bfa Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 18 2019 09:59:32 +0000 Subject: [PATCH 3/5] Add an API endpoint to adjust the list of blocked users on a project Signed-off-by: Pierre-Yves Chibon --- 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) From 4cfa1e4e36772cd265584ce2f45c8619717bebec Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 18 2019 09:59:32 +0000 Subject: [PATCH 4/5] Block all POST requests on the UI and the API for blocked users Expand the test on blocked users ensure POST actions are not accessible via neither the UI nor the API. Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index 1608d61..13e28df 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -121,6 +121,7 @@ class APIERROR(enum.Enum): ) ETRACKERREADONLY = "The issue tracker of this project is read-only" ENOPRSTATS = "No statistics could be computed for this PR" + EUBLOCKED = "You have been blocked from this project" def get_authorized_api_project(session, repo, user=None, namespace=None): @@ -151,6 +152,37 @@ def api_login_required(acls=None): response = check_api_acls(acls) if response: return response + + # Block all POST request from blocked users + if flask.request.method == "POST": + # Retrieve the variables in the URL + url_args = flask.request.view_args or {} + # Check if there is a `repo` and an `username` + repo = url_args.get("repo") + username = url_args.get("username") + namespace = url_args.get("namespace") + + if repo: + flask.g.repo = pagure.lib.query.get_authorized_project( + flask.g.session, + repo, + user=username, + namespace=namespace, + ) + + if ( + flask.g.repo + and flask.g.fas_user.username + in flask.g.repo.block_users + ): + output = { + "error": APIERROR.EUBLOCKED.value, + "error_code": APIERROR.EUBLOCKED.name, + } + response = flask.jsonify(output) + response.status_code = 403 + return response + return function(*args, **kwargs) return decorated_function diff --git a/pagure/flask_app.py b/pagure/flask_app.py index df9d687..d7a0e80 100644 --- a/pagure/flask_app.py +++ b/pagure/flask_app.py @@ -305,6 +305,11 @@ def set_request(): flask.g.session, flask.g.repo, user=flask.g.fas_user.username ) + # Block all POST request from blocked users + if flask.g.repo and flask.request.method != "GET": + if flask.g.fas_user.username in flask.g.repo.block_users: + flask.abort(403, "You have been blocked from this project") + if ( not flask.g.repo and namespace diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py index 8daea1d..1bb5aa6 100644 --- a/tests/test_pagure_flask_api.py +++ b/tests/test_pagure_flask_api.py @@ -237,10 +237,10 @@ class PagureFlaskApitests(tests.SimplePagureTest): output = self.app.get('/api/0/-/error_codes') self.assertEqual(output.status_code, 200) data = json.loads(output.get_data(as_text=True)) - self.assertEqual(len(data), 35) + self.assertEqual(len(data), 36) self.assertEqual( sorted(data.keys()), - [ + sorted([ 'EDATETIME', 'EDBERROR', 'EGITERROR', @@ -276,7 +276,8 @@ class PagureFlaskApitests(tests.SimplePagureTest): 'ETIMESTAMP', 'ETRACKERDISABLED', 'ETRACKERREADONLY', - ] + 'EUBLOCKED', + ]) ) @patch("pagure.lib.tasks.get_result") diff --git a/tests/test_pagure_flask_api_project_blockuser.py b/tests/test_pagure_flask_api_project_blockuser.py index da67892..dfed55c 100644 --- a/tests/test_pagure_flask_api_project_blockuser.py +++ b/tests/test_pagure_flask_api_project_blockuser.py @@ -45,6 +45,8 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest): super(PagureFlaskApiProjectBlockuserTests, self).setUp() tests.create_projects(self.session) + tests.create_projects_git( + os.path.join(self.path, 'repos'), bare=True) tests.create_tokens(self.session) tests.create_tokens_acl(self.session) @@ -61,11 +63,17 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest): project = pagure.lib.query.get_authorized_project(self.session, "test") self.assertEqual(project.block_users, []) + self.blocked_users = [] + + project = pagure.lib.query.get_authorized_project(self.session, "test2") + project.block_users = ["foo"] + self.session.add(project) + self.session.commit() 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, []) + self.assertEqual(project.block_users, self.blocked_users) super(PagureFlaskApiProjectBlockuserTests, self).tearDown() @@ -162,40 +170,42 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest): }, ) + def test_api_blockuser_with_data(self): + """ Test api_pull_request_assign method when the project doesn't exist. + """ + self.blocked_users = ["foo"] -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) + headers = {"Authorization": "token aaabbbcccddd"} + data = {"username": ["foo"]} - project = pagure.lib.query.get_authorized_project(self.session, "test") - self.assertEqual(project.block_users, []) + # 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"}) - 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"]) + # Second request, no changes + headers = {"Authorization": "token aaabbbcccddd"} + data = {"username": ["foo"]} - super(PagureFlaskApiProjectBlockuserFilledTests, self).tearDown() + 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"}) - def test_api_blockuser_with_data(self): - """ Test api_project_block_user method to block users. + def test_api_blockeduser_api(self): + """ Test doing a POST request to the API when the user is blocked. """ + self.blocked_users = ["pingou"] headers = {"Authorization": "token aaabbbcccddd"} - data = {"username": ["foo"]} + data = {"username": ["pingou"]} - # No user blocked + # user blocked output = self.app.post( "/api/0/test/blockuser", headers=headers, data=data ) @@ -203,6 +213,52 @@ class PagureFlaskApiProjectBlockuserFilledTests(tests.SimplePagureTest): data = json.loads(output.get_data(as_text=True)) self.assertDictEqual(data, {"message": "User(s) blocked"}) + # Second request, but user is blocked + headers = {"Authorization": "token aaabbbcccddd"} + data = {"username": ["foo"]} + + output = self.app.post( + "/api/0/test/blockuser", headers=headers, data=data + ) + self.assertEqual(output.status_code, 403) + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual( + data, + { + "error":"You have been blocked from this project", + "error_code":"EUBLOCKED" + } + ) + + def test_ui_new_issue_user_blocked(self): + """ Test doing a POST request to the UI when the user is blocked. + """ + + user = tests.FakeUser(username="foo") + with tests.user_set(self.app.application, user): + + output = self.app.get('/test2/new_issue') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'New Issue', + output.get_data(as_text=True)) + + csrf_token = self.get_csrf(output=output) + + data = { + 'title': 'Test issue', + 'issue_content': 'We really should improve on this issue', + 'status': 'Open', + 'csrf_token': csrf_token, + } + + output = self.app.post('/test2/new_issue', data=data) + self.assertEqual(output.status_code, 403) + output_text = output.get_data(as_text=True) + self.assertIn( + '

You have been blocked from this project

', + output_text) + if __name__ == "__main__": unittest.main(verbosity=2) From e14a441821557ecf22ad57833daa2151cd848a0a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 18 2019 09:59:32 +0000 Subject: [PATCH 5/5] Let admins set the list of users to block on a project in the settings Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/templates/settings.html b/pagure/templates/settings.html index 268dc93..f7fc247 100644 --- a/pagure/templates/settings.html +++ b/pagure/templates/settings.html @@ -79,6 +79,11 @@ Regenerate Repos + {% if repo.user.user == g.fas_user.username or pagure_admin %} + Block Users + {% endif %} + {% if config.get('ENABLE_GIVE_PROJECTS', True) and (repo.user.user == g.fas_user.username or pagure_admin) and not repo.is_fork %} @@ -1034,6 +1039,10 @@ +
+ {% include 'settings_block_users.html' %} +
+ {% if config.get('ENABLE_GIVE_PROJECTS', True) and (repo.user.user == g.fas_user.username or pagure_admin) and not repo.is_fork %} @@ -1418,6 +1427,39 @@ $('#user').selectize({ } }); {% endif %} + +$('.ajaxed').click(function(e) { + _form = $(this).closest('form') + $.ajax({ + url: _form.prop('action') , + type: 'POST', + data: _form.serialize(), + dataType: 'json', + success: function(res) { + console.log(res); + if ( res.message ) { + var _html = '
' + + ' ' + + '
'; + $('.bodycontent').prepend(_html) + } + }, + error: function(res) { + console.log(res); + alert('Request failed'); + } + }); + return false; +}); +