feat(m365): add exchange_mailbox_primary_smtp_custom_domain check (#11215)

Co-authored-by: Jasmine Sullivan <20147180@tafe.wa.edu.au>
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-06-09 16:24:25 +02:00
1 parent b3caee88e4
commit e710ebff1c
8 files changed
+617

No files matched your search

+1
View File
@@ -17,6 +17,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- External multi-provider compliance frameworks can be registered via the `prowler.compliance.universal` entry point group [(#11490)](https://github.com/prowler-cloud/prowler/pull/11490)
- AWS AI Security Framework support in the CLI dashboard [(#11475)](https://github.com/prowler-cloud/prowler/pull/11475)
- `entra_service_principal_privileged_role_no_owners` check for M365 provider, failing when a service principal with a permanent Tier 0 directory role has owners on the service principal or its parent app registration [(#11070)](https://github.com/prowler-cloud/prowler/issues/11070)
- `exchange_mailbox_primary_smtp_uses_custom_domain` check for M365 provider [(#11215)](https://github.com/prowler-cloud/prowler/pull/11215)
### 🐞 Fixed
@@ -950,6 +950,32 @@ class M365PowerShell(PowerShellSession):
"Get-TeamsProtectionPolicy | ConvertTo-Json -Depth 10", json_parse=True
)
def get_mailboxes(self) -> dict:
"""
Get Exchange Online Recipient-Facing Mailboxes.
Retrieves all recipient-facing mailboxes from Exchange Online with the
properties needed to evaluate primary SMTP domain policy.
Returns:
dict: Mailbox information in JSON format.
Example:
>>> get_mailboxes()
[
{
"Identity": "user1@contoso.com",
"DisplayName": "User One",
"PrimarySmtpAddress": "user1@contoso.com",
"RecipientTypeDetails": "UserMailbox"
}
]
"""
return self.execute(
"Get-EXOMailbox -ResultSize Unlimited | Select-Object Identity, DisplayName, PrimarySmtpAddress, RecipientTypeDetails | ConvertTo-Json -Depth 10",
json_parse=True,
)
def get_shared_mailboxes(self) -> dict:
"""
Get Exchange Online Shared Mailboxes.
@@ -0,0 +1,39 @@
{
"Provider": "m365",
"CheckID": "exchange_mailbox_primary_smtp_uses_custom_domain",
"CheckTitle": "Mailbox primary SMTP address must use a custom domain",
"CheckType": [],
"ServiceName": "exchange",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
"Description": "**Exchange Online mailboxes** should use a custom domain as their primary SMTP address, not the default **\\*.onmicrosoft.com** routing domain assigned by Microsoft on tenant creation. This check verifies that the **PrimarySmtpAddress** of every user-facing mailbox does not end with `.onmicrosoft.com`.",
"Risk": "Mailboxes still using **.onmicrosoft.com** as their primary SMTP address leak the internal **tenant identifier** in every From: header, helping attackers fingerprint the tenant for spear-phishing. They also bypass **DMARC/DKIM** hardening that organisations deploy on their custom domains and are frequently treated as low-trust by recipient anti-phishing engines.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/microsoft-365/admin/setup/add-domain",
"https://learn.microsoft.com/en-us/microsoft-365/admin/setup/domains-faq",
"https://learn.microsoft.com/en-us/powershell/module/exchange/get-mailbox",
"https://learn.microsoft.com/en-us/exchange/recipients-in-exchange-online/manage-user-mailboxes/manage-user-mailboxes"
],
"Remediation": {
"Code": {
"CLI": "Get-Mailbox -ResultSize Unlimited | Where-Object { $_.PrimarySmtpAddress -like '*.onmicrosoft.com' } | ForEach-Object { Set-Mailbox -Identity $_.Identity -PrimarySmtpAddress '<user>@<customdomain.com>' }",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft 365 admin center (https://admin.microsoft.com/)\n2. Go to Users > Active users and select the affected user\n3. Under the Aliases section, add the custom domain email address\n4. Set the custom domain address as the primary SMTP address\n5. Save changes and repeat for all affected mailboxes",
"Terraform": ""
},
"Recommendation": {
"Text": "Update the primary SMTP address of all affected mailboxes to use a custom domain. Ensure your custom domain is verified in the Microsoft 365 admin center before making this change.",
"Url": "https://hub.prowler.com/check/exchange_mailbox_primary_smtp_uses_custom_domain"
}
},
"Categories": [
"email-security"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,79 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.exchange.exchange_client import exchange_client
class exchange_mailbox_primary_smtp_uses_custom_domain(Check):
"""
Verify that every Exchange Online mailbox uses a custom domain as its
primary SMTP address, not the default .onmicrosoft.com routing domain.
The .onmicrosoft.com domain is assigned by Microsoft on tenant creation
and is not intended for ongoing mail. Mailboxes still using it leak the
internal tenant identifier in every From: header (aiding spear-phishing),
bypass DMARC/DKIM hardening on custom domains and are often treated as
low-trust by recipient anti-phishing engines.
- PASS: Primary SMTP address does not use the .onmicrosoft.com domain.
- FAIL: Primary SMTP address uses the .onmicrosoft.com domain.
- MANUAL: Exchange Online PowerShell unavailable; check cannot run.
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the check against all recipient-facing Exchange Online mailboxes.
Returns:
List[CheckReportM365]: A report for each mailbox with its SMTP
domain status, or a single MANUAL report if PowerShell was
unavailable.
"""
findings = []
# mailboxes is None when Exchange Online PowerShell could not be
# reached or the cmdlet raised. An empty list means PowerShell ran
# but the tenant has no recipient-facing mailboxes (no findings).
if exchange_client.mailboxes is None:
report = CheckReportM365(
metadata=self.metadata(),
resource={},
resource_name="Exchange Online Mailboxes",
resource_id="exchange_mailboxes",
)
report.status = "MANUAL"
report.status_extended = (
"Exchange Online PowerShell is unavailable. "
"Enable EXO PowerShell access to run this check."
)
findings.append(report)
return findings
for mailbox in exchange_client.mailboxes:
report = CheckReportM365(
metadata=self.metadata(),
resource=mailbox,
resource_name=mailbox.name or mailbox.identity,
resource_id=mailbox.identity,
)
if mailbox.primary_smtp_address.endswith(".onmicrosoft.com"):
report.status = "FAIL"
report.status_extended = (
f"Mailbox {mailbox.identity} "
f"({mailbox.recipient_type_details}) has primary SMTP "
f"address {mailbox.primary_smtp_address} using the "
f".onmicrosoft.com domain instead of a custom domain."
)
else:
report.status = "PASS"
report.status_extended = (
f"Mailbox {mailbox.identity} "
f"({mailbox.recipient_type_details}) has primary SMTP "
f"address {mailbox.primary_smtp_address} using a "
f"custom domain."
)
findings.append(report)
return findings
@@ -8,6 +8,15 @@ from prowler.lib.logger import logger
from prowler.providers.m365.lib.service.service import M365Service
from prowler.providers.m365.m365_provider import M365Provider
SYSTEM_MAILBOX_TYPES = {
"DiscoveryMailbox",
"ArbitrationMailbox",
"AuditLogMailbox",
"MonitoringMailbox",
"AuxAuditLogMailbox",
"SystemMailbox",
}
class Exchange(M365Service):
"""
@@ -34,6 +43,7 @@ class Exchange(M365Service):
self.role_assignment_policies = []
self.mailbox_audit_properties = []
self.shared_mailboxes = []
self.mailboxes = None
if self.powershell:
if self.powershell.connect_exchange_online():
@@ -46,6 +56,7 @@ class Exchange(M365Service):
self.role_assignment_policies = self._get_role_assignment_policies()
self.mailbox_audit_properties = self._get_mailbox_audit_properties()
self.shared_mailboxes = self._get_shared_mailboxes()
self.mailboxes = self._get_mailboxes()
self.powershell.close()
# Fetch license count via Graph API
@@ -355,6 +366,50 @@ class Exchange(M365Service):
)
return shared_mailboxes
def _get_mailboxes(self) -> Optional[list["Mailbox"]]:
"""
Get all recipient-facing mailboxes from Exchange Online.
Retrieves mailboxes of types UserMailbox, SharedMailbox, RoomMailbox
and EquipmentMailbox. System-managed mailbox types are excluded as
they are controlled by Microsoft and are not subject to domain policy.
Returns:
list[Mailbox]: List of mailboxes with their primary SMTP address
and recipient type details. Returns ``None`` when the
underlying PowerShell cmdlet raises, so callers can
distinguish "PowerShell unavailable" from "empty tenant".
"""
logger.info("Microsoft365 - Getting mailboxes...")
mailboxes = []
try:
mailboxes_data = self.powershell.get_mailboxes()
if not mailboxes_data:
return mailboxes
# PowerShell can return a single dict instead of a list when only
# one result is returned; normalize to a list for uniform handling.
if isinstance(mailboxes_data, dict):
mailboxes_data = [mailboxes_data]
for mailbox in mailboxes_data:
if mailbox:
recipient_type = mailbox.get("RecipientTypeDetails", "")
if recipient_type in SYSTEM_MAILBOX_TYPES:
continue
mailboxes.append(
Mailbox(
identity=mailbox.get("Identity", ""),
name=mailbox.get("DisplayName", ""),
primary_smtp_address=mailbox.get("PrimarySmtpAddress", ""),
recipient_type_details=recipient_type,
)
)
return mailboxes
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return None
class Organization(BaseModel):
"""
@@ -497,3 +552,22 @@ class SharedMailbox(BaseModel):
user_principal_name: str
external_directory_object_id: str
identity: str
class Mailbox(BaseModel):
"""
Model for an Exchange Online recipient-facing mailbox.
Attributes:
identity: The unique identity of the mailbox in Exchange.
name: Display name of the mailbox.
primary_smtp_address: The primary SMTP address used for outbound mail
and the From: header. This is the address the check evaluates.
recipient_type_details: The mailbox type (e.g., UserMailbox,
SharedMailbox, RoomMailbox, EquipmentMailbox).
"""
identity: str
name: str
primary_smtp_address: str
recipient_type_details: str
@@ -0,0 +1,300 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_exchange_mailbox_primary_smtp_uses_custom_domain:
def test_powershell_unavailable_manual(self):
"""MANUAL: Exchange Online PowerShell unavailable (mailboxes is None)."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
exchange_client.mailboxes = None
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain import (
exchange_mailbox_primary_smtp_uses_custom_domain,
)
check = exchange_mailbox_primary_smtp_uses_custom_domain()
result = check.execute()
assert len(result) == 1
assert result[0].status == "MANUAL"
assert "PowerShell" in result[0].status_extended
assert result[0].resource_name == "Exchange Online Mailboxes"
assert result[0].resource_id == "exchange_mailboxes"
def test_empty_tenant_no_findings(self):
"""Empty tenant (no mailboxes) produces zero findings, not MANUAL."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
exchange_client.mailboxes = []
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain import (
exchange_mailbox_primary_smtp_uses_custom_domain,
)
check = exchange_mailbox_primary_smtp_uses_custom_domain()
result = check.execute()
assert result == []
def test_custom_domain_passes(self):
"""PASS: Mailbox primary SMTP uses a custom domain."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain import (
exchange_mailbox_primary_smtp_uses_custom_domain,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Mailbox,
)
exchange_client.mailboxes = [
Mailbox(
identity="user1@contoso.com",
name="User One",
primary_smtp_address="user1@contoso.com",
recipient_type_details="UserMailbox",
)
]
check = exchange_mailbox_primary_smtp_uses_custom_domain()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "custom domain" in result[0].status_extended
assert result[0].resource_name == "User One"
assert result[0].resource_id == "user1@contoso.com"
assert result[0].location == "global"
def test_onmicrosoft_domain_fails(self):
"""FAIL: Mailbox primary SMTP uses .onmicrosoft.com."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain import (
exchange_mailbox_primary_smtp_uses_custom_domain,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Mailbox,
)
exchange_client.mailboxes = [
Mailbox(
identity="user1@contoso.onmicrosoft.com",
name="User One",
primary_smtp_address="user1@contoso.onmicrosoft.com",
recipient_type_details="UserMailbox",
)
]
check = exchange_mailbox_primary_smtp_uses_custom_domain()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert ".onmicrosoft.com" in result[0].status_extended
assert result[0].resource_name == "User One"
assert result[0].resource_id == "user1@contoso.onmicrosoft.com"
assert result[0].location == "global"
def test_mixed_mailboxes(self):
"""Test multiple mailboxes with mixed domain status."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain import (
exchange_mailbox_primary_smtp_uses_custom_domain,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Mailbox,
)
exchange_client.mailboxes = [
Mailbox(
identity="user1@contoso.com",
name="User One",
primary_smtp_address="user1@contoso.com",
recipient_type_details="UserMailbox",
),
Mailbox(
identity="shared@contoso.onmicrosoft.com",
name="Shared Mailbox",
primary_smtp_address="shared@contoso.onmicrosoft.com",
recipient_type_details="SharedMailbox",
),
]
check = exchange_mailbox_primary_smtp_uses_custom_domain()
result = check.execute()
assert len(result) == 2
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Mailbox user1@contoso.com (UserMailbox) has primary SMTP address user1@contoso.com using a custom domain."
)
assert result[1].status == "FAIL"
assert (
result[1].status_extended
== "Mailbox shared@contoso.onmicrosoft.com (SharedMailbox) has primary SMTP address shared@contoso.onmicrosoft.com using the .onmicrosoft.com domain instead of a custom domain."
)
def test_room_mailbox_custom_domain(self):
"""PASS: Room mailbox using a custom domain."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain import (
exchange_mailbox_primary_smtp_uses_custom_domain,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Mailbox,
)
exchange_client.mailboxes = [
Mailbox(
identity="boardroom@contoso.com",
name="Board Room",
primary_smtp_address="boardroom@contoso.com",
recipient_type_details="RoomMailbox",
)
]
check = exchange_mailbox_primary_smtp_uses_custom_domain()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_id == "boardroom@contoso.com"
assert result[0].location == "global"
def test_equipment_mailbox_onmicrosoft(self):
"""FAIL: Equipment mailbox using .onmicrosoft.com."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_mailbox_primary_smtp_uses_custom_domain.exchange_mailbox_primary_smtp_uses_custom_domain import (
exchange_mailbox_primary_smtp_uses_custom_domain,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Mailbox,
)
exchange_client.mailboxes = [
Mailbox(
identity="projector@contoso.onmicrosoft.com",
name="Projector",
primary_smtp_address="projector@contoso.onmicrosoft.com",
recipient_type_details="EquipmentMailbox",
)
]
check = exchange_mailbox_primary_smtp_uses_custom_domain()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_id == "projector@contoso.onmicrosoft.com"
assert result[0].location == "global"
@@ -495,6 +495,104 @@ class Test_Exchange_Service:
exchange_client.powershell.close()
@patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_mailboxes",
return_value=[
{
"Identity": "user1@contoso.com",
"DisplayName": "User One",
"PrimarySmtpAddress": "user1@contoso.com",
"RecipientTypeDetails": "UserMailbox",
},
{
"Identity": "room@contoso.com",
"DisplayName": "Boardroom",
"PrimarySmtpAddress": "room@contoso.com",
"RecipientTypeDetails": "RoomMailbox",
},
{
"Identity": "DiscoverySearchMailbox{D919BA05}",
"DisplayName": "Discovery Search Mailbox",
"PrimarySmtpAddress": "DiscoverySearchMailbox@contoso.onmicrosoft.com",
"RecipientTypeDetails": "DiscoveryMailbox",
},
{
"Identity": "SystemMailbox{1f05a927}",
"DisplayName": "Microsoft Exchange",
"PrimarySmtpAddress": "SystemMailbox@contoso.onmicrosoft.com",
"RecipientTypeDetails": "SystemMailbox",
},
],
)
def test_get_mailboxes_excludes_system_types(self, _mock_get_mailboxes):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online",
return_value=True,
),
):
exchange_client = Exchange(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
mailboxes = exchange_client.mailboxes
assert mailboxes is not None
assert len(mailboxes) == 2
identities = {m.identity for m in mailboxes}
assert identities == {"user1@contoso.com", "room@contoso.com"}
assert all(
m.recipient_type_details not in {"DiscoveryMailbox", "SystemMailbox"}
for m in mailboxes
)
exchange_client.powershell.close()
@patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_mailboxes",
return_value={
"Identity": "user1@contoso.com",
"DisplayName": "User One",
"PrimarySmtpAddress": "user1@contoso.com",
"RecipientTypeDetails": "UserMailbox",
},
)
def test_get_mailboxes_single_dict(self, _mock_get_mailboxes):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online",
return_value=True,
),
):
exchange_client = Exchange(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
mailboxes = exchange_client.mailboxes
assert mailboxes is not None
assert len(mailboxes) == 1
assert mailboxes[0].identity == "user1@contoso.com"
exchange_client.powershell.close()
@patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_mailboxes",
side_effect=Exception("Get-EXOMailbox failed"),
)
def test_get_mailboxes_returns_none_on_exception(self, _mock_get_mailboxes):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online",
return_value=True,
),
):
exchange_client = Exchange(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
assert exchange_client.mailboxes is None
exchange_client.powershell.close()
def test_get_total_paid_licenses_none(self):
with (
mock.patch(