Browse Source

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 <pingou@pingoured.fr>
Pierre-Yves Chibon 5 years ago
parent
commit
9c6dee2446

+ 12 - 0
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",
         [

+ 17 - 3
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
     )

+ 7 - 10
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:

+ 101 - 0
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):
                 '<pre>\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(
+                '<title>Settings Pagure CI - somenamespace/test3 - '
+                'Pagure</title>', output_text)
+            self.assertIn(
+                '<label for="ci_url">URL to the project on the CI '
+                'service</label>', output_text)
+            self.assertIn(
+                '<label for="ci_job">Name of the job to trigger'
+                '</label>', output_text)
+            self.assertIn(
+                '<input class="form-check-input mt-2" id="active_pr" name="active_pr" '
+                'type="checkbox" value="y">', 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(
+                '<h5 class="pl-2 font-weight-bold text-muted">'
+                'Project Settings</h5>\n', output_text)
+            self.assertIn(
+                '<title>Settings - somenamespace/test3 - Pagure</title>',
+                output_text)
+            self.assertIn(
+                '<h5 class="pl-2 font-weight-bold text-muted">'
+                'Project Settings</h5>\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(
+                '<title>Settings Pagure CI - somenamespace/test3 - '
+                'Pagure</title>', output_text)
+            self.assertIn(
+                '<label for="ci_url">URL to the project on the CI '
+                'service</label>', output_text)
+            self.assertIn(
+                '<label for="ci_job">Name of the job to trigger'
+                '</label>', output_text)
+            self.assertIn(
+                '<input checked class="form-check-input mt-2" id="active_pr" name="active_pr" '
+                'type="checkbox" value="y">', output_text)
+            self.assertIn(
+                '<pre>\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)

+ 114 - 0
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',