From 9c6dee24463fdbfe88c21cfa4a6d7d3af87a5b7c Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 18 2019 13:12:51 +0000 Subject: Add support for username and password based authentication for pagure-ci We used to query jenkins directly via an username provided URL, we switch to using the python jenkins library, which requires username and password to be specified in a different way. This commit asks for the user to specify the username and password to use to authenticate to jenkins and provides them to the python jenkins library as they should be. Fixes https://pagure.io/pagure/issue/4120 Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/hooks/pagure_ci.py b/pagure/hooks/pagure_ci.py index ee999a8..795a050 100644 --- a/pagure/hooks/pagure_ci.py +++ b/pagure/hooks/pagure_ci.py @@ -46,6 +46,8 @@ class PagureCITable(BASE): ci_type = sa.Column(sa.String(255), nullable=True) ci_url = sa.Column(sa.String(255), nullable=True, unique=False) ci_job = sa.Column(sa.String(255), nullable=True, unique=False) + ci_username = sa.Column(sa.String(255), nullable=True, unique=False) + ci_password = sa.Column(sa.String(255), nullable=True, unique=False) active = sa.Column(sa.Boolean, nullable=False, default=False) active_commit = sa.Column(sa.Boolean, nullable=False, default=False) active_pr = sa.Column(sa.Boolean, nullable=False, default=False) @@ -114,6 +116,16 @@ class PagureCiForm(FlaskForm): ], ) + ci_username = wtforms.StringField( + "Username to authenticate with if needed", + [wtforms.validators.Optional(), wtforms.validators.Length(max=255)], + ) + + ci_password = wtforms.StringField( + "Password to authenticate with if needed", + [wtforms.validators.Optional(), wtforms.validators.Length(max=255)], + ) + ci_job = wtforms.StringField( "Name of the job to trigger", [ diff --git a/pagure/lib/lib_ci.py b/pagure/lib/lib_ci.py index f2a30ec..eba958a 100644 --- a/pagure/lib/lib_ci.py +++ b/pagure/lib/lib_ci.py @@ -42,7 +42,11 @@ def process_jenkins_build(session, project, build_id, iteration=0): # Jenkins Base URL _log.info("Querying jenkins at: %s", project.ci_hook.ci_url) - jenk = jenkins.Jenkins(project.ci_hook.ci_url) + jenk = jenkins.Jenkins( + project.ci_hook.ci_url, + username=project.ci_hook.ci_username or None, + password=project.ci_hook.ci_password or None, + ) jenkins_name = project.ci_hook.ci_job _log.info( "Querying jenkins for project: %s, build: %s", jenkins_name, build_id @@ -130,7 +134,15 @@ def process_jenkins_build(session, project, build_id, iteration=0): def trigger_jenkins_build( - project_path, url, job, token, branch, branch_to, cause + project_path, + url, + job, + token, + branch, + branch_to, + cause, + ci_username=None, + ci_password=None, ): """ Trigger a build on a jenkins instance.""" try: @@ -150,7 +162,9 @@ def trigger_jenkins_build( "BRANCH_TO": branch_to, } - server = jenkins.Jenkins(url) + server = jenkins.Jenkins( + url, username=ci_username or None, password=ci_password or None + ) _log.info( "Pagure-CI: Triggering at: %s for: %s - data: %s", url, job, data ) diff --git a/pagure/lib/tasks_services.py b/pagure/lib/tasks_services.py index 4cef4e9..eb78d0a 100644 --- a/pagure/lib/tasks_services.py +++ b/pagure/lib/tasks_services.py @@ -463,23 +463,20 @@ def trigger_ci_build( if ci_type == "jenkins": + jenk_project = project if project.is_fork: - url = project.parent.ci_hook.ci_url - job = project.parent.ci_hook.ci_job - token = project.parent.ci_hook.pagure_ci_token - else: - url = project.ci_hook.ci_url - job = project.ci_hook.ci_job - token = project.ci_hook.pagure_ci_token + jenk_project = project.parent trigger_jenkins_build( project_path=project.path, - url=url, - job=job, - token=token, + url=jenk_project.ci_hook.ci_url, + job=jenk_project.ci_hook.ci_job, + token=jenk_project.ci_hook.pagure_ci_token, branch=branch, branch_to=branch_to, cause=cause, + ci_username=jenk_project.ci_hook.ci_username, + ci_password=jenk_project.ci_hook.ci_password, ) else: diff --git a/tests/test_pagure_flask_ui_plugins_pagure_ci.py b/tests/test_pagure_flask_ui_plugins_pagure_ci.py index 1604dc2..a32aaa1 100644 --- a/tests/test_pagure_flask_ui_plugins_pagure_ci.py +++ b/tests/test_pagure_flask_ui_plugins_pagure_ci.py @@ -6,6 +6,12 @@ import unittest import sys import os +import mock + +sys.path.insert(0, os.path.join(os.path.dirname( + os.path.abspath(__file__)), '..')) + +import pagure.lib.tasks_services import tests @@ -277,6 +283,101 @@ class PagureFlaskPluginPagureCItests(tests.SimplePagureTest): '
\nhttp://localhost.localdomain/api/0/ci/jenkins/somenamespace/test3/',
                 output_text)
 
