diff --git a/pagure/default_config.py b/pagure/default_config.py index b151e2b..b19c37c 100644 --- a/pagure/default_config.py +++ b/pagure/default_config.py @@ -54,6 +54,9 @@ ENABLE_NEW_PROJECTS = True # Enables / Disables deleting projects on this pagure instance ENABLE_DEL_PROJECTS = True +# Enables / Disables giving projects on this pagure instance +ENABLE_GIVE_PROJECTS = True + # Enables / Disables managing access to the repos ENABLE_USER_MNGT = True diff --git a/pagure/templates/settings.html b/pagure/templates/settings.html index ff26915..3476bb2 100644 --- a/pagure/templates/settings.html +++ b/pagure/templates/settings.html @@ -1059,6 +1059,36 @@ {% endif %} + {% if config.get('ENABLE_GIVE_PROJECTS', True) + and repo.user.user == g.fas_user.username + and not repo.is_fork %} +
+
+
+ Give Project +
+
+
+ {{ tag_form.csrf_token }} + + +
+
+
+
+ {% endif %} + {% if config.get('ENABLE_DEL_PROJECTS', True) %}
@@ -1216,5 +1246,28 @@ $('.extend-form').click(function(e) { $(tgt).append(form); }); +{% if config.get('ENABLE_GIVE_PROJECTS', True) + and repo.user.user == g.fas_user.username + and not repo.is_fork %} +$('#user').selectize({ + valueField: 'user', + labelField: 'user', + searchField: 'user', + maxItems: 1, + create: false, + load: function(query, callback) { + if (!query.length) return callback(); + $.getJSON( + "{{ url_for('api_ns.api_users') }}", { + pattern: query.term + }, + function( data ) { + callback( data.users.map(function(x) { return { user: x }; }) ); + } + ); + } +}); +{% endif %} + {% endblock %} diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index edd6237..b304a52 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -2560,3 +2560,60 @@ def delete_report(repo, username=None, namespace=None): return flask.redirect(flask.url_for( 'view_settings', username=username, repo=repo.name, namespace=namespace)) + + +@APP.route('//give', methods=['POST']) +@APP.route('///give', methods=['POST']) +@APP.route('/fork///give', methods=['POST']) +@APP.route( + '/fork////give', + methods=['POST']) +@login_required +def give_project(repo, username=None, namespace=None): + """ Give a project to someone else. + """ + if not APP.config.get('ENABLE_GIVE_PROJECTS', True): + flask.abort(404) + + if admin_session_timedout(): + flask.flash('Action canceled, try it again', 'error') + url = flask.url_for( + 'view_settings', username=username, repo=repo, + namespace=namespace) + return flask.redirect( + flask.url_for('auth_login', next=url)) + + repo = flask.g.repo + + if not flask.g.repo_admin: + flask.abort( + 403, + 'You are not allowed to change the settings for this project') + + if flask.g.fas_user.username != repo.user.user and not pagure.is_admin(): + flask.abort( + 403, + 'You are not allowed to give this project') + + form = pagure.forms.ConfirmationForm() + + if form.validate_on_submit(): + new_username = flask.request.form.get('user', '').strip() + new_owner = pagure.lib.search_user( + SESSION, username=new_username) + if not new_owner: + flask.abort( + 500, + 'No such user %s found' % new_username) + try: + repo.user = new_owner + SESSION.add(repo) + SESSION.commit() + flask.flash('Project updated') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + flask.flash(str(err), 'error') + + return flask.redirect(flask.url_for( + 'view_repo', username=username, repo=repo.name, + namespace=namespace)) diff --git a/tests/test_pagure_flask_ui_app_give_project.py b/tests/test_pagure_flask_ui_app_give_project.py new file mode 100644 index 0000000..66399be --- /dev/null +++ b/tests/test_pagure_flask_ui_app_give_project.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2017 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +__requires__ = ['SQLAlchemy >= 0.8'] +import pkg_resources + +import unittest +import shutil +import sys +import tempfile +import os + +from mock import patch, MagicMock + +sys.path.insert(0, os.path.join(os.path.dirname( + os.path.abspath(__file__)), '..')) + +import pagure +import pagure.lib +import tests + + +class PagureFlaskGiveRepotests(tests.Modeltests): + """ Tests for give a project on pagure """ + + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureFlaskGiveRepotests, 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 + + pagure.APP.config['VIRUS_SCAN_ATTACHMENTS'] = False + pagure.APP.config['GIT_FOLDER'] = self.path + pagure.APP.config['REQUESTS_FOLDER'] = os.path.join( + self.path, 'requests') + pagure.APP.config['TICKETS_FOLDER'] = os.path.join( + self.path, 'tickets') + pagure.APP.config['DOCS_FOLDER'] = os.path.join( + self.path, 'docs') + pagure.APP.config['UPLOAD_FOLDER_URL'] = '/releases/' + pagure.APP.config['UPLOAD_FOLDER_PATH'] = os.path.join( + self.path, 'releases') + self.app = pagure.APP.test_client() + + tests.create_projects(self.session) + tests.create_projects_git(self.path, bare=True) + + def _check_user(self, user='pingou'): + project = pagure.get_authorized_project( + self.session, project_name='test') + self.assertEqual(project.user.user, user) + + def test_give_project_no_project(self): + """ Test the give_project endpoint. """ + + # No such project + output = self.app.post('/test42/give') + self.assertEqual(output.status_code, 404) + + def test_give_project_no_csrf(self): + """ Test the give_project endpoint. """ + + user = tests.FakeUser() + user.username = 'pingou' + with tests.user_set(pagure.APP, user): + + self._check_user() + + # Missing CSRF + data = { + 'user': 'foo', + } + + output = self.app.post( + '/test/give', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Overview - test - Pagure', + output.data) + + self._check_user() + + def test_give_project_invalid_user(self): + """ Test the give_project endpoint. """ + + user = tests.FakeUser() + user.username = 'pingou' + with tests.user_set(pagure.APP, user): + csrf_token = self.get_csrf() + + self._check_user() + + # Invalid user + data = { + 'user': 'foobar', + 'csrf_token': csrf_token, + } + + output = self.app.post( + '/test/give', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 500) + self.assertIn( + '

