Skip to content

Commit

Permalink
Preventively patch xmlrpc using the defusedxml package
Browse files Browse the repository at this point in the history
and don't import directly from the standard xmlrpc package eventhough it
is mostly used during testing.

only tcms/issuestracker/bugzilla.py was using xmlrpc directly!
  • Loading branch information
atodorov committed Dec 22, 2023
1 parent a0e7145 commit eb010d2
Show file tree
Hide file tree
Showing 24 changed files with 242 additions and 27 deletions.
1 change: 1 addition & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
allpairspy==2.5.1
bleach==6.1.0
bleach-allowlist==1.0.3
defusedxml==0.7.1
Django==4.2.8
django-attachments==1.11
django-colorfield==0.11.0
Expand Down
3 changes: 2 additions & 1 deletion tcms/bugs/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# pylint: disable=attribute-defined-outside-init
# pylint: disable=wrong-import-position
import unittest
from xmlrpc.client import Fault as XmlRPCFault

import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault
from django.conf import settings

if "tcms.bugs.apps.AppConfig" not in settings.INSTALLED_APPS:
Expand Down
7 changes: 7 additions & 0 deletions tcms/core/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ class AppConfig(DjangoAppConfig):
name = "tcms.core"

def ready(self):
# B411:blacklist
# CWE: CWE-20 (https://cwe.mitre.org/data/definitions/20.html)
# https://bandit.readthedocs.io/en/1.7.6/blacklists/blacklist_imports.html#b411-import-xmlrpclib
import defusedxml

defusedxml.xmlrpc.monkey_patch()

from tcms.core import checks

register(checks.check_installation_id)
3 changes: 2 additions & 1 deletion tcms/issuetracker/bugzilla_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import os
import tempfile
from urllib.parse import urlencode
from xmlrpc.client import Fault

import bugzilla
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault
from django.conf import settings

from tcms.core.contrib.linkreference.models import LinkReference
Expand Down
1 change: 0 additions & 1 deletion tcms/issuetracker/tests/test_gitlab_ce.py

This file was deleted.

192 changes: 192 additions & 0 deletions tcms/issuetracker/tests/test_gitlab_ce.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# pylint: disable=attribute-defined-outside-init

import os
import time
import unittest

from tcms.core.contrib.linkreference.models import LinkReference
from tcms.issuetracker.types import Gitlab
from tcms.rpc.tests.utils import APITestCase
from tcms.testcases.models import BugSystem
from tcms.tests.factories import ComponentFactory, TestExecutionFactory


@unittest.skipUnless(
os.getenv("TEST_BUGTRACKER_INTEGRATION"),
"Bug tracker integration testing not enabled",
)
class TestGitlabIntegration(APITestCase):
existing_bug_id = 1
existing_bug_url = "http://bugtracker.kiwitcms.org/root/kiwitcms/issues/1"
existing_bug_url_in_group = (
"http://bugtracker.kiwitcms.org/group/sub_group/kiwitcms_in_group/issues/1"
)

def _fixture_setup(self):
super()._fixture_setup()

self.execution_1 = TestExecutionFactory()
self.execution_1.case.text = "Given-When-Then"
self.execution_1.case.save() # will generate history object

self.component = ComponentFactory(
name="Gitlab integration", product=self.execution_1.run.plan.product
)
self.execution_1.case.add_component(self.component)

bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name="GitLab-EE for root/kiwitcms",
tracker_type="tcms.issuetracker.types.Gitlab",
base_url="http://bugtracker.kiwitcms.org/root/kiwitcms/",
api_url="http://bugtracker.kiwitcms.org",
api_password="ypCa3Dzb23o5nvsixwPA",
)
self.integration = Gitlab(bug_system, None)

def test_bug_id_from_url(self):
result = self.integration.bug_id_from_url(self.existing_bug_url)
self.assertEqual(self.existing_bug_id, result)

# this is an alternative URL, with a dash
result = self.integration.bug_id_from_url(
"http://bugtracker.kiwitcms.org/root/kiwitcms/-/issues/1"
)
self.assertEqual(self.existing_bug_id, result)

def test_bug_id_from_url_in_group(self):
bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name="GitLab-EE for group/sub_group/kiwitcms_in_group",
tracker_type="tcms.issuetracker.types.Gitlab",
base_url="http://bugtracker.kiwitcms.org/group/sub_group/kiwitcms_in_group/",
api_url="http://bugtracker.kiwitcms.org",
api_password="ypCa3Dzb23o5nvsixwPA",
)
integration = Gitlab(bug_system, None)

result = integration.bug_id_from_url(self.existing_bug_url_in_group)
self.assertEqual(self.existing_bug_id, result)

# this is an alternative URL, with a dash
result = integration.bug_id_from_url(
"http://bugtracker.kiwitcms.org/group/sub_group/kiwitcms_in_group/-/issues/1"
)
self.assertEqual(self.existing_bug_id, result)

def test_details_for_public_url(self):
result = self.integration.details(self.existing_bug_url)

self.assertEqual("Hello GitLab", result["title"])
self.assertEqual("Created via CLI", result["description"])

def test_details_for_public_url_in_group(self):
bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name="GitLab-EE for group/sub_group/kiwitcms_in_group",
tracker_type="tcms.issuetracker.types.Gitlab",
base_url="http://bugtracker.kiwitcms.org/group/sub_group/kiwitcms_in_group/",
api_url="http://bugtracker.kiwitcms.org",
api_password="ypCa3Dzb23o5nvsixwPA",
)
integration = Gitlab(bug_system, None)

result = integration.details(self.existing_bug_url_in_group)

self.assertEqual("Hello GitLab Group", result["title"])
self.assertEqual("Created via CLI", result["description"])

