From 5ee960e13d87c0007f757539910563d4bba04e4c Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Wed, 20 Nov 2024 18:09:10 +0100 Subject: [PATCH] feat: added testing Added testing classes for provider, new service and check and corrected minor issues. --- .../providers/github/exceptions/__init__.py | 0 .../providers/github/exceptions/exceptions.py | 117 ++++++++++++++++++ prowler/providers/github/github_provider.py | 8 +- .../providers/github/lib/mutelist/mutelist.py | 2 +- .../services/repository/repository_service.py | 7 +- tests/providers/gcp/gcp_provider_test.py | 1 - tests/providers/github/github_fixtures.py | 28 +++-- .../providers/github/github_provider_test.py | 52 ++++++++ ...ository_public_has_securitymd_file_test.py | 95 ++++++++++++++ .../repository/repository_service_test.py | 41 ++++++ 10 files changed, 334 insertions(+), 17 deletions(-) create mode 100644 prowler/providers/github/exceptions/__init__.py create mode 100644 prowler/providers/github/exceptions/exceptions.py diff --git a/prowler/providers/github/exceptions/__init__.py b/prowler/providers/github/exceptions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/github/exceptions/exceptions.py b/prowler/providers/github/exceptions/exceptions.py new file mode 100644 index 0000000000..b5e08c780e --- /dev/null +++ b/prowler/providers/github/exceptions/exceptions.py @@ -0,0 +1,117 @@ +from prowler.exceptions.exceptions import ProwlerException + + +# Exceptions codes from 5000 to 5999 are reserved for GitHub exceptions +class GitHubBaseException(ProwlerException): + """Base class for GitHub Errors.""" + + GITHUB_ERROR_CODES = { + (2000, "GitHubEnvironmentVariableError"): { + "message": "GitHub environment variable error", + "remediation": "Check the GitHub environment variables and ensure they are properly set.", + }, + (2001, "GitHubInvalidTokenError"): { + "message": "GitHub token provided is not valid", + "remediation": "Check the GitHub token and ensure it is valid.", + }, + (2002, "GitHubSetUpIdentityError"): { + "message": "GitHub identity setup error related with credentials", + "remediation": "Check credentials and ensure they are properly set up for GitHub and the identity provider.", + }, + (2003, "GitHubNoAuthenticationMethodError"): { + "message": "No GitHub authentication method found", + "remediation": "Check that any authentication method is properly set up for GitHub.", + }, + (2006, "GitHubArgumentTypeValidationError"): { + "message": "GitHub argument type validation error", + "remediation": "Check the provided argument types specific to GitHub and ensure they meet the required format.", + }, + (2010, "GitHubHTTPResponseError"): { + "message": "Error in HTTP response from GitHub", + "remediation": "", + }, + (2014, "GitHubClientAuthenticationError"): { + "message": "Error in client authentication", + "remediation": "Check the client authentication and ensure it is properly set up.", + }, + (2015, "GitHubSetUpSessionError"): { + "message": "Error setting up session", + "remediation": "Check the session setup and ensure it is properly set up.", + }, + } + + def __init__(self, code, file=None, original_exception=None, message=None): + provider = "GitHub" + error_info = self.GITHUB_ERROR_CODES.get((code, self.__class__.__name__)) + if message: + error_info["message"] = message + super().__init__( + code=code, + source=provider, + file=file, + original_exception=original_exception, + error_info=error_info, + ) + + +class GitHubCredentialsError(GitHubBaseException): + """Base class for GitHub credentials errors.""" + + def __init__(self, code, file=None, original_exception=None, message=None): + super().__init__(code, file, original_exception, message) + + +class GitHubEnvironmentVariableError(GitHubCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2000, file=file, original_exception=original_exception, message=message + ) + + +class GitHubInvalidTokenError(GitHubCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2001, file=file, original_exception=original_exception, message=message + ) + + +class GitHubSetUpIdentityError(GitHubCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2002, file=file, original_exception=original_exception, message=message + ) + + +class GitHubNoAuthenticationMethodError(GitHubCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2003, file=file, original_exception=original_exception, message=message + ) + + +class GitHubArgumentTypeValidationError(GitHubBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2006, file=file, original_exception=original_exception, message=message + ) + + +class GitHubHTTPResponseError(GitHubBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2010, file=file, original_exception=original_exception, message=message + ) + + +class GitHubClientAuthenticationError(GitHubCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2014, file=file, original_exception=original_exception, message=message + ) + + +class GitHubSetUpSessionError(GitHubCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 2015, file=file, original_exception=original_exception, message=message + ) diff --git a/prowler/providers/github/github_provider.py b/prowler/providers/github/github_provider.py index 953d78f43c..9d614f39bc 100644 --- a/prowler/providers/github/github_provider.py +++ b/prowler/providers/github/github_provider.py @@ -11,7 +11,7 @@ from prowler.lib.logger import logger from prowler.lib.mutelist.mutelist import Mutelist from prowler.providers.common.models import Audit_Metadata from prowler.providers.common.provider import Provider -from prowler.providers.github.lib.mutelist.mutelist import GitHubMutelist +from prowler.providers.github.lib.mutelist.mutelist import GithubMutelist from prowler.providers.github.models import GithubIdentityInfo, GithubSession @@ -70,13 +70,13 @@ class GithubProvider(Provider): # Mutelist if mutelist_content: - self._mutelist = GitHubMutelist( + self._mutelist = GithubMutelist( mutelist_content=mutelist_content, ) else: if not mutelist_path: mutelist_path = get_default_mute_file_path(self.type) - self._mutelist = GitHubMutelist( + self._mutelist = GithubMutelist( mutelist_path=mutelist_path, ) Provider.set_global_provider(self) @@ -110,7 +110,7 @@ class GithubProvider(Provider): return self._fixer_config @property - def mutelist(self) -> GitHubMutelist: + def mutelist(self) -> GithubMutelist: """ mutelist method returns the provider's mutelist. """ diff --git a/prowler/providers/github/lib/mutelist/mutelist.py b/prowler/providers/github/lib/mutelist/mutelist.py index ba1ed7a56e..9c38e9d013 100644 --- a/prowler/providers/github/lib/mutelist/mutelist.py +++ b/prowler/providers/github/lib/mutelist/mutelist.py @@ -3,7 +3,7 @@ from prowler.lib.mutelist.mutelist import Mutelist from prowler.lib.outputs.utils import unroll_dict, unroll_tags -class GitHubMutelist(Mutelist): +class GithubMutelist(Mutelist): def is_finding_muted( self, finding: Check_Report_Github, diff --git a/prowler/providers/github/services/repository/repository_service.py b/prowler/providers/github/services/repository/repository_service.py index 8ddebf3015..b074414d4d 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -9,18 +9,18 @@ from prowler.providers.github.lib.service.service import GithubService class Repository(GithubService): def __init__(self, provider): super().__init__(__class__.__name__, provider) - self.repositories = {} - self._list_repositories() + self.repositories = self._list_repositories() def _list_repositories(self): logger.info("Repository - Listing Repositories...") + repos = {} try: for repo in self.client.get_user().get_repos(): try: securitymd_exists = repo.get_contents("SECURITY.md") is not None except Exception: securitymd_exists = False - self.repositories[repo.id] = Repo( + repos[repo.id] = Repo( id=repo.id, name=repo.name, full_name=repo.full_name, @@ -31,6 +31,7 @@ class Repository(GithubService): logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + return repos class Repo(BaseModel): diff --git a/tests/providers/gcp/gcp_provider_test.py b/tests/providers/gcp/gcp_provider_test.py index 9c74821935..f8a040add7 100644 --- a/tests/providers/gcp/gcp_provider_test.py +++ b/tests/providers/gcp/gcp_provider_test.py @@ -739,7 +739,6 @@ class TestGCPProvider: ), patch( "prowler.providers.gcp.gcp_provider.GcpProvider.validate_project_id" ) as mock_validate_project_id: - mock_validate_project_id.side_effect = GCPInvalidProviderIdError( "Invalid project ID" ) diff --git a/tests/providers/github/github_fixtures.py b/tests/providers/github/github_fixtures.py index f5b5db94b5..e330d7dd8c 100644 --- a/tests/providers/github/github_fixtures.py +++ b/tests/providers/github/github_fixtures.py @@ -1,4 +1,5 @@ from mock import MagicMock + from prowler.providers.github.github_provider import GithubProvider from prowler.providers.github.models import GithubIdentityInfo, GithubSession @@ -7,16 +8,27 @@ ACCOUNT_NAME = "account-name" ACCOUNT_ID = "account-id" ACCOUNT_URL = "/user" +# GitHub Credentials +TOKEN = "github-token" + + # Mocked GitHub Provider def set_mocked_github_provider( - credentials: -) -> GithubProvider: - provider = MagicMock() - provider.type = "github" - provider.session = GithubSession() - provider.identity = GithubIdentityInfo( + auth_method: str = "personal_access_token", + credentials: GithubSession = GithubSession(token=TOKEN), + identity: GithubIdentityInfo = GithubIdentityInfo( account_name=ACCOUNT_NAME, account_id=ACCOUNT_ID, account_url=ACCOUNT_URL, - ) - return provider \ No newline at end of file + ), + audit_config: dict = None, +) -> GithubProvider: + + provider = MagicMock() + provider.type = "github" + provider.auth_method = auth_method + provider.session = credentials + provider.identity = identity + provider.audit_config = audit_config + + return provider diff --git a/tests/providers/github/github_provider_test.py b/tests/providers/github/github_provider_test.py index e69de29bb2..dc8e9ecfe7 100644 --- a/tests/providers/github/github_provider_test.py +++ b/tests/providers/github/github_provider_test.py @@ -0,0 +1,52 @@ +from unittest.mock import patch + +from prowler.config.config import ( + default_fixer_config_file_path, + load_and_validate_config_file, +) +from prowler.providers.github.github_provider import GithubProvider +from prowler.providers.github.models import GithubIdentityInfo, GithubSession +from tests.providers.github.github_fixtures import ( + ACCOUNT_ID, + ACCOUNT_NAME, + ACCOUNT_URL, + TOKEN, +) + + +class TestGitHubProvider: + def test_github_provider(self): + # We need to set exactly one auth method + personal_access_token = True + oauth_app = False + github_app = False + fixer_config = load_and_validate_config_file( + "github", default_fixer_config_file_path + ) + + with patch( + "prowler.providers.github.github_provider.GithubProvider.setup_session", + return_value=GithubSession(token=TOKEN), + ), patch( + "prowler.providers.github.github_provider.GithubProvider.setup_identity", + return_value=GithubIdentityInfo( + account_id=ACCOUNT_ID, + account_name=ACCOUNT_NAME, + account_url=ACCOUNT_URL, + ), + ): + provider = GithubProvider( + personal_access_token, + github_app, + oauth_app, + ) + + assert provider._type == "github" + assert provider.session == GithubSession(token=TOKEN) + assert provider.identity == GithubIdentityInfo( + account_name=ACCOUNT_NAME, + account_id=ACCOUNT_ID, + account_url=ACCOUNT_URL, + ) + assert provider._audit_config == {} + assert provider._fixer_config == fixer_config diff --git a/tests/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file_test.py b/tests/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file_test.py index e69de29bb2..87c0b03caa 100644 --- a/tests/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file_test.py +++ b/tests/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file_test.py @@ -0,0 +1,95 @@ +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_public_has_securitymd_file_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_public_has_securitymd_file.repository_public_has_securitymd_file.repository_client", + new=repository_client, + ): + from prowler.providers.github.services.repository.repository_public_has_securitymd_file.repository_public_has_securitymd_file import ( + repository_public_has_securitymd_file, + ) + + check = repository_public_has_securitymd_file() + result = check.execute() + assert len(result) == 0 + + def test_one_repository_no_securitymd(self): + repository_client = mock.MagicMock + repo_name = "repo1" + repository_client.repositories = { + 1: Repo( + id=1, + name=repo_name, + full_name="account-name/repo1", + 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_public_has_securitymd_file.repository_public_has_securitymd_file.repository_client", + new=repository_client, + ): + from prowler.providers.github.services.repository.repository_public_has_securitymd_file.repository_public_has_securitymd_file import ( + repository_public_has_securitymd_file, + ) + + check = repository_public_has_securitymd_file() + 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 have a SECURITY.md file." + ) + + def test_one_repository_securitymd(self): + repository_client = mock.MagicMock + repo_name = "repo1" + repository_client.repositories = { + 1: Repo( + id=1, + name=repo_name, + full_name="account-name/repo1", + private=False, + 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_public_has_securitymd_file.repository_public_has_securitymd_file.repository_client", + new=repository_client, + ): + from prowler.providers.github.services.repository.repository_public_has_securitymd_file.repository_public_has_securitymd_file import ( + repository_public_has_securitymd_file, + ) + + check = repository_public_has_securitymd_file() + 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 have a SECURITY.md file." + ) diff --git a/tests/providers/github/services/repository/repository_service_test.py b/tests/providers/github/services/repository/repository_service_test.py index e69de29bb2..3592cbcb8f 100644 --- a/tests/providers/github/services/repository/repository_service_test.py +++ b/tests/providers/github/services/repository/repository_service_test.py @@ -0,0 +1,41 @@ +from unittest.mock import patch + +from prowler.providers.github.services.repository.repository_service import ( + Repo, + Repository, +) +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +def mock_list_repositories(_): + return { + 1: Repo( + id=1, + name="repo1", + full_name="account-name/repo1", + private=False, + securitymd=False, + ), + } + + +@patch( + "prowler.providers.github.services.repository.repository_service.Repository._list_repositories", + new=mock_list_repositories, +) +class Test_Repository_Service: + def test_get_client(self): + repository_service = Repository(set_mocked_github_provider()) + assert repository_service.client.__class__.__name__ == "Github" + + def test_get_service(self): + repository_service = Repository(set_mocked_github_provider()) + assert repository_service.__class__.__name__ == "Repository" + + def test_list_repositories(self): + repository_service = Repository(set_mocked_github_provider()) + assert len(repository_service.repositories) == 1 + assert repository_service.repositories[1].name == "repo1" + assert repository_service.repositories[1].full_name == "account-name/repo1" + assert repository_service.repositories[1].private is False + assert repository_service.repositories[1].securitymd is False