mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
chore(m365_powershell): remove unnecessary test_credentials (#9204)
This commit is contained in:
committed by
GitHub
parent
822d201159
commit
73a277f27b
@@ -42,6 +42,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
- Update AWS CodeArtifact service metadata to new format [(#8850)](https://github.com/prowler-cloud/prowler/pull/8850)
|
||||
- Rename OCI provider to oraclecloud with oci alias [(#9126)](https://github.com/prowler-cloud/prowler/pull/9126)
|
||||
- Remove unnecessary tests for M365_PowerShell module [(#9204)](https://github.com/prowler-cloud/prowler/pull/9204)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@ import os
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.powershell.powershell import PowerShellSession
|
||||
from prowler.providers.m365.exceptions.exceptions import (
|
||||
M365CertificateCreationError,
|
||||
M365GraphConnectionError,
|
||||
)
|
||||
from prowler.providers.m365.exceptions.exceptions import M365CertificateCreationError
|
||||
from prowler.providers.m365.lib.jwt.jwt_decoder import decode_msal_token
|
||||
from prowler.providers.m365.models import M365Credentials, M365IdentityInfo
|
||||
|
||||
@@ -138,79 +135,6 @@ class M365PowerShell(PowerShellSession):
|
||||
result = self.execute(command, timeout=connect_timeout)
|
||||
return result or "'execute_connect' command timeout reached"
|
||||
|
||||
def test_credentials(self, credentials: M365Credentials) -> bool:
|
||||
"""
|
||||
Test Microsoft 365 credentials by attempting to authenticate against Entra ID.
|
||||
|
||||
Supports testing two authentication methods:
|
||||
1. Application authentication (client_id/client_secret)
|
||||
2. Certificate authentication (certificate_content in base64/client_id)
|
||||
|
||||
Args:
|
||||
credentials (M365Credentials): The credentials object containing
|
||||
authentication information to test.
|
||||
|
||||
Returns:
|
||||
bool: True if credentials are valid and authentication succeeds, False otherwise.
|
||||
"""
|
||||
# Test Certificate Auth
|
||||
if credentials.certificate_content and credentials.client_id:
|
||||
try:
|
||||
logger.info("Testing Microsoft Graph Certificate connection...")
|
||||
self.test_graph_certificate_connection()
|
||||
logger.info("Microsoft Graph Certificate connection successful")
|
||||
teams_connection_successful = self.test_teams_certificate_connection()
|
||||
if not teams_connection_successful:
|
||||
self.test_exchange_certificate_connection()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Microsoft Graph Cer connection failed: {e}")
|
||||
raise M365GraphConnectionError(
|
||||
file=os.path.basename(__file__),
|
||||
original_exception=e,
|
||||
message="Check your Microsoft Application Certificate and ensure the app has proper permissions",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
logger.info("Testing Microsoft Graph Client Secret connection...")
|
||||
self.test_graph_connection()
|
||||
logger.info("Microsoft Graph Client Secret connection successful")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Microsoft Graph Client Secret connection failed: {e}")
|
||||
raise M365GraphConnectionError(
|
||||
file=os.path.basename(__file__),
|
||||
original_exception=e,
|
||||
message="Check your Microsoft Application Client Secret and ensure the app has proper permissions",
|
||||
)
|
||||
|
||||
def test_graph_connection(self) -> bool:
|
||||
"""Test Microsoft Graph API connection and raise exception if it fails."""
|
||||
try:
|
||||
if self.execute("Write-Output $graphToken") == "":
|
||||
raise M365GraphConnectionError(
|
||||
file=os.path.basename(__file__),
|
||||
message="Microsoft Graph token is empty or invalid.",
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Microsoft Graph connection failed: {e}")
|
||||
raise M365GraphConnectionError(
|
||||
file=os.path.basename(__file__),
|
||||
original_exception=e,
|
||||
message=f"Failed to connect to Microsoft Graph API: {str(e)}",
|
||||
)
|
||||
|
||||
def test_graph_certificate_connection(self) -> bool:
|
||||
"""Test Microsoft Graph API connection using certificate and raise exception if it fails."""
|
||||
result = self.execute_connect(
|
||||
"Connect-Graph -Certificate $certificate -AppId $clientID -TenantId $tenantID"
|
||||
)
|
||||
if "Welcome to Microsoft Graph!" not in result:
|
||||
logger.error(f"Microsoft Graph Certificate connection failed: {result}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def test_teams_connection(self) -> bool:
|
||||
"""Test Microsoft Teams API connection and raise exception if it fails."""
|
||||
try:
|
||||
@@ -926,7 +850,10 @@ def initialize_m365_powershell_modules():
|
||||
bool: True if all modules were successfully initialized, False otherwise
|
||||
"""
|
||||
|
||||
REQUIRED_MODULES = ["ExchangeOnlineManagement", "MicrosoftTeams", "MSAL.PS"]
|
||||
REQUIRED_MODULES = [
|
||||
"ExchangeOnlineManagement",
|
||||
"MicrosoftTeams",
|
||||
]
|
||||
|
||||
pwsh = PowerShellSession()
|
||||
try:
|
||||
@@ -938,7 +865,7 @@ def initialize_m365_powershell_modules():
|
||||
# Install module if not installed
|
||||
if not result:
|
||||
install_result = pwsh.execute(
|
||||
f'Install-Module "{module}" -Force -AllowClobber -Scope CurrentUser',
|
||||
f"Install-Module {module} -Force -AllowClobber -Scope CurrentUser",
|
||||
timeout=60,
|
||||
)
|
||||
if install_result:
|
||||
|
||||
@@ -444,12 +444,7 @@ class M365Provider(Provider):
|
||||
try:
|
||||
if init_modules:
|
||||
initialize_m365_powershell_modules()
|
||||
if test_session.test_credentials(credentials):
|
||||
return credentials
|
||||
raise M365ConfigCredentialsError(
|
||||
file=os.path.basename(__file__),
|
||||
message="The provided credentials are not valid.",
|
||||
)
|
||||
return credentials
|
||||
finally:
|
||||
test_session.close()
|
||||
|
||||
|
||||
@@ -4,10 +4,7 @@ from unittest.mock import MagicMock, call, patch
|
||||
import pytest
|
||||
|
||||
from prowler.lib.powershell.powershell import PowerShellSession
|
||||
from prowler.providers.m365.exceptions.exceptions import (
|
||||
M365CertificateCreationError,
|
||||
M365GraphConnectionError,
|
||||
)
|
||||
from prowler.providers.m365.exceptions.exceptions import M365CertificateCreationError
|
||||
from prowler.providers.m365.lib.powershell.m365_powershell import M365PowerShell
|
||||
from prowler.providers.m365.models import M365Credentials, M365IdentityInfo
|
||||
|
||||
@@ -115,31 +112,6 @@ class Testm365PowerShell:
|
||||
)
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_credentials_application_auth(self, mock_popen):
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
credentials = M365Credentials(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
tenant_id="test_tenant_id",
|
||||
)
|
||||
identity = M365IdentityInfo(
|
||||
identity_id="test_id",
|
||||
identity_type="Service Principal",
|
||||
tenant_id="test_tenant",
|
||||
tenant_domain="contoso.onmicrosoft.com",
|
||||
tenant_domains=["contoso.onmicrosoft.com"],
|
||||
location="test_location",
|
||||
)
|
||||
session = M365PowerShell(credentials, identity)
|
||||
session.execute = MagicMock(return_value="sometoken")
|
||||
|
||||
result = session.test_credentials(credentials)
|
||||
assert result is True
|
||||
session.execute.assert_any_call("Write-Output $graphToken")
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_remove_ansi(self, mock_popen):
|
||||
credentials = M365Credentials(
|
||||
@@ -339,13 +311,14 @@ class Testm365PowerShell:
|
||||
# Verify successful initialization
|
||||
assert result is True
|
||||
# Verify that execute was called for each module
|
||||
assert mock_execute_obj.call_count == 9 # 3 modules * 3 commands each
|
||||
assert (
|
||||
mock_execute_obj.call_count == 2 * 3
|
||||
) # number of modules * 3 commands each
|
||||
# Verify success messages were logged
|
||||
mock_info.assert_any_call(
|
||||
"Successfully installed module ExchangeOnlineManagement"
|
||||
)
|
||||
mock_info.assert_any_call("Successfully installed module MicrosoftTeams")
|
||||
mock_info.assert_any_call("Successfully installed module MSAL.PS")
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_initialize_m365_powershell_modules_failure(self, mock_popen):
|
||||
@@ -408,12 +381,11 @@ class Testm365PowerShell:
|
||||
main()
|
||||
|
||||
# Verify all info messages were logged in the correct order
|
||||
assert mock_info.call_count == 4
|
||||
assert mock_info.call_count == 3
|
||||
mock_info.assert_has_calls(
|
||||
[
|
||||
call("Successfully installed module ExchangeOnlineManagement"),
|
||||
call("Successfully installed module MicrosoftTeams"),
|
||||
call("Successfully installed module MSAL.PS"),
|
||||
call("M365 PowerShell modules initialized successfully"),
|
||||
]
|
||||
)
|
||||
@@ -456,96 +428,6 @@ class Testm365PowerShell:
|
||||
# Verify no info messages were logged
|
||||
mock_info.assert_not_called()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_graph_connection_success(self, mock_popen):
|
||||
"""Test test_graph_connection when token is valid"""
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
credentials = M365Credentials(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
tenant_id="test_tenant_id",
|
||||
)
|
||||
identity = M365IdentityInfo(
|
||||
identity_id="test_id",
|
||||
identity_type="Application",
|
||||
tenant_id="test_tenant",
|
||||
tenant_domain="example.com",
|
||||
tenant_domains=["example.com"],
|
||||
location="test_location",
|
||||
)
|
||||
session = M365PowerShell(credentials, identity)
|
||||
|
||||
# Mock execute to return a valid token
|
||||
session.execute = MagicMock(return_value="valid_token")
|
||||
|
||||
result = session.test_graph_connection()
|
||||
|
||||
assert result is True
|
||||
session.execute.assert_called_once_with("Write-Output $graphToken")
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_graph_connection_empty_token(self, mock_popen):
|
||||
"""Test test_graph_connection when token is empty"""
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
credentials = M365Credentials(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
tenant_id="test_tenant_id",
|
||||
)
|
||||
identity = M365IdentityInfo(
|
||||
identity_id="test_id",
|
||||
identity_type="Application",
|
||||
tenant_id="test_tenant",
|
||||
tenant_domain="example.com",
|
||||
tenant_domains=["example.com"],
|
||||
location="test_location",
|
||||
)
|
||||
session = M365PowerShell(credentials, identity)
|
||||
|
||||
# Mock execute to return empty token
|
||||
session.execute = MagicMock(return_value="")
|
||||
|
||||
with pytest.raises(M365GraphConnectionError) as exc_info:
|
||||
session.test_graph_connection()
|
||||
|
||||
assert "Microsoft Graph token is empty or invalid" in str(exc_info.value)
|
||||
session.execute.assert_called_once_with("Write-Output $graphToken")
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_graph_connection_exception(self, mock_popen):
|
||||
"""Test test_graph_connection when an exception occurs"""
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
credentials = M365Credentials(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
tenant_id="test_tenant_id",
|
||||
)
|
||||
identity = M365IdentityInfo(
|
||||
identity_id="test_id",
|
||||
identity_type="Application",
|
||||
tenant_id="test_tenant",
|
||||
tenant_domain="example.com",
|
||||
tenant_domains=["example.com"],
|
||||
location="test_location",
|
||||
)
|
||||
session = M365PowerShell(credentials, identity)
|
||||
|
||||
# Mock execute to raise an exception
|
||||
session.execute = MagicMock(side_effect=Exception("PowerShell error"))
|
||||
|
||||
with pytest.raises(M365GraphConnectionError) as exc_info:
|
||||
session.test_graph_connection()
|
||||
|
||||
assert "Failed to connect to Microsoft Graph API: PowerShell error" in str(
|
||||
exc_info.value
|
||||
)
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_teams_connection_success(self, mock_popen):
|
||||
"""Test test_teams_connection when token is valid"""
|
||||
@@ -1007,36 +889,6 @@ class Testm365PowerShell:
|
||||
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_credentials_certificate_auth_success(self, mock_popen):
|
||||
"""Test test_credentials method with certificate authentication - successful"""
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
certificate_content = base64.b64encode(b"fake_certificate").decode("utf-8")
|
||||
credentials = M365Credentials(
|
||||
client_id="test_client_id", certificate_content=certificate_content
|
||||
)
|
||||
identity = M365IdentityInfo()
|
||||
|
||||
# Create session without calling init_credential
|
||||
with patch.object(M365PowerShell, "init_credential"):
|
||||
session = M365PowerShell(credentials, identity)
|
||||
|
||||
# Mock successful certificate connections
|
||||
# Note: The actual implementation uses "or" so if teams succeeds, exchange won't be called
|
||||
session.test_teams_certificate_connection = MagicMock(return_value=True)
|
||||
session.test_exchange_certificate_connection = MagicMock(return_value=True)
|
||||
|
||||
result = session.test_credentials(credentials)
|
||||
assert result is True
|
||||
|
||||
session.test_teams_certificate_connection.assert_called_once()
|
||||
# Exchange connection should NOT be called if teams connection succeeds (due to "or" logic)
|
||||
session.test_exchange_certificate_connection.assert_not_called()
|
||||
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_credentials_certificate_auth_failure(self, mock_popen):
|
||||
"""Test test_credentials method with certificate authentication - failure"""
|
||||
@@ -1057,9 +909,6 @@ class Testm365PowerShell:
|
||||
session.test_teams_certificate_connection = MagicMock(return_value=False)
|
||||
session.test_exchange_certificate_connection = MagicMock(return_value=False)
|
||||
|
||||
result = session.test_credentials(credentials)
|
||||
assert result is True # Method always returns True after the try block
|
||||
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
@@ -1287,94 +1136,3 @@ class Testm365PowerShell:
|
||||
assert any('$tenantDomain = "contoso.com"' in cmd for cmd in executed_commands)
|
||||
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_credentials_certificate_auth_with_or_logic(self, mock_popen):
|
||||
"""Test test_credentials method with certificate auth using OR logic between Teams and Exchange"""
|
||||
certificate_content = base64.b64encode(b"fake_certificate").decode("utf-8")
|
||||
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
mock_process.returncode = 0
|
||||
|
||||
# Create session with non-certificate credentials first
|
||||
session = M365PowerShell(
|
||||
M365Credentials(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test_tenant_id",
|
||||
tenant_domains=["contoso.com"],
|
||||
),
|
||||
M365IdentityInfo(
|
||||
tenant_id="test_tenant_id",
|
||||
tenant_domain="contoso.com",
|
||||
tenant_domains=["contoso.com"],
|
||||
identity_id="test_identity_id",
|
||||
identity_type="Service Principal with Certificate",
|
||||
),
|
||||
)
|
||||
|
||||
# Mock that Teams connection fails but Exchange succeeds
|
||||
session.test_teams_certificate_connection = MagicMock(return_value=False)
|
||||
session.test_exchange_certificate_connection = MagicMock(return_value=True)
|
||||
|
||||
result = session.test_credentials(
|
||||
M365Credentials(
|
||||
client_id="test_client_id",
|
||||
tenant_id="test_tenant_id",
|
||||
certificate_content=certificate_content,
|
||||
tenant_domains=["contoso.com"],
|
||||
)
|
||||
)
|
||||
|
||||
assert result is True
|
||||
session.test_teams_certificate_connection.assert_called_once()
|
||||
session.test_exchange_certificate_connection.assert_called_once()
|
||||
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_test_credentials_certificate_auth_both_fail(self, mock_popen):
|
||||
"""Test test_credentials method with certificate auth when both Teams and Exchange fail"""
|
||||
certificate_content = base64.b64encode(b"fake_certificate").decode("utf-8")
|
||||
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
mock_process.returncode = 0
|
||||
|
||||
# Create session with non-certificate credentials first
|
||||
session = M365PowerShell(
|
||||
M365Credentials(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test_tenant_id",
|
||||
tenant_domains=["contoso.com"],
|
||||
),
|
||||
M365IdentityInfo(
|
||||
tenant_id="test_tenant_id",
|
||||
tenant_domain="contoso.com",
|
||||
tenant_domains=["contoso.com"],
|
||||
identity_id="test_identity_id",
|
||||
identity_type="Service Principal with Certificate",
|
||||
),
|
||||
)
|
||||
|
||||
# Mock that both connections fail
|
||||
session.test_teams_certificate_connection = MagicMock(return_value=False)
|
||||
session.test_exchange_certificate_connection = MagicMock(return_value=False)
|
||||
|
||||
# Even when both fail, the method should return True (this is the intended logic)
|
||||
result = session.test_credentials(
|
||||
M365Credentials(
|
||||
client_id="test_client_id",
|
||||
tenant_id="test_tenant_id",
|
||||
certificate_content=certificate_content,
|
||||
tenant_domains=["contoso.com"],
|
||||
)
|
||||
)
|
||||
|
||||
assert result is True
|
||||
session.test_teams_certificate_connection.assert_called_once()
|
||||
session.test_exchange_certificate_connection.assert_called_once()
|
||||
|
||||
session.close()
|
||||
|
||||
@@ -932,37 +932,6 @@ class TestM365Provider:
|
||||
assert result.certificate_content == certificate_content
|
||||
assert identity.identity_type == "Service Principal with Certificate"
|
||||
|
||||
def test_setup_powershell_invalid_credentials(self):
|
||||
"""Test setup_powershell with invalid credentials"""
|
||||
credentials_dict = {
|
||||
"client_id": "test_client_id",
|
||||
"tenant_id": "test_tenant_id",
|
||||
"client_secret": "test_client_secret",
|
||||
}
|
||||
|
||||
with (
|
||||
patch("prowler.providers.m365.m365_provider.M365PowerShell") as mock_ps,
|
||||
pytest.raises(M365ConfigCredentialsError) as exception,
|
||||
):
|
||||
mock_session = MagicMock()
|
||||
mock_session.test_credentials.return_value = False
|
||||
mock_session.close = MagicMock()
|
||||
mock_ps.return_value = mock_session
|
||||
|
||||
M365Provider.setup_powershell(
|
||||
m365_credentials=credentials_dict,
|
||||
identity=M365IdentityInfo(
|
||||
identity_id=IDENTITY_ID,
|
||||
identity_type="User",
|
||||
tenant_id=TENANT_ID,
|
||||
tenant_domain=DOMAIN,
|
||||
tenant_domains=["test.onmicrosoft.com"],
|
||||
location=LOCATION,
|
||||
),
|
||||
)
|
||||
assert exception.type == M365ConfigCredentialsError
|
||||
assert "The provided credentials are not valid." in str(exception.value)
|
||||
|
||||
def test_validate_arguments_browser_auth_without_tenant_id(self):
|
||||
"""Test validate_arguments with browser_auth but missing tenant_id"""
|
||||
with pytest.raises(M365BrowserAuthNoTenantIDError) as exception:
|
||||
|
||||
Reference in New Issue
Block a user