def test_details_for_private_url(self):
bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name="Private GitLab for root/katinar",
tracker_type="tcms.issuetracker.types.Gitlab",
base_url="http://bugtracker.kiwitcms.org/root/katinar/",
api_url="http://bugtracker.kiwitcms.org",
api_password="ypCa3Dzb23o5nvsixwPA",
)
integration = Gitlab(bug_system, None)

result = integration.details(
"http://bugtracker.kiwitcms.org/root/katinar/-/issues/1"
)

self.assertEqual("Hello Private Issue", result["title"])
self.assertEqual("Created in secret via CLI", result["description"])

def test_auto_update_bugtracker(self):
repo_id = self.integration.repo_id
gl_project = self.integration.rpc.projects.get(repo_id)
gl_issue = gl_project.issues.get(self.existing_bug_id)

# make sure there are no comments to confuse the test
initial_comment_count = 0
for comment in gl_issue.notes.list():
initial_comment_count += 1
self.assertNotIn("Confirmed via test execution", comment.body)

# simulate user adding a new bug URL to a TE and clicking
# 'Automatically update bug tracker'
result = self.rpc_client.TestExecution.add_link(
{
"execution_id": self.execution_1.pk,
"is_defect": True,
"url": self.existing_bug_url,
},
True,
)

# making sure RPC above returned the same URL
self.assertEqual(self.existing_bug_url, result["url"])

# wait until comments have been refreshed b/c this seem to happen async
retries = 0
while len(gl_issue.notes.list()) <= initial_comment_count:
time.sleep(1)
retries += 1
self.assertLess(retries, 20)

# sort by id b/c the gitlab library returns newest comments first but
# that may be depending on configuration !
last_comment = sorted(gl_issue.notes.list(), key=lambda x: x.id)[-1]

# assert that a comment has been added as the last one
# and also verify its text
for expected_string in [
"Confirmed via test execution",
f"TR-{self.execution_1.run_id}: {self.execution_1.run.summary}",
self.execution_1.run.get_full_url(),
f"TE-{self.execution_1.pk}: {self.execution_1.case.summary}",
]:
self.assertIn(expected_string, last_comment.body)

def test_report_issue_from_test_execution_1click_works(self):
# simulate user clicking the 'Report bug' button in TE widget, TR page
result = self.rpc_client.Bug.report(
self.execution_1.pk, self.integration.bug_system.pk
)
self.assertEqual(result["rc"], 0)
self.assertIn(self.integration.bug_system.base_url, result["response"])
self.assertIn("/-/issues/", result["response"])

# assert that the result looks like valid URL parameters
new_issue_id = self.integration.bug_id_from_url(result["response"])
repo_id = self.integration.repo_id
gl_project = self.integration.rpc.projects.get(repo_id)
issue = gl_project.issues.get(new_issue_id)

self.assertEqual(f"Failed test: {self.execution_1.case.summary}", issue.title)
for expected_string in [
f"Filed from execution {self.execution_1.get_full_url()}",
"Reporter",
self.execution_1.build.version.product.name,
self.component.name,
"Steps to reproduce",
self.execution_1.case.text,
]:
self.assertIn(expected_string, issue.description)

# verify that LR has been added to TE
self.assertTrue(
LinkReference.objects.filter(
execution=self.execution_1,
url=result["response"],
is_defect=True,
).exists()
)
3 changes: 2 additions & 1 deletion tcms/kiwi_attachments/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
# pylint: disable=attribute-defined-outside-init, invalid-name, objects-update-used

import base64
from xmlrpc.client import Fault

import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault
from django.utils.translation import gettext_lazy as _
from parameterized import parameterized

Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_attachment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init, invalid-name, objects-update-used

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase
from tcms.tests import user_should_have_perm
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APITestCase

Expand Down
4 changes: 2 additions & 2 deletions tcms/rpc/tests/test_build.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=invalid-name, attribute-defined-outside-init, objects-update-used

from xmlrpc.client import Fault as XmlRPCFault

import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault
from django.test import override_settings

from tcms.rpc.tests.utils import APITestCase
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_category.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init, invalid-name, objects-update-used

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase
from tcms.testcases.models import Category
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_classification.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.management.models import Classification
from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_component.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init, invalid-name

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APITestCase
from tcms.tests.factories import ComponentFactory, ProductFactory
Expand Down
4 changes: 2 additions & 2 deletions tcms/rpc/tests/test_environment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from xmlrpc.client import Fault as XmlRPCFault

import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault
from django.test import override_settings

from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_plantype.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APIPermissionsTestCase
from tcms.testplans.models import PlanType
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_priority.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase

Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_product.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.management.models import Product
from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

import unittest
from datetime import timedelta
from xmlrpc.client import Fault as XmlRPCFault

import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from attachments.models import Attachment
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault
from django.contrib.auth.models import Permission
from django.core.exceptions import ValidationError
from parameterized import parameterized
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_testcasestatus.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase

Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_testexecution.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
# pylint: disable=invalid-name, attribute-defined-outside-init, objects-update-used

import time
from xmlrpc.client import Fault as XmlRPCFault

import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault
from django.forms.models import model_to_dict
from django.test import override_settings
from django.utils import timezone
Expand Down
3 changes: 2 additions & 1 deletion tcms/rpc/tests/test_testexecutionstatus.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=attribute-defined-outside-init, invalid-name, objects-update-used

from xmlrpc.client import Fault as XmlRPCFault
import defusedxml.xmlrpc

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'defusedxml' is not used.
from defusedxml.xmlrpc.xmlrpc_client import Fault as XmlRPCFault

from tcms.rpc.tests.utils import APIPermissionsTestCase, APITestCase

Expand Down
Loading

0 comments on commit eb010d2

Please sign in to comment.