mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat(repository): add new check repository_default_branch_requires_conversation_resolution (#6208)
Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
committed by
GitHub
parent
beb0457aff
commit
451b36093f
@@ -16,6 +16,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- Add `repository_default_branch_deletion_disabled` check for GitHub provider. [(#6200)](https://github.com/prowler-cloud/prowler/pull/6200)
|
||||
- Add `repository_default_branch_status_checks_required` check for GitHub provider. [(#6204)](https://github.com/prowler-cloud/prowler/pull/6204)
|
||||
- Add `repository_default_branch_protection_applies_to_admins` check for GitHub provider. [(#6205)](https://github.com/prowler-cloud/prowler/pull/6205)
|
||||
- Add `repository_default_branch_requires_conversation_resolution` check for GitHub provider. [(#6208)](https://github.com/prowler-cloud/prowler/pull/6208)
|
||||
- Add `organization_members_mfa_required` check for GitHub provider. [(#6304)](https://github.com/prowler-cloud/prowler/pull/6304)
|
||||
- Add GitHub provider documentation and CIS v1.0.0 compliance. [(#6116)](https://github.com/prowler-cloud/prowler/pull/6116)
|
||||
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "github",
|
||||
"CheckID": "repository_default_branch_requires_conversation_resolution",
|
||||
"CheckTitle": "Check if repository requires conversation resolution before merging",
|
||||
"CheckType": [],
|
||||
"ServiceName": "repository",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "GitHubRepository",
|
||||
"Description": "Ensure that the repository requires conversation resolution before merging.",
|
||||
"Risk": "Leaving comments unresolved before merging code can lead to overlooked issues, including potential bugs or security vulnerabilities, that might affect the quality and security of the codebase. Unaddressed concerns could result in a lower quality of code, increasing the risk of production failures or breaches.",
|
||||
"RelatedUrl": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-conversation-resolution-before-merging",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that all comments in a code change proposal are resolved before merging. This guarantees that every reviewer’s concern is addressed, improving code quality and security by preventing issues from being ignored or overlooked.",
|
||||
"Url": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportGithub
|
||||
from prowler.providers.github.services.repository.repository_client import (
|
||||
repository_client,
|
||||
)
|
||||
|
||||
|
||||
class repository_default_branch_requires_conversation_resolution(Check):
|
||||
"""Check if a repository requires conversation resolution
|
||||
|
||||
This class verifies whether each repository requires conversation resolution.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportGithub]:
|
||||
"""Execute the Github Repository Merging Requires Conversation Resolution check
|
||||
|
||||
Iterates over all repositories and checks if they require conversation resolution.
|
||||
|
||||
Returns:
|
||||
List[CheckReportGithub]: A list of reports for each repository
|
||||
"""
|
||||
findings = []
|
||||
for repo in repository_client.repositories.values():
|
||||
if repo.conversation_resolution is not None:
|
||||
report = CheckReportGithub(
|
||||
metadata=self.metadata(), resource=repo, repository=repo.name
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Repository {repo.name} does not require conversation resolution."
|
||||
)
|
||||
|
||||
if repo.conversation_resolution:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Repository {repo.name} does require conversation resolution."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -38,6 +38,7 @@ class Repository(GithubService):
|
||||
branch_deletion = True
|
||||
status_checks = False
|
||||
enforce_admins = False
|
||||
conversation_resolution = False
|
||||
try:
|
||||
branch = repo.get_branch(default_branch)
|
||||
if branch.protected:
|
||||
@@ -60,6 +61,9 @@ class Repository(GithubService):
|
||||
protection.required_status_checks is not None
|
||||
)
|
||||
enforce_admins = protection.enforce_admins
|
||||
conversation_resolution = (
|
||||
protection.required_conversation_resolution
|
||||
)
|
||||
branch_protection = True
|
||||
except Exception as error:
|
||||
# If the branch is not found, it is not protected
|
||||
@@ -77,6 +81,7 @@ class Repository(GithubService):
|
||||
branch_deletion = None
|
||||
status_checks = None
|
||||
enforce_admins = None
|
||||
conversation_resolution = None
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
@@ -95,6 +100,7 @@ class Repository(GithubService):
|
||||
default_branch_deletion=branch_deletion,
|
||||
status_checks=status_checks,
|
||||
enforce_admins=enforce_admins,
|
||||
conversation_resolution=conversation_resolution,
|
||||
default_branch_protection=branch_protection,
|
||||
)
|
||||
|
||||
@@ -122,3 +128,4 @@ class Repo(BaseModel):
|
||||
status_checks: Optional[bool]
|
||||
enforce_admins: Optional[bool]
|
||||
approval_count: Optional[int]
|
||||
conversation_resolution: Optional[bool]
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.github.services.repository.repository_service import Repo
|
||||
from tests.providers.github.github_fixtures import set_mocked_github_provider
|
||||
|
||||
|
||||
class Test_repository_default_branch_requires_conversation_resolution_test:
|
||||
def test_no_repositories(self):
|
||||
repository_client = mock.MagicMock
|
||||
repository_client.repositories = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.repository.repository_default_branch_requires_conversation_resolution.repository_default_branch_requires_conversation_resolution.repository_client",
|
||||
new=repository_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.repository.repository_default_branch_requires_conversation_resolution.repository_default_branch_requires_conversation_resolution import (
|
||||
repository_default_branch_requires_conversation_resolution,
|
||||
)
|
||||
|
||||
check = repository_default_branch_requires_conversation_resolution()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_conversation_resolution_disabled(self):
|
||||
repository_client = mock.MagicMock
|
||||
repo_name = "repo1"
|
||||
default_branch = "main"
|
||||
repository_client.repositories = {
|
||||
1: Repo(
|
||||
id=1,
|
||||
name=repo_name,
|
||||
full_name="account-name/repo1",
|
||||
default_branch=default_branch,
|
||||
conversation_resolution=False,
|
||||
private=False,
|
||||
securitymd=False,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.repository.repository_default_branch_requires_conversation_resolution.repository_default_branch_requires_conversation_resolution.repository_client",
|
||||
new=repository_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.repository.repository_default_branch_requires_conversation_resolution.repository_default_branch_requires_conversation_resolution import (
|
||||
repository_default_branch_requires_conversation_resolution,
|
||||
)
|
||||
|
||||
check = repository_default_branch_requires_conversation_resolution()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].resource_id == 1
|
||||
assert result[0].resource_name == "repo1"
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Repository {repo_name} does not require conversation resolution."
|
||||
)
|
||||
|
||||
def test_conversation_resolution_enabled(self):
|
||||
repository_client = mock.MagicMock
|
||||
repo_name = "repo1"
|
||||
default_branch = "main"
|
||||
repository_client.repositories = {
|
||||
1: Repo(
|
||||
id=1,
|
||||
name=repo_name,
|
||||
full_name="account-name/repo1",
|
||||
private=False,
|
||||
default_branch=default_branch,
|
||||
conversation_resolution=True,
|
||||
securitymd=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.repository.repository_default_branch_requires_conversation_resolution.repository_default_branch_requires_conversation_resolution.repository_client",
|
||||
new=repository_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.repository.repository_default_branch_requires_conversation_resolution.repository_default_branch_requires_conversation_resolution import (
|
||||
repository_default_branch_requires_conversation_resolution,
|
||||
)
|
||||
|
||||
check = repository_default_branch_requires_conversation_resolution()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].resource_id == 1
|
||||
assert result[0].resource_name == "repo1"
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Repository {repo_name} does require conversation resolution."
|
||||
)
|
||||
@@ -24,6 +24,7 @@ def mock_list_repositories(_):
|
||||
status_checks=True,
|
||||
approval_count=2,
|
||||
enforce_admins=True,
|
||||
conversation_resolution=True,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -55,4 +56,5 @@ class Test_Repository_Service:
|
||||
assert repository_service.repositories[1].default_branch_deletion
|
||||
assert repository_service.repositories[1].status_checks
|
||||
assert repository_service.repositories[1].enforce_admins
|
||||
assert repository_service.repositories[1].conversation_resolution
|
||||
assert repository_service.repositories[1].approval_count == 2
|
||||
|
||||
Reference in New Issue
Block a user