From 5cdb69f2249139f3cf2f115fe870c86726d35043 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Nov 13 2017 15:25:27 +0000 Subject: [PATCH 1/4] Allow filtering the user's project by access This commit allows to return only some of the projects the user is involved in based on their access level instead of returning them all. It also replaces SQL based counts of the projects by simple call to ``len()`` in python since the SQL query itself isn't paginated. Fixes https://pagure.io/pagure/issue/2738 Signed-off-by: Pierre-Yves Chibon --- diff --git a/doc/usage/tips_tricks.rst b/doc/usage/tips_tricks.rst index 4319085..4fc9ba9 100644 --- a/doc/usage/tips_tricks.rst +++ b/doc/usage/tips_tricks.rst @@ -39,3 +39,24 @@ will get the emails printed to the console instead of being sent. The admin of the instance can then access the URL to manually validate the account from there. This is generally used for development where we don't need to send any emails. + + +Filter an user's projects by their access +----------------------------------------- + +When watching an user's page, the list of all the project that user is +involved in is presented regardless of whether the user has ticket, commit, +admin access or is the main admin of the project. + +You can specify an ``acl=`` argument to the url to filter the list of +project by access. + + +.. note:: This also works for your home page when you are logged in + + +Examples: +~~~~~~~~~ +https://pagure.io/user/pingou?acl=main admin +https://pagure.io/user/pingou?acl=admin +https://pagure.io/user/pingou?acl=commit diff --git a/pagure/ui/app.py b/pagure/ui/app.py index 74b140e..21b7d3e 100644 --- a/pagure/ui/app.py +++ b/pagure/ui/app.py @@ -87,6 +87,8 @@ def index_auth(): """ user = _get_user(username=flask.g.fas_user.username) + acl = flask.request.args.get('acl', '').strip().lower() or None + repopage = flask.request.args.get('repopage', 1) try: repopage = int(repopage) @@ -108,14 +110,29 @@ def index_auth(): username=flask.g.fas_user.username, exclude_groups=APP.config.get('EXCLUDE_GROUP_INDEX'), fork=False, private=flask.g.fas_user.username) - - repos_length = pagure.lib.search_projects( - SESSION, - username=flask.g.fas_user.username, - exclude_groups=APP.config.get('EXCLUDE_GROUP_INDEX'), - fork=False, - count=True, - private=flask.g.fas_user.username) + if repos and acl: + if acl == 'commit': + repos = [ + repo + for repo in repos + if user in repo.committers + or user.username == repo.user.username + ] + elif acl == 'admin': + repos = [ + repo + for repo in repos + if user in repo.admins + or user.username == repo.user.username + ] + elif acl == 'main admin': + repos = [ + repo + for repo in repos + if user.username == repo.user.username + ] + + repos_length = len(repos) forks = pagure.lib.search_projects( SESSION, @@ -123,12 +140,7 @@ def index_auth(): fork=True, private=flask.g.fas_user.username) - forks_length = pagure.lib.search_projects( - SESSION, - username=flask.g.fas_user.username, - fork=True, - count=True, - private=flask.g.fas_user.username) + forks_length = len(forks) watch_list = pagure.lib.user_watch_list( SESSION, @@ -316,6 +328,8 @@ def view_user(username): """ user = _get_user(username=username) + acl = flask.request.args.get('acl', '').strip().lower() or None + repopage = flask.request.args.get('repopage', 1) try: repopage = int(repopage) @@ -348,13 +362,30 @@ def view_user(username): start=repo_start, limit=limit, private=private) - repos_length = pagure.lib.search_projects( - SESSION, - username=username, - fork=False, - exclude_groups=APP.config.get('EXCLUDE_GROUP_INDEX'), - count=True, - private=private) + + if repos and acl: + if acl == 'commit': + repos = [ + repo + for repo in repos + if user in repo.committers + or user.username == repo.user.username + ] + elif acl == 'admin': + repos = [ + repo + for repo in repos + if user in repo.admins + or user.username == repo.user.username + ] + elif acl == 'main admin': + repos = [ + repo + for repo in repos + if user.username == repo.user.username + ] + + repos_length = len(repos) forks = pagure.lib.search_projects( SESSION, @@ -363,12 +394,7 @@ def view_user(username): start=fork_start, limit=limit, private=private) - forks_length = pagure.lib.search_projects( - SESSION, - username=username, - fork=True, - count=True, - private=private) + forks_length = len(forks) total_page_repos = int(ceil(repos_length / float(limit))) total_page_forks = int(ceil(forks_length / float(limit))) @@ -412,7 +438,7 @@ def view_user_requests(username): @APP.route('/user//issues') def view_user_issues(username): """ - Shows the issues created or assigned to the + Shows the issues created or assigned to the specified user. diff --git a/tests/test_pagure_flask_ui_app.py b/tests/test_pagure_flask_ui_app.py index eb5729b..56d5f36 100644 --- a/tests/test_pagure_flask_ui_app.py +++ b/tests/test_pagure_flask_ui_app.py @@ -43,58 +43,6 @@ class PagureFlaskApptests(tests.Modeltests): pagure.ui.filters.SESSION = self.session pagure.ui.repo.SESSION = self.session - @patch.dict('pagure.APP.config', {'HTML_TITLE': 'Pagure HTML title set'}) - def test_index_html_title(self): - """ Test the index endpoint with a set html title. """ - - output = self.app.get('/') - self.assertEqual(output.status_code, 200) - self.assertIn( - 'Home - Pagure HTML title set', - output.data) - - def test_index(self): - """ Test the index endpoint. """ - - output = self.app.get('/') - self.assertEqual(output.status_code, 200) - self.assertIn('Home - Pagure', output.data) - self.assertIn( - '

