feat(github): add organization_actions_pull_request_approval_disabled check (#12394)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
Chethas Dileep
2026-08-27 08:25:32 +02:00
committed by GitHub
co-authored by Daniel Barranquero
parent bd1956446d
commit 2721d42594
8 changed files with 242 additions and 20 deletions
@@ -0,0 +1,79 @@
from unittest import mock
from prowler.providers.github.services.organization.organization_service import Org
from tests.providers.github.github_fixtures import set_mocked_github_provider
CHECK_CLIENT_PATH = "prowler.providers.github.services.organization.organization_actions_pull_request_approval_disabled.organization_actions_pull_request_approval_disabled.organization_client"
class Test_organization_actions_pull_request_approval_disabled:
"""Unit tests for the organization_actions_pull_request_approval_disabled check."""
def _run_check(self, organizations):
"""Execute the check against the provided organizations."""
organization_client = mock.MagicMock
organization_client.organizations = organizations
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(CHECK_CLIENT_PATH, new=organization_client),
):
from prowler.providers.github.services.organization.organization_actions_pull_request_approval_disabled.organization_actions_pull_request_approval_disabled import (
organization_actions_pull_request_approval_disabled,
)
check = organization_actions_pull_request_approval_disabled()
return check.execute()
def test_no_organizations(self):
"""Test that no findings are reported when there are no organizations."""
assert len(self._run_check({})) == 0
def test_pull_request_approval_unknown(self):
"""Test that no finding is reported when the setting could not be read."""
organizations = {
1: Org(
id=1, name="test-organization", can_approve_pull_request_reviews=None
)
}
assert len(self._run_check(organizations)) == 0
def test_pull_request_approval_disabled(self):
"""Test that an organization not allowing workflow approvals passes the check."""
organizations = {
1: Org(
id=1, name="test-organization", can_approve_pull_request_reviews=False
)
}
result = self._run_check(organizations)
assert len(result) == 1
assert result[0].resource_id == 1
assert result[0].resource_name == "test-organization"
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Organization test-organization does not allow GitHub Actions to approve pull requests."
)
def test_pull_request_approval_enabled(self):
"""Test that an organization allowing workflow approvals fails the check."""
organizations = {
1: Org(
id=1, name="test-organization", can_approve_pull_request_reviews=True
)
}
result = self._run_check(organizations)
assert len(result) == 1
assert result[0].resource_id == 1
assert result[0].resource_name == "test-organization"
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Organization test-organization allows GitHub Actions to approve pull requests."
)
@@ -514,7 +514,8 @@ class Test_Organization_Default_Workflow_Permissions:
)
assert (
organization_service._get_default_workflow_permissions(mock_org) == "read"
organization_service._get_actions_workflow_permissions(mock_org)[0]
== "read"
), "The organization default GITHUB_TOKEN permissions should be read from the Actions permissions endpoint"
mock_org._requester.requestJsonAndCheck.assert_called_once_with(
"GET",
@@ -525,6 +526,27 @@ class Test_Organization_Default_Workflow_Permissions:
},
)
def test_can_approve_pull_request_reviews(self):
"""Test that the pull request approval setting is read from the same API response"""
organization_service = self._get_service()
mock_org = MagicMock()
mock_org.login = "test-org"
mock_org._requester.requestJsonAndCheck.return_value = (
{},
{
"default_workflow_permissions": "write",
"can_approve_pull_request_reviews": True,
},
)
assert organization_service._get_actions_workflow_permissions(mock_org) == (
"write",
True,
), "Both Actions workflow permissions settings should come from a single request"
assert (
mock_org._requester.requestJsonAndCheck.call_count == 1
), "Both settings should be read without issuing a second request"
def test_default_workflow_permissions_missing_field(self):
"""Test that a response without the permissions field yields no value"""
organization_service = self._get_service()
@@ -532,10 +554,31 @@ class Test_Organization_Default_Workflow_Permissions:
mock_org.login = "test-org"
mock_org._requester.requestJsonAndCheck.return_value = ({}, {})
assert (
organization_service._get_default_workflow_permissions(mock_org) is None
assert organization_service._get_actions_workflow_permissions(mock_org) == (
None,
None,
), "An unexpected response payload should not be reported as a permissions value"
def test_default_workflow_permissions_unsupported_value(self):
"""Test that a value outside read/write is treated as unknown rather than a failure
The endpoint documents only "read" and "write". Accepting any string meant a
value such as "READ" or "none" reached the check and produced a FAIL finding,
reporting a write-capable default the organization may not actually have.
"""
organization_service = self._get_service()
mock_org = MagicMock()
mock_org.login = "test-org"
mock_org._requester.requestJsonAndCheck.return_value = (
{},
{"default_workflow_permissions": "none"},
)
assert organization_service._get_actions_workflow_permissions(mock_org) == (
None,
None,
), "An unsupported permissions value should be left unknown instead of reported as write"
def test_default_workflow_permissions_not_available(self):
"""Test that accounts without Actions permissions settings yield no value"""
organization_service = self._get_service()
@@ -545,8 +588,9 @@ class Test_Organization_Default_Workflow_Permissions:
404, "Not Found", None
)
assert (
organization_service._get_default_workflow_permissions(mock_org) is None
assert organization_service._get_actions_workflow_permissions(mock_org) == (
None,
None,
), "Accounts without Actions permissions settings should not produce a value"
def test_default_workflow_permissions_access_denied(self):
@@ -561,8 +605,9 @@ class Test_Organization_Default_Workflow_Permissions:
with patch(
"prowler.providers.github.services.organization.organization_service.logger"
) as mock_logger:
assert (
organization_service._get_default_workflow_permissions(mock_org) is None
assert organization_service._get_actions_workflow_permissions(mock_org) == (
None,
None,
), "Insufficient permissions should not produce a value"
mock_logger.warning.assert_called()
@@ -576,4 +621,4 @@ class Test_Organization_Default_Workflow_Permissions:
)
with raises(RateLimitExceededException):
organization_service._get_default_workflow_permissions(mock_org)
organization_service._get_actions_workflow_permissions(mock_org)