From b72cee8e672543dcf83734cbdfa858e8e6c3ade0 Mon Sep 17 00:00:00 2001 From: Michal Konečný Date: Jul 31 2020 12:51:30 +0000 Subject: [PATCH 1/2] Add API for orphaning process Signed-off-by: Michal Konečný --- diff --git a/pagure_distgit/plugin.py b/pagure_distgit/plugin.py index 1757e33..31d883d 100644 --- a/pagure_distgit/plugin.py +++ b/pagure_distgit/plugin.py @@ -182,6 +182,58 @@ def _is_active_in_pdc(name, namespace): return data["results"][0]["active"] is True +@DISTGIT_NS.route("/orphan//", methods=["POST"]) +@api_login_required(acls=["modify_project"]) +@api_method +def orphan_endpoint(namespace, repo): + """ Orphan the package. + """ + _log.info("Received a request to orphan: %s/%s", namespace, repo) + + repo = _get_repo(repo, namespace=namespace) + _check_token(repo, project_token=False) + + user_obj = pagure.lib.query.get_user( + flask.g.session, flask.g.fas_user.username + ) + if not user_obj: + raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOUSER) + + if repo.user.user == "orphan": + raise pagure.exceptions.APIError( + 401, error_code=APIERROR.EMODIFYPROJECTNOTALLOWED + ) + + orphan_user_obj = pagure.lib.query.get_user(flask.g.session, "orphan") + + try: + pagure.lib.query.set_project_owner( + flask.g.session, repo, orphan_user_obj + ) + if user_obj in repo.users: + pagure.lib.query.remove_user_of_project( + flask.g.session, user_obj, repo, user_obj.user + ) + pagure.lib.query.update_watch_status( + flask.g.session, repo, user_obj.username, "-1" + ) + flask.g.session.commit() + except SQLAlchemyError as err: # pragma: no cover + flask.g.session.rollback() + _log.exception("Error when orphaning project") + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) + + pagure.lib.notify.log( + repo, + topic="project.orphan", + msg={"project": repo.to_json(public=True), "agent": user_obj.username}, + ) + + output = {"point_of_contact": repo.user.user} + + return flask.jsonify(output) + + @DISTGIT_NS.route("/take_orphan//", methods=["POST"]) @api_login_required(acls=["modify_project"]) @api_method diff --git a/pagure_distgit_tests/test_plugin.py b/pagure_distgit_tests/test_plugin.py new file mode 100644 index 0000000..e9f6fb2 --- /dev/null +++ b/pagure_distgit_tests/test_plugin.py @@ -0,0 +1,131 @@ +import json +import os +from unittest.mock import call, patch + +import pagure.lib.query +import pagure.lib.model + +from pagure_distgit import plugin + +import tests + + +class PagureFlaskApiOrphanEndpointTests(tests.Modeltests): + """ Tests the orphan endpoints added in pagure-dist-git. """ + + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureFlaskApiOrphanEndpointTests, self).setUp() + self.session.flush() + 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) + tests.create_user( + self.session, "orphan", "orphan", ["orphan@fedoraproject.org"] + ) + repo = pagure.lib.query.get_authorized_project( + self.session, "test3", namespace="somenamespace", + ) + token = pagure.lib.query.get_api_token(self.session, "aaabbbcccddd") + token.project = repo + self.session.add(token) + self.session.commit() + self._app.register_blueprint(plugin.DISTGIT_NS) + + def test_token_missing_ACL(self): + """Test the orphan endpoint with an API token missing the `modify_project` ACL. + """ + headers = {"Authorization": "token foo_token"} + output = self.app.post( + "/_dg/orphan/somenamespace/test3", headers=headers + ) + # invalid token + assert output.status_code == 401 + + def test_invalid_token(self): + """Test the orphan endpoint with an invalid API token. """ + headers = {"Authorization": "token BBBZZZOOO"} + datainput = {} + output = self.app.post( + "/_dg/orphan/somenamespace/test3", data=datainput, headers=headers, + ) + assert output.status_code == 401 + + def test_already_orphaned_package(self): + """Assert that already orphaned package can't be orphaned again. + """ + headers = {"Authorization": "token aaabbbcccddd"} + datainput = {} + orphan_user_obj = pagure.lib.query.get_user(self.session, "orphan") + repo = pagure.lib.query.get_authorized_project( + self.session, "test3", namespace="somenamespace", + ) + repo.user = orphan_user_obj + self.session.add(repo) + self.session.commit() + output = self.app.post( + "/_dg/orphan/somenamespace/test3", data=datainput, headers=headers, + ) + assert output.status_code == 401 + + @patch("pagure_distgit.plugin.pagure.lib.notify.log") + def test_orphan_package(self, mock_log): + """Assert that package is correctly orphaned. + """ + headers = {"Authorization": "token aaabbbcccddd"} + datainput = {} + repo = pagure.lib.query.get_authorized_project( + self.session, "test3", namespace="somenamespace", + ) + user = repo.user + project_user = pagure.lib.model.ProjectUser( + project_id=repo.id, user_id=repo.user.id, access="admin", + ) + self.session.add(project_user) + self.session.commit() + output = self.app.post( + "/_dg/orphan/somenamespace/test3", data=datainput, headers=headers, + ) + assert output.status_code == 200 + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, {"point_of_contact": "orphan"}) + + assert repo.user.user == "orphan" + + watch_level = pagure.lib.query.get_watch_level_on_repo( + self.session, user, "test3" + ) + assert watch_level == [] + + assert user not in repo.users + + assert mock_log.call_count == 2 + + @patch("pagure_distgit.plugin.pagure.lib.notify.log") + def test_orphan_package_repo_users_empty(self, mock_log): + """Assert that package is correctly orphaned. + """ + headers = {"Authorization": "token aaabbbcccddd"} + datainput = {} + repo = pagure.lib.query.get_authorized_project( + self.session, "test3", namespace="somenamespace", + ) + user = repo.user + output = self.app.post( + "/_dg/orphan/somenamespace/test3", data=datainput, headers=headers, + ) + assert output.status_code == 200 + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, {"point_of_contact": "orphan"}) + # refresh the repo object, so we have the current state + self.session.refresh(repo) + + assert repo.user.user == "orphan" + + watch_level = pagure.lib.query.get_watch_level_on_repo( + self.session, user, "test3" + ) + assert watch_level == [] + + assert mock_log.call_count == 1 From 2118c608a13575385fad72bd0364ca828f9a703e Mon Sep 17 00:00:00 2001 From: Michal Konečný Date: Jul 31 2020 15:58:04 +0000 Subject: [PATCH 2/2] Fix the error when orphan user doesn't exist Signed-off-by: Michal Konečný --- diff --git a/pagure_distgit/plugin.py b/pagure_distgit/plugin.py index 31d883d..ea4f2b4 100644 --- a/pagure_distgit/plugin.py +++ b/pagure_distgit/plugin.py @@ -19,6 +19,7 @@ import flask import pagure.utils from pagure.api import APIERROR, api_login_required, api_method from pagure.api.utils import _check_token, _get_repo +from pagure.exceptions import PagureException import pagure_distgit.forms from pagure_distgit import model @@ -204,7 +205,11 @@ def orphan_endpoint(namespace, repo): 401, error_code=APIERROR.EMODIFYPROJECTNOTALLOWED ) - orphan_user_obj = pagure.lib.query.get_user(flask.g.session, "orphan") + try: + orphan_user_obj = pagure.lib.query.get_user(flask.g.session, "orphan") + except PagureException: + _log.exception("Error when retrieving orphan user") + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) try: pagure.lib.query.set_project_owner( @@ -218,7 +223,7 @@ def orphan_endpoint(namespace, repo): flask.g.session, repo, user_obj.username, "-1" ) flask.g.session.commit() - except SQLAlchemyError as err: # pragma: no cover + except SQLAlchemyError: # pragma: no cover flask.g.session.rollback() _log.exception("Error when orphaning project") raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) diff --git a/pagure_distgit_tests/test_plugin.py b/pagure_distgit_tests/test_plugin.py index e9f6fb2..aafed1c 100644 --- a/pagure_distgit_tests/test_plugin.py +++ b/pagure_distgit_tests/test_plugin.py @@ -129,3 +129,17 @@ class PagureFlaskApiOrphanEndpointTests(tests.Modeltests): assert watch_level == [] assert mock_log.call_count == 1 + + def test_orphan_package_repo_no_orphan_user(self): + """Assert that missing orphan user is correctly handled. + """ + headers = {"Authorization": "token aaabbbcccddd"} + datainput = {} + orphan_user_obj = pagure.lib.query.get_user(self.session, "orphan") + self.session.delete(orphan_user_obj) + self.session.commit() + + output = self.app.post( + "/_dg/orphan/somenamespace/test3", data=datainput, headers=headers, + ) + assert output.status_code == 400