All Projects ' - '0

', output.data) - - tests.create_projects(self.session) - - output = self.app.get('/?page=abc') - self.assertEqual(output.status_code, 200) - self.assertIn( - '

All Projects ' - '3

', output.data) - - # Add a 3rd project with a long description - item = pagure.lib.model.Project( - user_id=2, # foo - name='test3', - description='test project #3 with a very long description', - hook_token='aaabbbeeefff', - ) - self.session.add(item) - self.session.commit() - - user = tests.FakeUser(username='foo') - with tests.user_set(pagure.APP, user): - output = self.app.get('/?repopage=abc&forkpage=def') - self.assertIn( - 'Projects 1', - output.data) - self.assertIn( - 'Forks 0', - output.data) - self.assertEqual( - output.data.count('

No group found

'), 1) - self.assertEqual( - output.data.count('
'), 6) - def test_watch_list(self): ''' Test for watch list of a user ''' @@ -1656,5 +1604,6 @@ class PagureFlaskApptests(tests.Modeltests): output = self.app.get('/settings/token/new') self.assertEqual(output.status_code, 302) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/tests/test_pagure_flask_ui_app_index.py b/tests/test_pagure_flask_ui_app_index.py new file mode 100644 index 0000000..3d8220a --- /dev/null +++ b/tests/test_pagure_flask_ui_app_index.py @@ -0,0 +1,441 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2017 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +__requires__ = ['SQLAlchemy >= 0.8'] +import pkg_resources + +import datetime +import unittest +import shutil +import sys +import os + +import six +import json +import pygit2 +from mock import patch, MagicMock + +sys.path.insert(0, os.path.join(os.path.dirname( + os.path.abspath(__file__)), '..')) + +import pagure.lib +import tests + + +class PagureFlaskAppIndextests(tests.Modeltests): + """ Tests for the index page of flask app controller of pagure """ + + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureFlaskAppIndextests, self).setUp() + + pagure.APP.config['TESTING'] = True + pagure.SESSION = self.session + pagure.ui.SESSION = self.session + pagure.ui.app.SESSION = self.session + pagure.ui.filters.SESSION = self.session + pagure.ui.repo.SESSION = self.session + + @patch.dict('pagure.APP.config', {'HTML_TITLE': 'Pagure HTML title set'}) + def test_index_html_title(self): + """ Test the index endpoint with a set html title. """ + + output = self.app.get('/') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Home - Pagure HTML title set', + output.data) + + def test_index_logged_out(self): + """ Test the index endpoint when logged out. """ + + output = self.app.get('/') + self.assertEqual(output.status_code, 200) + self.assertIn('Home - Pagure', output.data) + self.assertIn( + '

All Projects ' + '0

', output.data) + + tests.create_projects(self.session) + + output = self.app.get('/?page=abc') + self.assertEqual(output.status_code, 200) + self.assertIn( + '

All Projects ' + '3

', output.data) + + def test_index_logged_in(self): + """ Test the index endpoint when logged in. """ + tests.create_projects(self.session) + + # Add a 3rd project with a long description + item = pagure.lib.model.Project( + user_id=2, # foo + name='test3', + description='test project #3 with a very long description', + hook_token='aaabbbeeefff', + ) + self.session.add(item) + self.session.commit() + + user = tests.FakeUser(username='foo') + with tests.user_set(pagure.APP, user): + output = self.app.get('/?repopage=abc&forkpage=def') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + def test_index_commit_access_while_admin(self): + """ Test the index endpoint filter for commit access only when user + is an admin. """ + tests.create_projects(self.session) + + # Add a 3rd project just for foo + item = pagure.lib.model.Project( + user_id=2, # foo + name='test3', + description='test project #3 with a very long description', + hook_token='aaabbbeeefff', + ) + self.session.add(item) + self.session.commit() + + user = tests.FakeUser(username='foo') + with tests.user_set(pagure.APP, user): + # Before + output = self.app.get('/?acl=commit') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + # Add foo to test with admin level + project = pagure.lib._get_project(self.session, 'test') + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='foo', + user='pingou', + access='admin') + self.session.commit() + self.assertEqual(msg, 'User added') + + # After + output = self.app.get('/?acl=commit') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 2', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + def test_index_commit_access_while_commit(self): + """ Test the index endpoint filter for commit access only when user + is an committer. """ + tests.create_projects(self.session) + + # Add a 3rd project just for foo + item = pagure.lib.model.Project( + user_id=2, # foo + name='test3', + description='test project #3 with a very long description', + hook_token='aaabbbeeefff', + ) + self.session.add(item) + self.session.commit() + + user = tests.FakeUser(username='foo') + with tests.user_set(pagure.APP, user): + # Before + output = self.app.get('/?acl=commit') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + # Add foo to test with commit level + project = pagure.lib._get_project(self.session, 'test') + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='foo', + user='pingou', + access='commit') + self.session.commit() + self.assertEqual(msg, 'User added') + + # After + output = self.app.get('/?acl=commit') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 2', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + def test_index_commit_access_while_ticket(self): + """ Test the index endpoint filter for commit access only when user + is has ticket access. """ + tests.create_projects(self.session) + + # Add a 3rd project just for foo + item = pagure.lib.model.Project( + user_id=2, # foo + name='test3', + description='test project #3 with a very long description', + hook_token='aaabbbeeefff', + ) + self.session.add(item) + self.session.commit() + + user = tests.FakeUser(username='foo') + with tests.user_set(pagure.APP, user): + # Before + output = self.app.get('/?acl=ticket') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + # Add foo to test with ticket level + project = pagure.lib._get_project(self.session, 'test') + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='foo', + user='pingou', + access='ticket') + self.session.commit() + self.assertEqual(msg, 'User added') + + # After - projects with ticket access aren't shown + output = self.app.get('/?acl=ticket') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + def test_index_admin_access_while_commit(self): + """ Test the index endpoint filter for admin access only when user + is an admin. """ + tests.create_projects(self.session) + + # Add a 3rd project just for foo + item = pagure.lib.model.Project( + user_id=2, # foo + name='test3', + description='test project #3 with a very long description', + hook_token='aaabbbeeefff', + ) + self.session.add(item) + self.session.commit() + + user = tests.FakeUser(username='foo') + with tests.user_set(pagure.APP, user): + # Before + output = self.app.get('/?acl=admin') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + # Add foo to test with commit level + project = pagure.lib._get_project(self.session, 'test') + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='foo', + user='pingou', + access='admin') + self.session.commit() + self.assertEqual(msg, 'User added') + + # After + output = self.app.get('/?acl=admin') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 2', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + def test_index_admin_access_while_commit(self): + """ Test the index endpoint filter for admin access only when user + is an committer. """ + tests.create_projects(self.session) + + # Add a 3rd project just for foo + item = pagure.lib.model.Project( + user_id=2, # foo + name='test3', + description='test project #3 with a very long description', + hook_token='aaabbbeeefff', + ) + self.session.add(item) + self.session.commit() + + user = tests.FakeUser(username='foo') + with tests.user_set(pagure.APP, user): + # Before + output = self.app.get('/?acl=admin') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + # Add foo to test with commit level + project = pagure.lib._get_project(self.session, 'test') + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='foo', + user='pingou', + access='commit') + self.session.commit() + self.assertEqual(msg, 'User added') + + # After + output = self.app.get('/?acl=admin') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + def test_index_main_admin_access_while_commit(self): + """ Test the index endpoint filter for main admin access only when + user is an committer. """ + tests.create_projects(self.session) + + # Add a 3rd project just for foo + item = pagure.lib.model.Project( + user_id=2, # foo + name='test3', + description='test project #3 with a very long description', + hook_token='aaabbbeeefff', + ) + self.session.add(item) + self.session.commit() + + user = tests.FakeUser(username='foo') + with tests.user_set(pagure.APP, user): + # Before + output = self.app.get('/?acl=main admin') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + # Add foo to test with commit level + project = pagure.lib._get_project(self.session, 'test') + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='foo', + user='pingou', + access='commit') + self.session.commit() + self.assertEqual(msg, 'User added') + + # After + output = self.app.get('/?acl=main admin') + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Projects 1', + output.data) + self.assertIn( + 'Forks 0', + output.data) + self.assertEqual( + output.data.count('

No group found

'), 1) + self.assertEqual( + output.data.count('
'), 6) + + +if __name__ == '__main__': + unittest.main(verbosity=2) From ccbef11c2d46c3877e1a44ef811cda0266aa53fc Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Nov 13 2017 15:25:27 +0000 Subject: [PATCH 2/4] Couple of quick fixes to the documentation We're here including a document that wasn't listed anywhere (the explanations of the read only mode) and fixing a link that wasn't properly formated. Signed-off-by: Pierre-Yves Chibon --- diff --git a/doc/usage/index.rst b/doc/usage/index.rst index ca4583e..e8f2f8a 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -34,6 +34,7 @@ Contents: first_steps forks + read_only pull_requests markdown project_settings @@ -54,5 +55,5 @@ Contents: Pagure API ---------- -The API documentation can be found at `https://pagure.io/api/0/`_ +The API documentation can be found at `https://pagure.io/api/0/ `_ or in ``/api/0/`` of you local pagure instance. From 2ff10ff81c35635423f949dfde74a5d9e65c5317 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Nov 13 2017 15:29:34 +0000 Subject: [PATCH 3/4] More fixes to the doc, thanks @bowlofeggs Signed-off-by: Pierre-Yves Chibon --- diff --git a/doc/usage/tips_tricks.rst b/doc/usage/tips_tricks.rst index 4fc9ba9..11c92b1 100644 --- a/doc/usage/tips_tricks.rst +++ b/doc/usage/tips_tricks.rst @@ -44,15 +44,15 @@ any emails. Filter an user's projects by their access ----------------------------------------- -When watching an user's page, the list of all the project that user is +When watching a user's page, the list of all the project that user is involved in is presented regardless of whether the user has ticket, commit, admin access or is the main admin of the project. You can specify an ``acl=`` argument to the url to filter the list of -project by access. +projects by access. -.. note:: This also works for your home page when you are logged in +.. note:: This also works for your home page when you are logged in. Examples: From 314ec8600ca33cdcebc9e724f0fe96258316be2a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Nov 13 2017 15:29:49 +0000 Subject: [PATCH 4/4] Create an utility function to reduce code duplication Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/ui/app.py b/pagure/ui/app.py index 21b7d3e..07eeb05 100644 --- a/pagure/ui/app.py +++ b/pagure/ui/app.py @@ -36,6 +36,33 @@ def _get_user(username): except pagure.exceptions.PagureException as e: flask.abort(404, e.message) +def _filter_acls(repos, acl, user): + """ Filter the given list of repositories to return only the ones where + the user has the specified acl. + """ + if acl == 'commit': + repos = [ + repo + for repo in repos + if user in repo.committers + or user.username == repo.user.username + ] + elif acl == 'admin': + repos = [ + repo + for repo in repos + if user in repo.admins + or user.username == repo.user.username + ] + elif acl == 'main admin': + repos = [ + repo + for repo in repos + if user.username == repo.user.username + ] + + return repos + @APP.route('/browse/projects', endpoint='browse_projects') @APP.route('/browse/projects/', endpoint='browse_projects') @@ -111,26 +138,7 @@ def index_auth(): exclude_groups=APP.config.get('EXCLUDE_GROUP_INDEX'), fork=False, private=flask.g.fas_user.username) if repos and acl: - if acl == 'commit': - repos = [ - repo - for repo in repos - if user in repo.committers - or user.username == repo.user.username - ] - elif acl == 'admin': - repos = [ - repo - for repo in repos - if user in repo.admins - or user.username == repo.user.username - ] - elif acl == 'main admin': - repos = [ - repo - for repo in repos - if user.username == repo.user.username - ] + repos = _filter_acls(repos, acl, user) repos_length = len(repos) @@ -364,26 +372,7 @@ def view_user(username): private=private) if repos and acl: - if acl == 'commit': - repos = [ - repo - for repo in repos - if user in repo.committers - or user.username == repo.user.username - ] - elif acl == 'admin': - repos = [ - repo - for repo in repos - if user in repo.admins - or user.username == repo.user.username - ] - elif acl == 'main admin': - repos = [ - repo - for repo in repos - if user.username == repo.user.username - ] + repos = _filter_acls(repos, acl, user) repos_length = len(repos)