From b6e1551397d81dfdd252ba5d6d5160d8b321c251 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 21 Apr 2025 17:49:30 +0200 Subject: [PATCH] Revert "feat: remove provider_id and adapt logic" This reverts commit 3d9d118d2c094242726c503085da5ba0b6e4d61e. --- .../providers/m365/exceptions/exceptions.py | 8 +- .../m365/lib/powershell/m365_powershell.py | 2 +- prowler/providers/m365/m365_provider.py | 55 +++++-------- prowler/providers/m365/models.py | 2 +- .../lib/powershell/m365_powershell_test.py | 6 +- tests/providers/m365/m365_provider_test.py | 79 ++++++++++--------- 6 files changed, 71 insertions(+), 81 deletions(-) diff --git a/prowler/providers/m365/exceptions/exceptions.py b/prowler/providers/m365/exceptions/exceptions.py index 86299d13c5..2b5840dd10 100644 --- a/prowler/providers/m365/exceptions/exceptions.py +++ b/prowler/providers/m365/exceptions/exceptions.py @@ -62,9 +62,9 @@ class M365BaseException(ProwlerException): "message": "The provided tenant ID and client ID do not belong to the provided client secret", "remediation": "Check the tenant ID and client ID and ensure they belong to the provided client secret.", }, - (6014, "M365InvalidTenantDomainError"): { - "message": "The provided tenant domain is not valid", - "remediation": "Check the tenant domain and ensure it is a valid domain.", + (6014, "M365InvalidProviderIdError"): { + "message": "The provided provider_id does not match with the available subscriptions", + "remediation": "Check the provider_id and ensure it is a valid subscription for the given credentials.", }, (6015, "M365NoAuthenticationMethodError"): { "message": "No Microsoft 365 authentication method found", @@ -235,7 +235,7 @@ class M365TenantIdAndClientIdNotBelongingToClientSecretError(M365CredentialsErro ) -class M365InvalidTenantDomainError(M365BaseException): +class M365InvalidProviderIdError(M365BaseException): def __init__(self, file=None, original_exception=None, message=None): super().__init__( 6014, file=file, original_exception=original_exception, message=message diff --git a/prowler/providers/m365/lib/powershell/m365_powershell.py b/prowler/providers/m365/lib/powershell/m365_powershell.py index fa510c0ecb..49326cd38b 100644 --- a/prowler/providers/m365/lib/powershell/m365_powershell.py +++ b/prowler/providers/m365/lib/powershell/m365_powershell.py @@ -115,7 +115,7 @@ class M365PowerShell(PowerShellSession): # Validate user credentials belong to tenant user_domain = credentials.user.split("@")[1] - if not credentials.tenant_domain.endswith(user_domain): + if not credentials.provider_id.endswith(user_domain): raise M365UserNotBelongingToTenantError( file=os.path.basename(__file__), message="The provided M365 User does not belong to the specified tenant.", diff --git a/prowler/providers/m365/m365_provider.py b/prowler/providers/m365/m365_provider.py index 296b8bbf3e..f6ca473af4 100644 --- a/prowler/providers/m365/m365_provider.py +++ b/prowler/providers/m365/m365_provider.py @@ -38,7 +38,7 @@ from prowler.providers.m365.exceptions.exceptions import ( M365GetTokenIdentityError, M365HTTPResponseError, M365InteractiveBrowserCredentialError, - M365InvalidTenantDomainError, + M365InvalidProviderIdError, M365MissingEnvironmentCredentialsError, M365NoAuthenticationMethodError, M365NotTenantIdButClientIdAndClientSecretError, @@ -201,7 +201,7 @@ class M365Provider(Provider): self._credentials = self.setup_powershell( env_auth=env_auth, m365_credentials=m365_credentials, - tenant_domain=self._identity.tenant_domain, + provider_id=self.identity.tenant_domain, ) # Audit Config @@ -373,7 +373,7 @@ class M365Provider(Provider): @staticmethod def setup_powershell( - env_auth: bool = False, m365_credentials: dict = {}, tenant_domain: str = None + env_auth: bool = False, m365_credentials: dict = {}, provider_id: str = None ) -> M365Credentials: """Gets the M365 credentials. @@ -393,7 +393,7 @@ class M365Provider(Provider): client_id=m365_credentials.get("client_id", ""), client_secret=m365_credentials.get("client_secret", ""), tenant_id=m365_credentials.get("tenant_id", ""), - tenant_domain=m365_credentials.get("tenant_domain", ""), + provider_id=m365_credentials.get("provider_id", ""), ) elif env_auth: m365_user = getenv("M365_USER") @@ -416,7 +416,7 @@ class M365Provider(Provider): client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, - tenant_domain=tenant_domain, + provider_id=provider_id, ) if credentials: @@ -447,11 +447,8 @@ class M365Provider(Provider): f"M365 Region: {Fore.YELLOW}{self.region_config.name}{Style.RESET_ALL}", f"M365 Tenant Domain: {Fore.YELLOW}{self._identity.tenant_domain}{Style.RESET_ALL} M365 Tenant ID: {Fore.YELLOW}{self._identity.tenant_id}{Style.RESET_ALL}", f"M365 Identity Type: {Fore.YELLOW}{self._identity.identity_type}{Style.RESET_ALL} M365 Identity ID: {Fore.YELLOW}{self._identity.identity_id}{Style.RESET_ALL}", + f"M365 User: {Fore.YELLOW}{self.credentials.user}{Style.RESET_ALL}", ] - if self.credentials and self.credentials.user: - report_lines.append( - f"M365 User: {Fore.YELLOW}{self.credentials.user}{Style.RESET_ALL}" - ) report_title = ( f"{Style.BRIGHT}Using the M365 credentials below:{Style.RESET_ALL}" ) @@ -484,6 +481,7 @@ class M365Provider(Provider): - client_secret: The M365 client secret - user: The M365 user email - encrypted_password: The M365 encrypted password + - provider_id: The M365 provider ID (in this case the Tenant ID). region_config (M365RegionConfig): The region configuration object. Returns: @@ -610,6 +608,7 @@ class M365Provider(Provider): client_secret=None, user=None, encrypted_password=None, + provider_id=None, ) -> Connection: """Test connection to M365 subscription. @@ -628,7 +627,7 @@ class M365Provider(Provider): client_secret (str): The M365 client secret. user (str): The M365 user email. encrypted_password (str): The M365 encrypted_password. - tenant_domain (str): The M365 tenant domain. + provider_id (str): The M365 provider ID (in this case the Tenant ID). Returns: @@ -696,30 +695,18 @@ class M365Provider(Provider): region_config, ) - # Set up the M365 identity - M365Provider.setup_identity( - az_cli_auth, - sp_env_auth, - env_auth, - browser_auth, - client_id, - ) - - # Set up the M365 GraphServiceClient GraphServiceClient(credentials=credentials) logger.info("M365 provider: Connection to MSGraph successful") # Set up PowerShell credentials - if user and encrypted_password: - M365Provider.setup_powershell( - env_auth, - m365_credentials, - M365Provider.identity.tenant_domain, - ) - logger.info("M365 provider: Connection to PowerShell successful") - else: - logger.info("M365 provider: No PowerShell credentials provided") + M365Provider.setup_powershell( + env_auth, + m365_credentials, + provider_id, + ) + + logger.info("M365 provider: Connection to PowerShell successful") return Connection(is_connected=True) @@ -794,14 +781,14 @@ class M365Provider(Provider): if raise_on_exception: raise client_secret_error return Connection(error=client_secret_error) - # Exceptions from tenant_domain validation - except M365InvalidTenantDomainError as invalid_tenant_domain_error: + # Exceptions from provider_id validation + except M365InvalidProviderIdError as invalid_credentials_error: logger.error( - f"{invalid_tenant_domain_error.__class__.__name__}[{invalid_tenant_domain_error.__traceback__.tb_lineno}]: {invalid_tenant_domain_error}" + f"{invalid_credentials_error.__class__.__name__}[{invalid_credentials_error.__traceback__.tb_lineno}]: {invalid_credentials_error}" ) if raise_on_exception: - raise invalid_tenant_domain_error - return Connection(error=invalid_tenant_domain_error) + raise invalid_credentials_error + return Connection(error=invalid_credentials_error) # Exceptions from SubscriptionClient except HttpResponseError as http_response_error: logger.error( diff --git a/prowler/providers/m365/models.py b/prowler/providers/m365/models.py index 4a1eeaf237..a2e3b68151 100644 --- a/prowler/providers/m365/models.py +++ b/prowler/providers/m365/models.py @@ -25,7 +25,7 @@ class M365Credentials(BaseModel): client_id: str = "" client_secret: str = "" tenant_id: str = "" - tenant_domain: str = "" + provider_id: str = "" class M365OutputOptions(ProviderOutputOptions): diff --git a/tests/providers/m365/lib/powershell/m365_powershell_test.py b/tests/providers/m365/lib/powershell/m365_powershell_test.py index b2fa2d3439..56cc28ef33 100644 --- a/tests/providers/m365/lib/powershell/m365_powershell_test.py +++ b/tests/providers/m365/lib/powershell/m365_powershell_test.py @@ -94,7 +94,7 @@ class Testm365PowerShell: client_id="test_client_id", client_secret="test_client_secret", tenant_id="test_tenant_id", - tenant_domain="contoso.onmicrosoft.com", + provider_id="contoso.onmicrosoft.com", ) session = M365PowerShell(credentials) @@ -144,7 +144,7 @@ class Testm365PowerShell: client_id="test_client_id", client_secret="test_client_secret", tenant_id="test_tenant_id", - tenant_domain="contoso.onmicrosoft.com", + provider_id="contoso.onmicrosoft.com", ) session = M365PowerShell(credentials) @@ -187,7 +187,7 @@ class Testm365PowerShell: client_id="test_client_id", client_secret="test_client_secret", tenant_id="test_tenant_id", - tenant_domain="contoso.onmicrosoft.com", + provider_id="contoso.onmicrosoft.com", ) session = M365PowerShell(credentials) diff --git a/tests/providers/m365/m365_provider_test.py b/tests/providers/m365/m365_provider_test.py index d610f30b6a..80222441ed 100644 --- a/tests/providers/m365/m365_provider_test.py +++ b/tests/providers/m365/m365_provider_test.py @@ -282,9 +282,6 @@ class TestM365Provider: patch( "prowler.providers.m365.m365_provider.M365Provider.setup_session" ) as mock_setup_session, - patch( - "prowler.providers.m365.m365_provider.M365Provider.setup_identity" - ) as mock_setup_identity, patch( "prowler.providers.m365.m365_provider.GraphServiceClient" ) as mock_graph_client, @@ -300,16 +297,6 @@ class TestM365Provider: mock_session = MagicMock() mock_setup_session.return_value = mock_session - # Mock setup_identity to return a mocked identity object - mock_identity = M365IdentityInfo( - identity_id=IDENTITY_ID, - identity_type=IDENTITY_TYPE, - tenant_id=TENANT_ID, - tenant_domain=DOMAIN, - location=LOCATION, - ) - mock_setup_identity.return_value = mock_identity - # Mock GraphServiceClient to avoid real API calls mock_client = MagicMock() mock_graph_client.return_value = mock_client @@ -333,12 +320,6 @@ class TestM365Provider: patch( "prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials" ) as mock_validate_static_credentials, - patch( - "prowler.providers.m365.m365_provider.M365Provider.setup_identity" - ) as mock_setup_identity, - patch( - "prowler.providers.m365.m365_provider.M365Provider.setup_region_config" - ) as mock_setup_region_config, ): # Mock setup_session to return a mocked session object mock_session = MagicMock() @@ -347,25 +328,6 @@ class TestM365Provider: # Mock ValidateStaticCredentials to avoid real API calls mock_validate_static_credentials.return_value = None - # Mock setup_identity to return a mocked identity object - mock_identity = M365IdentityInfo( - identity_id=IDENTITY_ID, - identity_type=IDENTITY_TYPE, - tenant_id=TENANT_ID, - tenant_domain=DOMAIN, - location=LOCATION, - ) - mock_setup_identity.return_value = mock_identity - - # Mock setup_region_config to return a valid region config - mock_region_config = M365RegionConfig( - name="M365Global", - authority=None, - base_url="https://graph.microsoft.com", - credential_scopes=["https://graph.microsoft.com/.default"], - ) - mock_setup_region_config.return_value = mock_region_config - test_connection = M365Provider.test_connection( tenant_id=str(uuid4()), region="M365Global", @@ -477,6 +439,47 @@ class TestM365Provider: in exception.value.args[0] ) + def test_setup_powershell_valid_credentials(self): + credentials_dict = { + "user": "test@example.com", + "encrypted_password": "test_password", + "client_id": "test_client_id", + "tenant_id": "test_tenant_id", + "client_secret": "test_client_secret", + } + + with patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials", + return_value=True, + ): + result = M365Provider.setup_powershell( + env_auth=False, m365_credentials=credentials_dict + ) + + assert result.user == credentials_dict["user"] + assert result.passwd == credentials_dict["encrypted_password"] + + def test_setup_powershell_invalid_env_credentials(self): + credentials = None + + with patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell" + ) as mock_powershell: + mock_session = MagicMock() + mock_session.test_credentials.return_value = False + mock_powershell.return_value = mock_session + + with pytest.raises(M365MissingEnvironmentCredentialsError) as exc_info: + M365Provider.setup_powershell( + env_auth=True, m365_credentials=credentials + ) + + assert ( + "Missing M365_USER or M365_ENCRYPTED_PASSWORD environment variables required for credentials authentication" + in str(exc_info.value) + ) + mock_session.test_credentials.assert_not_called() + def test_test_connection_user_not_belonging_to_tenant( self, ):