chore(m365): deprecate user auth (#8865)

This commit is contained in:
Hugo Pereira Brito
2025-10-09 12:24:24 +02:00
committed by GitHub
parent 1a7f52fc9c
commit ecf749fce8
15 changed files with 200 additions and 1199 deletions

View File

@@ -147,29 +147,6 @@ class TestM365Arguments:
assert kwargs["action"] == "store_true"
assert "Azure CLI authentication" in kwargs["help"]
def test_env_auth_argument_configuration(self):
"""Test that env-auth argument is configured correctly"""
mock_m365_args = MagicMock()
mock_m365_args.subparsers = self.mock_subparsers
mock_m365_args.common_providers_parser = MagicMock()
arguments.init_parser(mock_m365_args)
# Find the env-auth argument call
calls = self.mock_auth_modes_group.add_argument.call_args_list
env_auth_call = None
for call in calls:
if call[0][0] == "--env-auth":
env_auth_call = call
break
assert env_auth_call is not None
# Check argument configuration
kwargs = env_auth_call[1]
assert kwargs["action"] == "store_true"
assert "User and Password environment variables" in kwargs["help"]
def test_sp_env_auth_argument_configuration(self):
"""Test that sp-env-auth argument is configured correctly"""
mock_m365_args = MagicMock()
@@ -191,7 +168,7 @@ class TestM365Arguments:
# Check argument configuration
kwargs = sp_env_call[1]
assert kwargs["action"] == "store_true"
assert "Azure Service Principal environment variables" in kwargs["help"]
assert "Service Principal environment variables" in kwargs["help"]
def test_browser_auth_argument_configuration(self):
"""Test that browser-auth argument is configured correctly"""
@@ -336,28 +313,7 @@ class TestM365ArgumentsIntegration:
args = parser.parse_args(["m365", "--az-cli-auth"])
assert args.az_cli_auth is True
assert args.env_auth is False
assert args.sp_env_auth is False
assert args.browser_auth is False
assert args.certificate_auth is False
def test_real_argument_parsing_env_auth(self):
"""Test parsing arguments with environment authentication"""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
common_parser = argparse.ArgumentParser(add_help=False)
mock_m365_args = MagicMock()
mock_m365_args.subparsers = subparsers
mock_m365_args.common_providers_parser = common_parser
arguments.init_parser(mock_m365_args)
# Parse arguments with environment auth
args = parser.parse_args(["m365", "--env-auth"])
assert args.az_cli_auth is False
assert args.env_auth is True
assert not hasattr(args, "env_auth")
assert args.sp_env_auth is False
assert args.browser_auth is False
assert args.certificate_auth is False
@@ -378,7 +334,7 @@ class TestM365ArgumentsIntegration:
args = parser.parse_args(["m365", "--sp-env-auth"])
assert args.az_cli_auth is False
assert args.env_auth is False
assert not hasattr(args, "env_auth")
assert args.sp_env_auth is True
assert args.browser_auth is False
assert args.certificate_auth is False
@@ -406,7 +362,7 @@ class TestM365ArgumentsIntegration:
)
assert args.az_cli_auth is False
assert args.env_auth is False
assert not hasattr(args, "env_auth")
assert args.sp_env_auth is False
assert args.browser_auth is True
assert args.certificate_auth is False
@@ -428,7 +384,7 @@ class TestM365ArgumentsIntegration:
args = parser.parse_args(["m365", "--certificate-auth"])
assert args.az_cli_auth is False
assert args.env_auth is False
assert not hasattr(args, "env_auth")
assert args.sp_env_auth is False
assert args.browser_auth is False
assert args.certificate_auth is True
@@ -495,7 +451,7 @@ class TestM365ArgumentsIntegration:
args = parser.parse_args(["m365"])
assert args.az_cli_auth is False
assert args.env_auth is False
assert not hasattr(args, "env_auth")
assert args.sp_env_auth is False
assert args.browser_auth is False
assert args.certificate_auth is False
@@ -547,7 +503,7 @@ class TestM365ArgumentsIntegration:
# This should raise SystemExit due to mutually exclusive group
try:
parser.parse_args(["m365", "--az-cli-auth", "--env-auth"])
parser.parse_args(["m365", "--az-cli-auth", "--sp-env-auth"])
assert False, "Expected SystemExit due to mutually exclusive arguments"
except SystemExit:
# This is expected
@@ -636,6 +592,6 @@ class TestM365ArgumentsIntegration:
assert args.certificate_path == "/home/user/cert.pem"
assert args.tenant_id == "12345678-1234-1234-1234-123456789012"
assert args.az_cli_auth is False
assert args.env_auth is False
assert not hasattr(args, "env_auth")
assert args.sp_env_auth is False
assert args.browser_auth is False

View File