+    @mock.patch('pagure.lib.tasks_services.trigger_jenkins_build')
+    def test_plugin_pagure_ci_namespaced_auth(self, trigger_jenk):
+        """ Test the pagure ci plugin on/off endpoint. """
+
+        tests.create_projects(self.session)
+        tests.create_projects_git(os.path.join(self.path, 'repos'))
+
+        user = tests.FakeUser(username='pingou')
+        with tests.user_set(self.app.application, user):
+            output = self.app.get('/somenamespace/test3/settings/Pagure CI')
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                'Settings Pagure CI - somenamespace/test3 - '
+                'Pagure', output_text)
+            self.assertIn(
+                '', output_text)
+            self.assertIn(
+                '', output_text)
+            self.assertIn(
+                '', output_text)
+
+            csrf_token = output_text.split(
+                'name="csrf_token" type="hidden" value="')[1].split('">')[0]
+
+            # Activate hook
+            data = {
+                'active_pr': 'y',
+                'ci_url': 'https://jenkins.fedoraproject.org',
+                'ci_job': 'test/job',
+                'ci_type': 'jenkins',
+                'ci_username': 'jenkins_username',
+                'ci_password': 'jenkins_password',
+                'csrf_token': csrf_token,
+            }
+
+            # Activate hook
+            output = self.app.post(
+                '/somenamespace/test3/settings/Pagure CI', data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '
' + 'Project Settings
\n', output_text) + self.assertIn( + 'Settings - somenamespace/test3 - Pagure', + output_text) + self.assertIn( + '
' + 'Project Settings
\n', output_text) + self.assertIn( + 'Hook Pagure CI activated', + output_text) + + output = self.app.get('/somenamespace/test3/settings/Pagure CI') + self.assertEqual(output.status_code, 200) + output_text = output.get_data(as_text=True) + self.assertIn( + 'Settings Pagure CI - somenamespace/test3 - ' + 'Pagure', output_text) + self.assertIn( + '', output_text) + self.assertIn( + '', output_text) + self.assertIn( + '', output_text) + self.assertIn( + '
\nhttp://localhost.localdomain/api/0/ci/jenkins/somenamespace/test3/',
+                output_text)
+
+        output = pagure.lib.tasks_services.trigger_ci_build(
+            project_name='somenamespace/test3',
+            cause='PR#ID',
+            branch='feature',
+            branch_to='master',
+            ci_type='jenkins')
+        self.assertIsNone(output)
+        trigger_jenk.assert_called_once_with(
+           branch=u'feature',
+           cause=u'PR#ID',
+           ci_password="jenkins_password",
+           ci_username="jenkins_username",
+           job=u'test/job',
+           project_path=u'somenamespace/test3.git',
+           token=mock.ANY,
+           url=u'https://jenkins.fedoraproject.org',
+           branch_to='master',
+        )
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/tests/test_pagure_lib_task_services.py b/tests/test_pagure_lib_task_services.py
index a5063bd..866bbeb 100644
--- a/tests/test_pagure_lib_task_services.py
+++ b/tests/test_pagure_lib_task_services.py
@@ -587,6 +587,8 @@ class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
         trigger_jenk.assert_called_once_with(
            branch=u'feature',
            cause=u'PR#ID',
+           ci_password=None,
+           ci_username=None,
            job=u'pagure',
            project_path=u'test.git',
            token=u'random_token',
@@ -607,6 +609,118 @@ class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
         trigger_jenk.assert_called_once_with(
            branch=u'feature',
            cause=u'PR#ID',
+           ci_password=None,
+           ci_username=None,
+           job=u'pagure',
+           project_path=u'forks/foo/test.git',
+           token=u'random_token',
+           url=u'https://ci.server.org/',
+           branch_to='master',
+        )
+
+
+class PagureLibTaskServicesJenkinsCIAuthtests(tests.Modeltests):
+    """ Tests for pagure.lib.task_services """
+
+    maxDiff = None
+
+    def setUp(self):
+        """ Set up the environnment, ran before every tests. """
+        super(PagureLibTaskServicesJenkinsCIAuthtests, self).setUp()
+
+        pagure.config.config['REQUESTS_FOLDER'] = None
+        self.sshkeydir = os.path.join(self.path, 'sshkeys')
+        pagure.config.config['MIRROR_SSHKEYS_FOLDER'] = self.sshkeydir
+
+        tests.create_projects(self.session)
+        project = pagure.lib.query.get_authorized_project(self.session, 'test')
+
+        # Install the plugin at the DB level
+        plugin = pagure.lib.plugins.get_plugin('Pagure CI')
+        dbobj = plugin.db_object()
+        dbobj.ci_url = 'https://ci.server.org/'
+        dbobj.ci_job = 'pagure'
+        dbobj.ci_username = 'jenkins_username'
+        dbobj.ci_password = 'jenkins_password'
+        dbobj.pagure_ci_token = 'random_token'
+        dbobj.project_id = project.id
+        self.session.add(dbobj)
+        self.session.commit()
+
+        # Create a fork of test for foo
+        item = pagure.lib.model.Project(
+            user_id=2,  # foo
+            name='test',
+            is_fork=True,
+            parent_id=1,
+            description='test project #1',
+            hook_token='aaabbbccc_foo',
+        )
+        item.close_status = ['Invalid', 'Insufficient data', 'Fixed', 'Duplicate']
+        self.session.add(item)
+        self.session.commit()
+
+    @patch('pagure.lib.tasks_services.trigger_jenkins_build')
+    def test_trigger_ci_build_invalid_ci(self, trigger_jenk):
+        """ Test the trigger_ci_build method. """
+        output = pagure.lib.tasks_services.trigger_ci_build(
+            project_name='test',
+            cause='PR#ID',
+            branch='feature',
+            branch_to='master',
+            ci_type='travis')
+        self.assertIsNone(output)
+        trigger_jenk.assert_not_called()
+
+    @patch('pagure.lib.tasks_services.trigger_jenkins_build')
+    def test_trigger_ci_build_invalid_ci_fork(self, trigger_jenk):
+        """ Test the trigger_ci_build method. """
+        output = pagure.lib.tasks_services.trigger_ci_build(
+            project_name='forks/foo/test',
+            cause='PR#ID',
+            branch='feature',
+            branch_to='master',
+            ci_type='travis')
+        self.assertIsNone(output)
+        trigger_jenk.assert_not_called()
+
+    @patch('pagure.lib.tasks_services.trigger_jenkins_build')
+    def test_trigger_ci_build_valid_project(self, trigger_jenk):
+        """ Test the trigger_ci_build method. """
+        output = pagure.lib.tasks_services.trigger_ci_build(
+            project_name='test',
+            cause='PR#ID',
+            branch='feature',
+            branch_to='master',
+            ci_type='jenkins')
+        self.assertIsNone(output)
+        trigger_jenk.assert_called_once_with(
+           branch=u'feature',
+           cause=u'PR#ID',
+           ci_password="jenkins_password",
+           ci_username="jenkins_username",
+           job=u'pagure',
+           project_path=u'test.git',
+           token=u'random_token',
+           url=u'https://ci.server.org/',
+           branch_to='master',
+        )
+
+    @patch('pagure.lib.tasks_services.trigger_jenkins_build')
+    def test_trigger_ci_build_valid_project_fork(self, trigger_jenk):
+        """ Test the trigger_ci_build method. """
+        output = pagure.lib.tasks_services.trigger_ci_build(
+            project_name='forks/foo/test',
+            cause='PR#ID',
+            branch='feature',
+            branch_to='master',
+            ci_type='jenkins')
+        self.assertIsNone(output)
+        trigger_jenk.assert_called_once_with(
+           branch=u'feature',
+           cause=u'PR#ID',
+           ci_password="jenkins_password",
+           ci_username="jenkins_username",
            job=u'pagure',
            project_path=u'forks/foo/test.git',
            token=u'random_token',