No such user foobar found

', + output.data) + + self._check_user() + + def test_give_project_not_owner(self): + """ Test the give_project endpoint. """ + + user = tests.FakeUser() + user.username = 'foo' + with tests.user_set(pagure.APP, user): + csrf_token = self.get_csrf() + + self._check_user() + + # User isn't the admin + data = { + 'user': 'foo', + 'csrf_token': csrf_token, + } + + output = self.app.post( + '/test/give', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 403) + self.assertIn( + '

You are not allowed to change the settings for this ' + 'project

', output.data) + + self._check_user() + + def test_give_project_not_admin(self): + """ Test the give_project endpoint. """ + + user = tests.FakeUser() + user.username = 'foo' + with tests.user_set(pagure.APP, user): + csrf_token = self.get_csrf() + + self._check_user() + + # User isn't the admin + data = { + 'user': 'foo', + 'csrf_token': csrf_token, + } + + output = self.app.post( + '/test/give', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 403) + self.assertIn( + '

You are not allowed to change the settings for this ' + 'project

', output.data) + + self._check_user() + + def test_give_project_not_owner(self): + """ Test the give_project endpoint. """ + project = pagure.get_authorized_project( + self.session, project_name='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') + + user = tests.FakeUser() + user.username = 'foo' + with tests.user_set(pagure.APP, user): + csrf_token = self.get_csrf() + + self._check_user() + + # User isn't the owner + data = { + 'user': 'foo', + 'csrf_token': csrf_token, + } + + output = self.app.post( + '/test/give', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 403) + self.assertIn( + '

You are not allowed to give this project

', + output.data) + + self._check_user() + + @patch.dict('pagure.APP.config', {'PAGURE_ADMIN_USERS': 'foo'}) + def test_give_project_not_owner_but_admin(self): + """ Test the give_project endpoint. """ + + user = tests.FakeUser() + user.username = 'foo' + user.cla_done = True + user.groups = ['foo'] + with tests.user_set(pagure.APP, user): + csrf_token = self.get_csrf() + + self._check_user() + + # User isn't the owner but is an instance admin + data = { + 'user': 'foo', + 'csrf_token': csrf_token, + } + + output = self.app.post( + '/test/give', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + '\n Project updated\n', + output.data) + + self._check_user('foo') + + @patch.dict('pagure.APP.config', {'PAGURE_ADMIN_USERS': 'foo'}) + def test_give_project(self): + """ Test the give_project endpoint. """ + + user = tests.FakeUser() + user.username = 'pingou' + with tests.user_set(pagure.APP, user): + csrf_token = self.get_csrf() + + self._check_user() + + # All good + data = { + 'user': 'foo', + 'csrf_token': csrf_token, + } + + output = self.app.post( + '/test/give', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + '\n Project updated\n', + output.data) + + self._check_user('foo') + + +if __name__ == '__main__': + unittest.main(verbosity=2)