@@ -7,8 +7,6 @@ from prowler.lib.powershell.powershell import PowerShellSession
from prowler.providers.m365.exceptions.exceptions import (
M365CertificateCreationError,
M365GraphConnectionError,
M365UserCredentialsError,
M365UserNotBelongingToTenantError,
)
from prowler.providers.m365.lib.powershell.m365_powershell import M365PowerShell
from prowler.providers.m365.models import M365Credentials, M365IdentityInfo
@@ -19,7 +17,11 @@ class Testm365PowerShell:
def test_init(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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="User",
@@ -40,7 +42,11 @@ class Testm365PowerShell:
@patch("subprocess.Popen")
def test_sanitize(self, _):
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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="User",
@@ -77,180 +83,50 @@ class Testm365PowerShell:
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(
user="test@example.com",
passwd="test_password",
encrypted_passwd="test_password",
client_id="test_client_id",
client_secret="test_client_secret",
tenant_id="test_tenant_id",
)
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="User",
identity_type="Service Principal",
tenant_id="test_tenant",
tenant_domain="example.com",
tenant_domains=["example.com"],
location="test_location",
)
session = M365PowerShell(credentials, identity)
with patch.object(M365PowerShell, "init_credential") as mock_init:
session = M365PowerShell(credentials, identity)
mock_init.assert_called_once_with(credentials)
# Mock encrypt_password to return a known value
session.encrypt_password = MagicMock(return_value="encrypted_password")
session.execute = MagicMock()
session.init_credential(credentials)
# Call original init_credential to verify application authentication setup
M365PowerShell.init_credential(session, credentials)
# Verify encrypt_password was called
session.encrypt_password.assert_any_call(credentials.passwd)
# Verify execute was called with the correct commands
session.execute.assert_any_call(f'$user = "{credentials.user}"')
session.execute.assert_any_call('$clientID = "test_client_id"')
session.execute.assert_any_call('$clientSecret = "test_client_secret"')
session.execute.assert_any_call('$tenantID = "test_tenant_id"')
session.execute.assert_any_call(
f'$secureString = "{credentials.encrypted_passwd}" | ConvertTo-SecureString'
'$graphtokenBody = @{ Grant_Type = "client_credentials"; Scope = "https://graph.microsoft.com/.default"; Client_Id = $clientID; Client_Secret = $clientSecret }'
)
session.execute.assert_any_call(
"$credential = New-Object System.Management.Automation.PSCredential ($user, $secureString)"
'$graphToken = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $graphtokenBody | Select-Object -ExpandProperty Access_Token'
)
session.close()
@patch("subprocess.Popen")
def test_test_credentials_exchange_success(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(
user="test@contoso.onmicrosoft.com",
passwd="test_password",
encrypted_passwd="test_encrypted_password",
client_id="test_client_id",
client_secret="test_client_secret",
tenant_id="test_tenant_id",
)
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="User",
tenant_id="test_tenant",
tenant_domain="contoso.onmicrosoft.com",
tenant_domains=["contoso.onmicrosoft.com"],
location="test_location",
user="test@contoso.onmicrosoft.com",
)
session = M365PowerShell(credentials, identity)
# Mock encrypt_password to return a known value
session.encrypt_password = MagicMock(return_value="encrypted_password")
# Mock execute to simulate successful Exchange connection
def mock_execute_side_effect(command):
if "Connect-ExchangeOnline" in command:
return "Connected successfully https://aka.ms/exov3-module"
return ""
session.execute = MagicMock(side_effect=mock_execute_side_effect)
# Execute the test
result = session.test_credentials(credentials)
assert result is True
# Verify execute was called with the correct commands
session.execute.assert_any_call(
f'$securePassword = "{credentials.encrypted_passwd}" | ConvertTo-SecureString'
)
session.execute.assert_any_call(
f'$credential = New-Object System.Management.Automation.PSCredential("{session.sanitize(credentials.user)}", $securePassword)'
)
# Exchange connection should be tested
session.execute.assert_any_call(
"Connect-ExchangeOnline -Credential $credential"
)
# Verify Teams connection was NOT called (since Exchange succeeded)
teams_calls = [
call
for call in session.execute.call_args_list
if "Connect-MicrosoftTeams" in str(call)
]
assert (
len(teams_calls) == 0
), "Teams connection should not be called when Exchange succeeds"
session.close()
@patch("subprocess.Popen")
def test_test_credentials_exchange_fail_teams_success(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(
user="test@contoso.onmicrosoft.com",
passwd="test_password",
encrypted_passwd="test_encrypted_password",
client_id="test_client_id",
client_secret="test_client_secret",
tenant_id="test_tenant_id",
)
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="User",
tenant_id="test_tenant",
tenant_domain="contoso.onmicrosoft.com",
tenant_domains=["contoso.onmicrosoft.com"],
location="test_location",
user="test@contoso.onmicrosoft.com",
)
session = M365PowerShell(credentials, identity)
# Mock encrypt_password to return a known value
session.encrypt_password = MagicMock(return_value="encrypted_password")
# Mock execute to simulate Exchange fail and Teams success
def mock_execute_side_effect(command):
if "Connect-ExchangeOnline" in command:
return (
"Connection failed" # No "https://aka.ms/exov3-module" in response
)
elif "Connect-MicrosoftTeams" in command:
return "Connected successfully test@contoso.onmicrosoft.com"
return ""
session.execute = MagicMock(side_effect=mock_execute_side_effect)
# Execute the test
result = session.test_credentials(credentials)
assert result is True
# Verify execute was called with the correct commands
session.execute.assert_any_call(
f'$securePassword = "{credentials.encrypted_passwd}" | ConvertTo-SecureString'
)
session.execute.assert_any_call(
f'$credential = New-Object System.Management.Automation.PSCredential("{session.sanitize(credentials.user)}", $securePassword)'
)
# Both Exchange and Teams connections should be tested
session.execute.assert_any_call(
"Connect-ExchangeOnline -Credential $credential"
)
session.execute.assert_any_call(
"Connect-MicrosoftTeams -Credential $credential"
)
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(
user="",
passwd="",
encrypted_passwd="",
client_id="test_client_id",
client_secret="test_client_secret",
tenant_id="test_tenant_id",
)
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="Application",
identity_type="Service Principal",
tenant_id="test_tenant",
tenant_domain="contoso.onmicrosoft.com",
tenant_domains=["contoso.onmicrosoft.com"],
@@ -264,162 +140,13 @@ class Testm365PowerShell:
session.execute.assert_any_call("Write-Output $graphToken")
session.close()
@patch("subprocess.Popen")
@patch("msal.ConfidentialClientApplication")
def test_test_credentials_user_not_belonging_to_tenant(self, mock_msal, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
mock_msal_instance = MagicMock()
mock_msal.return_value = mock_msal_instance
mock_msal_instance.acquire_token_by_username_password.return_value = {
"access_token": "test_token"
}
credentials = M365Credentials(
user="user@otherdomain.com",
passwd="test_password",
client_id="test_client_id",
client_secret="test_client_secret",
tenant_id="test_tenant_id",
)
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="User",
tenant_id="test_tenant",
tenant_domain="contoso.onmicrosoft.com",
tenant_domains=["contoso.onmicrosoft.com"],
location="test_location",
)
session = M365PowerShell(credentials, identity)
# Mock the execute method to return the decrypted password
def mock_execute(command, *args, **kwargs):
if "Write-Output" in command:
return "decrypted_password"
return None
session.execute = MagicMock(side_effect=mock_execute)
session.process.stdin.write = MagicMock()
session.read_output = MagicMock(return_value="decrypted_password")
with pytest.raises(M365UserNotBelongingToTenantError) as exception:
session.test_credentials(credentials)
assert exception.type == M365UserNotBelongingToTenantError
assert (
"The user domain otherdomain.com does not match any of the tenant domains: contoso.onmicrosoft.com"
in str(exception.value)
)
# Verify MSAL was not called since domain validation failed first
mock_msal.assert_not_called()
mock_msal_instance.acquire_token_by_username_password.assert_not_called()
session.close()
@patch("subprocess.Popen")
def test_test_credentials_auth_failure_aadsts_error(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(
user="test@contoso.onmicrosoft.com",
passwd="test_password",
encrypted_passwd="test_encrypted_password",
client_id="test_client_id",
client_secret="test_client_secret",
tenant_id="test_tenant_id",
)
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="User",
tenant_id="test_tenant",
tenant_domain="contoso.onmicrosoft.com",
tenant_domains=["contoso.onmicrosoft.com"],
location="test_location",
)
session = M365PowerShell(credentials, identity)
# Mock encrypt_password and execute to simulate AADSTS error
session.encrypt_password = MagicMock(return_value="encrypted_password")
session.execute = MagicMock(
return_value="AADSTS50126: Error validating credentials due to invalid username or password"
)
with pytest.raises(M365UserCredentialsError) as exc_info:
session.test_credentials(credentials)
assert (
"AADSTS50126: Error validating credentials due to invalid username or password"
in str(exc_info.value)
)
# Verify execute was called with the correct commands
session.execute.assert_any_call(
f'$securePassword = "{credentials.encrypted_passwd}" | ConvertTo-SecureString'
)
session.execute.assert_any_call(
f'$credential = New-Object System.Management.Automation.PSCredential("{session.sanitize(credentials.user)}", $securePassword)'
)
session.execute.assert_any_call(
"Connect-ExchangeOnline -Credential $credential"
)
session.close()
@patch("subprocess.Popen")
def test_test_credentials_auth_failure_no_access_token(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(
user="test@contoso.onmicrosoft.com",
passwd="test_password",
encrypted_passwd="test_encrypted_password",
client_id="test_client_id",
client_secret="test_client_secret",
tenant_id="test_tenant_id",
)
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="User",
tenant_id="test_tenant",
tenant_domain="contoso.onmicrosoft.com",
tenant_domains=["contoso.onmicrosoft.com"],
location="test_location",
)
session = M365PowerShell(credentials, identity)
# Mock encrypt_password and execute to simulate AADSTS invalid grant error
session.encrypt_password = MagicMock(return_value="encrypted_password")
session.execute = MagicMock(
return_value="AADSTS70002: The request body must contain the following parameter: 'client_secret' or 'client_assertion'."
)
with pytest.raises(M365UserCredentialsError) as exc_info:
session.test_credentials(credentials)
assert (
"AADSTS70002: The request body must contain the following parameter: 'client_secret' or 'client_assertion'."
in str(exc_info.value)
)
# Verify execute was called with the correct commands
session.execute.assert_any_call(
f'$securePassword = "{credentials.encrypted_passwd}" | ConvertTo-SecureString'
)
session.execute.assert_any_call(
f'$credential = New-Object System.Management.Automation.PSCredential("{session.sanitize(credentials.user)}", $securePassword)'
)
session.execute.assert_any_call(
"Connect-ExchangeOnline -Credential $credential"
)
session.close()
@patch("subprocess.Popen")
def test_remove_ansi(self, mock_popen):
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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="User",
@@ -446,7 +173,11 @@ class Testm365PowerShell:
def test_execute(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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="User",
@@ -469,7 +200,11 @@ class Testm365PowerShell:
"""Test the read_output method with various scenarios"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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="User",
@@ -516,7 +251,11 @@ class Testm365PowerShell:
def test_json_parse_output(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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="User",
@@ -549,7 +288,11 @@ class Testm365PowerShell:
def test_close(self, mock_popen):
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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="User",
@@ -718,7 +461,11 @@ class Testm365PowerShell:
"""Test test_graph_connection when token is valid"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -743,7 +490,11 @@ class Testm365PowerShell:
"""Test test_graph_connection when token is empty"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -769,7 +520,11 @@ class Testm365PowerShell:
"""Test test_graph_connection when an exception occurs"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -797,7 +552,11 @@ class Testm365PowerShell:
"""Test test_teams_connection when token is valid"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -835,7 +594,11 @@ class Testm365PowerShell:
"""Test test_teams_connection when token lacks required permissions"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -870,7 +633,11 @@ class Testm365PowerShell:
"""Test test_teams_connection when an exception occurs"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -899,7 +666,11 @@ class Testm365PowerShell:
"""Test test_exchange_connection when token is valid"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -937,7 +708,11 @@ class Testm365PowerShell:
"""Test test_exchange_connection when token lacks required permissions"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -972,7 +747,11 @@ class Testm365PowerShell:
"""Test test_exchange_connection when an exception occurs"""
mock_process = MagicMock()
mock_popen.return_value = mock_process
credentials = M365Credentials(user="test@example.com", passwd="test_password")
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",
@@ -995,58 +774,6 @@ class Testm365PowerShell:
)
session.close()
@patch("subprocess.Popen")
def test_encrypt_password(self, mock_popen):
credentials = M365Credentials(user="test@example.com", passwd="test_password")
identity = M365IdentityInfo(
identity_id="test_id",
identity_type="User",
tenant_id="test_tenant",
tenant_domain="example.com",
tenant_domains=["example.com"],
location="test_location",
)
session = M365PowerShell(credentials, identity)
# Test non-Windows system (should use utf-16le hex encoding)
from unittest import mock
with mock.patch("platform.system", return_value="Linux"):
result = session.encrypt_password("password123")
expected = "password123".encode("utf-16le").hex()
assert result == expected
# Test Windows system with tuple return
with mock.patch("platform.system", return_value="Windows"):
import sys
win32crypt_mock = mock.MagicMock()
win32crypt_mock.CryptProtectData.return_value = (None, b"encrypted_bytes")
sys.modules["win32crypt"] = win32crypt_mock
result = session.encrypt_password("password123")
assert result == b"encrypted_bytes".hex()
# Clean up mock
del sys.modules["win32crypt"]
# Test error handling
with mock.patch("platform.system", return_value="Windows"):
import sys
win32crypt_mock = mock.MagicMock()
win32crypt_mock.CryptProtectData.side_effect = Exception("Test error")
sys.modules["win32crypt"] = win32crypt_mock
with pytest.raises(Exception) as exc_info:
session.encrypt_password("password123")
assert "Error encrypting password: Test error" in str(exc_info.value)
# Clean up mock
del sys.modules["win32crypt"]
session.close()
@patch("subprocess.Popen")
def test_clean_certificate_content(self, mock_popen):
"""Test clean_certificate_content method with various certificate content formats"""

View File

@@ -23,7 +23,9 @@ LOCATION = "global"
def set_mocked_m365_provider(
session_credentials: DefaultAzureCredential = DefaultAzureCredential(),
credentials: M365Credentials = M365Credentials(
user="user@email.com", passwd="111111aa111111aaa1111"
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
tenant_id=TENANT_ID,
),
identity: M365IdentityInfo = M365IdentityInfo(
identity_id=IDENTITY_ID,

View File

@@ -29,20 +29,15 @@ from prowler.providers.m365.exceptions.exceptions import (
M365GetTokenIdentityError,
M365HTTPResponseError,
M365InvalidProviderIdError,
M365MissingEnvironmentCredentialsError,
M365NoAuthenticationMethodError,
M365NotTenantIdButClientIdAndClientSecretError,
M365NotValidCertificateContentError,
M365NotValidCertificatePathError,
M365NotValidClientIdError,
M365NotValidClientSecretError,
M365NotValidPasswordError,
M365NotValidTenantIdError,
M365NotValidUserError,
M365TenantIdAndClientIdNotBelongingToClientSecretError,
M365TenantIdAndClientSecretNotBelongingToClientIdError,
M365UserCredentialsError,
M365UserNotBelongingToTenantError,
)
from prowler.providers.m365.m365_provider import M365Provider
from prowler.providers.m365.models import (
@@ -97,8 +92,6 @@ class TestM365Provider:
client_id=CLIENT_ID,
tenant_id=TENANT_ID,
client_secret=CLIENT_SECRET,
user="",
passwd="",
),
),
):
@@ -106,71 +99,6 @@ class TestM365Provider:
sp_env_auth=True,
az_cli_auth=False,
browser_auth=False,
env_auth=False,
tenant_id=tenant_id,
client_id=client_id,
client_secret=client_secret,
region=azure_region,
config_path=default_config_file_path,
fixer_config=fixer_config,
)
assert m365_provider.region_config == M365RegionConfig(
name="M365Global",
authority=None,
base_url="https://graph.microsoft.com",
credential_scopes=["https://graph.microsoft.com/.default"],
)
assert m365_provider.identity == M365IdentityInfo(
identity_id=IDENTITY_ID,
identity_type=IDENTITY_TYPE,
tenant_id=TENANT_ID,
tenant_domain=DOMAIN,
location=LOCATION,
)
def test_m365_provider_env_auth(self):
tenant_id = None
client_id = None
client_secret = None
fixer_config = load_and_validate_config_file(
"m365", default_fixer_config_file_path
)
azure_region = "M365Global"
with (
patch(
"prowler.providers.m365.m365_provider.M365Provider.setup_session",
return_value=ClientSecretCredential(
client_id=CLIENT_ID,
tenant_id=TENANT_ID,
client_secret=CLIENT_SECRET,
),
),
patch(
"prowler.providers.m365.m365_provider.M365Provider.setup_identity",
return_value=M365IdentityInfo(
identity_id=IDENTITY_ID,
identity_type=IDENTITY_TYPE,
tenant_id=TENANT_ID,
tenant_domain=DOMAIN,
location=LOCATION,
),
),
patch(
"prowler.providers.m365.m365_provider.M365Provider.setup_powershell",
return_value=M365Credentials(
user="test@test.com",
passwd="password",
),
),
):
m365_provider = M365Provider(
sp_env_auth=False,
az_cli_auth=False,
browser_auth=False,
env_auth=True,
tenant_id=tenant_id,
client_id=client_id,
client_secret=client_secret,
@@ -228,7 +156,6 @@ class TestM365Provider:
sp_env_auth=False,
az_cli_auth=True,
browser_auth=False,
env_auth=False,
region=azure_region,
config_path=default_config_file_path,
fixer_config=fixer_config,
@@ -278,7 +205,6 @@ class TestM365Provider:
sp_env_auth=False,
az_cli_auth=False,
browser_auth=True,
env_auth=False,
tenant_id=TENANT_ID,
region=azure_region,
config_path=default_config_file_path,
@@ -398,68 +324,6 @@ class TestM365Provider:
assert test_connection.is_connected
assert test_connection.error is None
def test_test_connection_tenant_id_client_id_client_secret_no_user_password(
self,
):
with (
patch(
"prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials"
) as mock_validate_static_credentials,
patch(
"prowler.providers.m365.m365_provider.M365Provider.validate_arguments"
),
patch("prowler.providers.m365.m365_provider.M365Provider.setup_powershell"),
):
mock_validate_static_credentials.side_effect = M365NotValidUserError(
file=os.path.basename(__file__),
message="The provided M365 User is not valid.",
)
with pytest.raises(M365NotValidUserError) as exception:
M365Provider.test_connection(
tenant_id=str(uuid4()),
region="M365Global",
raise_on_exception=True,
client_id=str(uuid4()),
client_secret=str(uuid4()),
user=None,
password="test_password",
)
assert exception.type == M365NotValidUserError
assert "The provided M365 User is not valid." in str(exception.value)
def test_test_connection_tenant_id_client_id_client_secret_user_no_password(
self,
):
with (
patch(
"prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials"
) as mock_validate_static_credentials,
patch(
"prowler.providers.m365.m365_provider.M365Provider.validate_arguments"
),
patch("prowler.providers.m365.m365_provider.M365Provider.setup_powershell"),
):
mock_validate_static_credentials.side_effect = M365NotValidPasswordError(
file=os.path.basename(__file__),
message="The provided M365 Password is not valid.",
)
with pytest.raises(M365NotValidPasswordError) as exception:
M365Provider.test_connection(
tenant_id=str(uuid4()),
region="M365Global",
raise_on_exception=True,
client_id=str(uuid4()),
client_secret=str(uuid4()),
user="test@example.com",
password=None,
)
assert exception.type == M365NotValidPasswordError
assert "The provided M365 Password is not valid." in str(exception.value)
def test_test_connection_with_httpresponseerror(self):
with (
patch(
@@ -513,27 +377,24 @@ class TestM365Provider:
assert exception.type == M365NoAuthenticationMethodError
assert (
"M365 provider requires at least one authentication method set: [--env-auth | --az-cli-auth | --sp-env-auth | --browser-auth | --certificate-auth]"
"M365 provider requires at least one authentication method set: [--az-cli-auth | --sp-env-auth | --browser-auth | --certificate-auth]"
in exception.value.args[0]
)
def test_setup_powershell_valid_credentials(self):
credentials_dict = {
"user": "test@example.com",
"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,
),
):
with patch("prowler.providers.m365.m365_provider.M365PowerShell") as mock_ps:
mock_session = MagicMock()
mock_session.test_credentials.return_value = True
mock_session.close = MagicMock()
mock_ps.return_value = mock_session
result = M365Provider.setup_powershell(
env_auth=False,
m365_credentials=credentials_dict,
identity=M365IdentityInfo(
identity_id=IDENTITY_ID,
@@ -544,42 +405,9 @@ class TestM365Provider:
location=LOCATION,
),
)
assert result.user == credentials_dict["user"]
assert result.passwd == credentials_dict["password"]
def test_test_connection_user_not_belonging_to_tenant(
self,
):
with (
patch(
"prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials"
) as mock_validate_static_credentials,
patch(
"prowler.providers.m365.m365_provider.M365Provider.validate_arguments"
),
patch("prowler.providers.m365.m365_provider.M365Provider.setup_powershell"),
):
mock_validate_static_credentials.side_effect = M365UserNotBelongingToTenantError(
file=os.path.basename(__file__),
message="The provided M365 User does not belong to the specified tenant.",
)
with pytest.raises(M365UserNotBelongingToTenantError) as exception:
M365Provider.test_connection(
tenant_id="contoso.onmicrosoft.com",
region="M365Global",
raise_on_exception=True,
client_id=str(uuid4()),
client_secret=str(uuid4()),
user="user@otherdomain.com",
password="test_password",
)
assert exception.type == M365UserNotBelongingToTenantError
assert (
"The provided M365 User does not belong to the specified tenant."
in str(exception.value)
)
assert result.client_id == credentials_dict["client_id"]
assert result.client_secret == credentials_dict["client_secret"]
def test_validate_static_credentials_invalid_tenant_id(self):
with pytest.raises(M365NotValidTenantIdError) as exception:
@@ -587,8 +415,6 @@ class TestM365Provider:
tenant_id="invalid-tenant-id",
client_id="12345678-1234-5678-1234-567812345678",
client_secret="test_secret",
user="test@example.com",
password="test_password",
)
assert "The provided Tenant ID is not valid." in str(exception.value)
@@ -598,8 +424,6 @@ class TestM365Provider:
tenant_id="12345678-1234-5678-1234-567812345678",
client_id="",
client_secret="test_secret",
user="test@example.com",
password="test_password",
)
assert "The provided Client ID is not valid." in str(exception.value)
@@ -609,36 +433,12 @@ class TestM365Provider:
tenant_id="12345678-1234-5678-1234-567812345678",
client_id="12345678-1234-5678-1234-567812345678",
client_secret="",
user="test@example.com",
password="test_password",
)
assert (
"You must provide a client secret, certificate content or certificate path. Please check your credentials and try again."
in str(exception.value)
)
def test_validate_arguments_missing_env_credentials(self):
with pytest.raises(M365ConfigCredentialsError) as exception:
M365Provider.validate_arguments(
az_cli_auth=False,
sp_env_auth=False,
env_auth=True,
browser_auth=False,
certificate_auth=False,
tenant_id="test_tenant_id",
client_id="test_client_id",
client_secret=None,
user=None,
password=None,
certificate_content=None,
certificate_path=None,
)
assert (
"You must provide a valid set of credentials. Please check your credentials and try again."
in str(exception.value)
)
def test_test_connection_invalid_provider_id(self):
with (
patch(
@@ -680,8 +480,6 @@ class TestM365Provider:
raise_on_exception=True,
client_id=str(uuid4()),
client_secret=str(uuid4()),
user=f"user@{user_domain}",
password="test_password",
provider_id=provider_id,
)
@@ -694,24 +492,23 @@ class TestM365Provider:
def test_provider_init_modules_false(self):
"""Test that initialize_m365_powershell_modules is not called when init_modules is False"""
credentials_dict = {
"user": "test@example.com",
"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,
),
patch("prowler.providers.m365.m365_provider.M365PowerShell") as mock_ps,
patch(
"prowler.providers.m365.m365_provider.initialize_m365_powershell_modules"
) as mock_init_modules,
):
mock_session = MagicMock()
mock_session.test_credentials.return_value = True
mock_session.close = MagicMock()
mock_ps.return_value = mock_session
M365Provider.setup_powershell(
env_auth=False,
m365_credentials=credentials_dict,
identity=M365IdentityInfo(
identity_id=IDENTITY_ID,
@@ -728,24 +525,23 @@ class TestM365Provider:
def test_provider_init_modules_true(self):
"""Test that initialize_m365_powershell_modules is called when init_modules is True"""
credentials_dict = {
"user": "test@example.com",
"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,
),
patch("prowler.providers.m365.m365_provider.M365PowerShell") as mock_ps,
patch(
"prowler.providers.m365.m365_provider.initialize_m365_powershell_modules"
) as mock_init_modules,
):
mock_session = MagicMock()
mock_session.test_credentials.return_value = True
mock_session.close = MagicMock()
mock_ps.return_value = mock_session
M365Provider.setup_powershell(
env_auth=False,
m365_credentials=credentials_dict,
identity=M365IdentityInfo(
identity_id=IDENTITY_ID,
@@ -762,26 +558,25 @@ class TestM365Provider:
def test_setup_powershell_init_modules_failure(self):
"""Test that setup_powershell handles initialization failures correctly"""
credentials_dict = {
"user": "test@example.com",
"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,
),
patch("prowler.providers.m365.m365_provider.M365PowerShell") as mock_ps,
patch(
"prowler.providers.m365.m365_provider.initialize_m365_powershell_modules",
side_effect=Exception("Module initialization failed"),
),
):
mock_session = MagicMock()
mock_session.test_credentials.return_value = True
mock_session.close = MagicMock()
mock_ps.return_value = mock_session
with pytest.raises(Exception) as exc_info:
M365Provider.setup_powershell(
env_auth=False,
m365_credentials=credentials_dict,
identity=M365IdentityInfo(
identity_id=IDENTITY_ID,
@@ -837,8 +632,6 @@ class TestM365Provider:
raise_on_exception=True,
client_id=str(uuid4()),
client_secret=str(uuid4()),
user="user@contoso.onmicrosoft.com",
password="test_password",
provider_id=provider_id,
)
@@ -893,7 +686,6 @@ class TestM365Provider:
sp_env_auth=False,
az_cli_auth=False,
browser_auth=False,
env_auth=False,
certificate_auth=True,
tenant_id=tenant_id,
client_id=client_id,
@@ -1067,64 +859,6 @@ class TestM365Provider:
check_certificate_content=True
)
def test_setup_powershell_env_auth_missing_credentials(self):
"""Test setup_powershell with env_auth but missing environment variables"""
with (
patch.dict(os.environ, {}, clear=True),
pytest.raises(M365MissingEnvironmentCredentialsError) as exception,
):
M365Provider.setup_powershell(
env_auth=True,
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 == M365MissingEnvironmentCredentialsError
assert (
"Missing M365_USER or M365_PASSWORD environment variables required for credentials authentication."
in str(exception.value)
)
def test_setup_powershell_env_auth_success(self):
"""Test setup_powershell with env_auth and valid environment variables"""
with (
patch.dict(
os.environ,
{
"M365_USER": "test@example.com",
"M365_PASSWORD": "password",
"AZURE_CLIENT_ID": CLIENT_ID,
"AZURE_CLIENT_SECRET": CLIENT_SECRET,
"AZURE_TENANT_ID": TENANT_ID,
},
),
patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials",
return_value=True,
),
):
result = M365Provider.setup_powershell(
env_auth=True,
identity=M365IdentityInfo(
identity_id=IDENTITY_ID,
identity_type="User",
tenant_id=TENANT_ID,
tenant_domain=DOMAIN,
tenant_domains=["test.onmicrosoft.com"],
location=LOCATION,
),
)
assert result.user == "test@example.com"
assert result.passwd == "password"
assert result.client_id == CLIENT_ID
def test_setup_powershell_sp_env_auth_success(self):
"""Test setup_powershell with sp_env_auth and valid environment variables"""
with (
@@ -1136,11 +870,13 @@ class TestM365Provider:
"AZURE_TENANT_ID": TENANT_ID,
},
),
patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials",
return_value=True,
),
patch("prowler.providers.m365.m365_provider.M365PowerShell") as mock_ps,
):
mock_session = MagicMock()
mock_session.test_credentials.return_value = True
mock_session.close = MagicMock()
mock_ps.return_value = mock_session
result = M365Provider.setup_powershell(
sp_env_auth=True,
identity=M365IdentityInfo(
@@ -1170,11 +906,13 @@ class TestM365Provider:
"M365_CERTIFICATE_CONTENT": certificate_content,
},
),
patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials",
return_value=True,
),
patch("prowler.providers.m365.m365_provider.M365PowerShell") as mock_ps,
):
mock_session = MagicMock()
mock_session.test_credentials.return_value = True
mock_session.close = MagicMock()
mock_ps.return_value = mock_session
identity = M365IdentityInfo(
identity_id=IDENTITY_ID,
identity_type="Service Principal with Certificate",
@@ -1197,22 +935,21 @@ class TestM365Provider:
def test_setup_powershell_invalid_credentials(self):
"""Test setup_powershell with invalid credentials"""
credentials_dict = {
"user": "test@example.com",
"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=False,
),
pytest.raises(M365UserCredentialsError) as exception,
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(
env_auth=False,
m365_credentials=credentials_dict,
identity=M365IdentityInfo(
identity_id=IDENTITY_ID,
@@ -1223,9 +960,8 @@ class TestM365Provider:
location=LOCATION,
),
)
assert exception.type == M365UserCredentialsError
assert "The provided User credentials are not valid." in str(exception.value)
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"""
@@ -1233,14 +969,11 @@ class TestM365Provider:
M365Provider.validate_arguments(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=True,
certificate_auth=False,
tenant_id=None,
client_id=None,
client_secret=None,
user=None,
password=None,
certificate_content=None,
certificate_path=None,
)
@@ -1257,14 +990,11 @@ class TestM365Provider:
M365Provider.validate_arguments(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=False,
tenant_id=TENANT_ID,
client_id=None,
client_secret=None,
user=None,
password=None,
certificate_content=None,
certificate_path=None,
)
@@ -1280,14 +1010,11 @@ class TestM365Provider:
M365Provider.validate_arguments(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=False,
tenant_id=None,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
user=None,
password=None,
certificate_content=None,
certificate_path=None,
)
@@ -1326,8 +1053,6 @@ class TestM365Provider:
"tenant_id": TENANT_ID,
"client_id": CLIENT_ID,
"client_secret": None,
"user": None,
"password": None,
"certificate_content": certificate_content,
},
),
@@ -1392,8 +1117,6 @@ class TestM365Provider:
tenant_id=str(uuid4()),
client_id=str(uuid4()),
client_secret="test_secret",
user="test@example.com",
password="test_password",
)
assert exception.type == M365ClientIdAndClientSecretNotBelongingToTenantIdError
@@ -1419,8 +1142,6 @@ class TestM365Provider:
tenant_id=str(uuid4()),
client_id=str(uuid4()),
client_secret="test_secret",
user="test@example.com",
password="test_password",
)
assert exception.type == M365TenantIdAndClientSecretNotBelongingToClientIdError
@@ -1446,8 +1167,6 @@ class TestM365Provider:
tenant_id=str(uuid4()),
client_id=str(uuid4()),
client_secret="test_secret",
user="test@example.com",
password="test_password",
)
assert exception.type == M365TenantIdAndClientIdNotBelongingToClientSecretError
@@ -1529,7 +1248,6 @@ class TestM365Provider:
result = M365Provider.setup_session(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=True,
certificate_path=None,
@@ -1580,7 +1298,6 @@ class TestM365Provider:
M365Provider.setup_session(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=True,
certificate_path=None,
@@ -1600,8 +1317,6 @@ class TestM365Provider:
"tenant_id": TENANT_ID,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"user": None,
"password": None,
"certificate_content": certificate_content,
}
@@ -1623,7 +1338,6 @@ class TestM365Provider:
result = M365Provider.setup_session(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=False,
certificate_path=None,
@@ -1671,14 +1385,11 @@ class TestM365Provider:
M365Provider.validate_arguments(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=True,
tenant_id=TENANT_ID,
client_id=CLIENT_ID,
client_secret=None,
user=None,
password=None,
certificate_content=certificate_content,
certificate_path=None,
)
@@ -1689,14 +1400,11 @@ class TestM365Provider:
M365Provider.validate_arguments(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=False,
tenant_id=TENANT_ID,
client_id=CLIENT_ID,
client_secret=None,
user=None,
password=None,
certificate_content=None,
certificate_path=None,
)
@@ -1991,7 +1699,6 @@ class TestM365Provider:
result = M365Provider.setup_session(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=True,
certificate_path=certificate_path,
@@ -2018,8 +1725,6 @@ class TestM365Provider:
"tenant_id": TENANT_ID,
"client_id": CLIENT_ID,
"client_secret": None,
"user": None,
"password": None,
"certificate_content": None,
"certificate_path": certificate_path,
}
@@ -2036,7 +1741,6 @@ class TestM365Provider:
result = M365Provider.setup_session(
az_cli_auth=False,
sp_env_auth=False,
env_auth=False,
browser_auth=False,
certificate_auth=False,
certificate_path=None,