From 3e658c9b3bc4b622cece31fb43e05438881f6b6f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Nov 25 2020 13:40:03 +0000 Subject: Add a new API endpoint listing all the contributors of a project In addition to who they are, we're also providing here their level of access in detail (ie: including the pattern that applies to the collaborators). This endpoint will be useful in the dist-git use-case of pagure Relates to: https://pagure.io/fedora-infrastructure/issue/9427 Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index ae5485d..6fcf915 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -530,6 +530,7 @@ def api(): project.api_new_git_tags, project.api_project_git_urls, project.api_project_watchers, + project.api_project_contributors, project.api_git_branches, project.api_new_branch, project.api_fork_project, diff --git a/pagure/api/project.py b/pagure/api/project.py index 79ca14c..654c008 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -3085,6 +3085,115 @@ def api_project_block_user(repo, namespace=None, username=None): return jsonout +@API.route("//contributors") +@API.route("///contributors") +@API.route("/fork///contributors") +@API.route("/fork////contributors") +@api_method +def api_project_contributors(repo, namespace=None, username=None): + """ + Contributors of a project + ------------------------- + List all the contributors of a project, by their access level. + + :: + + GET /api/0//contributors + GET /api/0///contributors + + :: + + GET /api/0/fork///contributors + GET /api/0/fork////contributors + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "groups": { + "admin": [], + "collaborators": [ + { + "branches": "f*", + "user": "packager" + } + ], + "commit": [ + "infra" + ], + "ticket": [] + }, + "users": { + "admin": [ + "pingou" + ], + "collaborators": [ + { + "branches": "epel*", + "user": "ngompa" + } + ], + "commit": [ + "kevin" + ], + "ticket": [ + "ralph" + ] + } + } + + """ + + project = _get_repo(repo, username, namespace) + + # USERS + admins = set([u.user for u in project.admins + [project.user]]) + committers = set(u.user for u in project.committers) + collaborators = set([u.user.user for u in project.collaborators]) + users = set([u.user for u in project.users]) + + output_users = { + "admin": sorted(admins), + "commit": sorted(committers - admins), + "ticket": sorted(users - collaborators - committers - admins), + "collaborators": sorted( + [ + {"user": u.user.user, "branches": u.branches} + for u in project.collaborators + if u not in admins and u not in committers + ], + key=lambda x: x["user"], + ), + } + + # GROUPS + admins = set([g.group_name for g in project.admin_groups]) + committers = set([g.group_name for g in project.committer_groups]) + collaborators = set( + [g.group.group_name for g in project.collaborator_project_groups] + ) + groups = set([g.group_name for g in project.groups]) + + output_groups = { + "admin": sorted(admins), + "commit": sorted(committers - admins), + "ticket": sorted(groups - collaborators - committers - admins), + "collaborators": sorted( + [ + {"user": g.group.group_name, "branches": g.branches} + for g in project.collaborator_project_groups + if g not in admins and g not in committers + ], + key=lambda x: x["user"], + ), + } + + jsonout = flask.jsonify({"users": output_users, "groups": output_groups}) + return jsonout + + @API.route("//delete", methods=["POST"]) @API.route("///delete", methods=["POST"]) @API.route("/fork///delete", methods=["POST"]) diff --git a/tests/test_pagure_flask_api_project_contributors.py b/tests/test_pagure_flask_api_project_contributors.py new file mode 100644 index 0000000..ed1762d --- /dev/null +++ b/tests/test_pagure_flask_api_project_contributors.py @@ -0,0 +1,708 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2020 - 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 PagureFlaskApiProjectContributorsTests(tests.SimplePagureTest): + """ Tests for the flask API of pagure for listing contributors of a project + """ + + 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(PagureFlaskApiProjectContributorsTests, self).setUp() + + tests.create_projects(self.session) + tests.create_projects_git(os.path.join(self.path, "repos"), bare=True) + + def test_just_main_admin(self): + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": [], + "collaborators": [], + "commit": [], + "ticket": [], + }, + "users": { + "admin": ["pingou"], + "collaborators": [], + "commit": [], + "ticket": [], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_user(self): + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="foo", + user="pingou", + access="admin", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": [], + "collaborators": [], + "commit": [], + "ticket": [], + }, + "users": { + "admin": ["foo", "pingou"], + "collaborators": [], + "commit": [], + "ticket": [], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_user_and_commit(self): + + tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"]) + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="foo", + user="pingou", + access="admin", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="baz", + user="pingou", + access="commit", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": [], + "collaborators": [], + "commit": [], + "ticket": [], + }, + "users": { + "admin": ["foo", "pingou"], + "collaborators": [], + "commit": ["baz"], + "ticket": [], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_user_and_commit_and_ticket(self): + + tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"]) + tests.create_user( + self.session, "alex", "Alex Ander", ["alex@ander.com"] + ) + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="foo", + user="pingou", + access="admin", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="baz", + user="pingou", + access="commit", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="alex", + user="pingou", + access="ticket", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": [], + "collaborators": [], + "commit": [], + "ticket": [], + }, + "users": { + "admin": ["foo", "pingou"], + "collaborators": [], + "commit": ["baz"], + "ticket": ["alex"], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_user_and_commit_and_ticket_and_contributors(self): + + tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"]) + tests.create_user( + self.session, "alex", "Alex Ander", ["alex@ander.com"] + ) + tests.create_user(self.session, "ralph", "Ralph B.", ["ralph@b.com"]) + tests.create_user(self.session, "kevin", "Kevin F.", ["kevin@f.com"]) + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="foo", + user="pingou", + access="admin", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="baz", + user="pingou", + access="commit", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="alex", + user="pingou", + access="ticket", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="ralph", + user="pingou", + access="collaborator", + branches="epel*", + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="kevin", + user="pingou", + access="collaborator", + branches="f*", + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": [], + "collaborators": [], + "commit": [], + "ticket": [], + }, + "users": { + "admin": ["foo", "pingou"], + "collaborators": [ + {"branches": "f*", "user": "kevin"}, + {"branches": "epel*", "user": "ralph"}, + ], + "commit": ["baz"], + "ticket": ["alex"], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_group(self): + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="admin_groups", + user="pingou", + access="admin", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": ["admin_groups"], + "collaborators": [], + "commit": [], + "ticket": [], + }, + "users": { + "admin": ["pingou"], + "collaborators": [], + "commit": [], + "ticket": [], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_and_commit_groups(self): + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="admin_groups", + user="pingou", + access="admin", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="commit_group", + user="pingou", + access="commit", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": ["admin_groups"], + "collaborators": [], + "commit": ["commit_group"], + "ticket": [], + }, + "users": { + "admin": ["pingou"], + "collaborators": [], + "commit": [], + "ticket": [], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_and_commit_and_ticket_groups(self): + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="admin_groups", + user="pingou", + access="admin", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="commit_group", + user="pingou", + access="commit", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="ticket_group", + user="pingou", + access="ticket", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": ["admin_groups"], + "collaborators": [], + "commit": ["commit_group"], + "ticket": ["ticket_group"], + }, + "users": { + "admin": ["pingou"], + "collaborators": [], + "commit": [], + "ticket": [], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_admin_and_commit_and_ticket_and_collaborators_groups(self): + + project = pagure.lib.query.get_authorized_project(self.session, "test") + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="admin_groups", + user="pingou", + access="admin", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="commit_group", + user="pingou", + access="commit", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="ticket_group", + user="pingou", + access="ticket", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="epel_group", + user="pingou", + access="collaborator", + branches="epel*", + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="fedora_group", + user="pingou", + access="collaborator", + branches="f*", + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": ["admin_groups"], + "collaborators": [ + {"branches": "epel*", "user": "epel_group"}, + {"branches": "f*", "user": "fedora_group"}, + ], + "commit": ["commit_group"], + "ticket": ["ticket_group"], + }, + "users": { + "admin": ["pingou"], + "collaborators": [], + "commit": [], + "ticket": [], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + def test_full(self): + + tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"]) + tests.create_user( + self.session, "alex", "Alex Ander", ["alex@ander.com"] + ) + tests.create_user(self.session, "ralph", "Ralph B.", ["ralph@b.com"]) + tests.create_user(self.session, "kevin", "Kevin F.", ["kevin@f.com"]) + + project = pagure.lib.query.get_authorized_project(self.session, "test") + + # Add users for all kinds of access + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="foo", + user="pingou", + access="admin", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="baz", + user="pingou", + access="commit", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="alex", + user="pingou", + access="ticket", + branches=None, + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="ralph", + user="pingou", + access="collaborator", + branches="epel*", + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + msg = pagure.lib.query.add_user_to_project( + session=self.session, + project=project, + new_user="kevin", + user="pingou", + access="collaborator", + branches="f*", + required_groups=None, + ) + self.session.commit() + self.assertEqual(msg, "User added") + + # Create groups for all kinds of access + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="admin_groups", + user="pingou", + access="admin", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="commit_group", + user="pingou", + access="commit", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="ticket_group", + user="pingou", + access="ticket", + branches=None, + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="epel_group", + user="pingou", + access="collaborator", + branches="epel*", + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + msg = pagure.lib.query.add_group_to_project( + session=self.session, + project=project, + new_group="fedora_group", + user="pingou", + access="collaborator", + branches="f*", + create=True, + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, "Group added") + + output = self.app.get("/api/0/test/contributors") + self.assertEqual(output.status_code, 200) + expected_rv = { + "groups": { + "admin": ["admin_groups"], + "collaborators": [ + {"branches": "epel*", "user": "epel_group"}, + {"branches": "f*", "user": "fedora_group"}, + ], + "commit": ["commit_group"], + "ticket": ["ticket_group"], + }, + "users": { + "admin": ["foo", "pingou"], + "collaborators": [ + {"branches": "f*", "user": "kevin"}, + {"branches": "epel*", "user": "ralph"}, + ], + "commit": ["baz"], + "ticket": ["alex"], + }, + } + data = json.loads(output.get_data(as_text=True)) + self.assertDictEqual(data, expected_rv) + + +if __name__ == "__main__": + unittest.main(verbosity=2)