feat(m365): add entra_conditional_access_policy_mdm_compliant_device_required check (#10220)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
Hugo Pereira Brito
2026-03-26 11:36:30 +01:00
committed by GitHub
co-authored by Daniel Barranquero
parent dd00d71a07
commit c651f60e3a
12 changed files with 1418 additions and 0 deletions
@@ -0,0 +1,485 @@
from types import SimpleNamespace
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ApplicationEnforcedRestrictions,
ApplicationsConditions,
ConditionalAccessGrantControl,
ConditionalAccessPolicy,
ConditionalAccessPolicyState,
Conditions,
GrantControlOperator,
GrantControls,
PersistentBrowser,
SessionControls,
SignInFrequency,
SignInFrequencyInterval,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
CHECK_MODULE = (
"prowler.providers.m365.services.entra."
"entra_conditional_access_policy_mdm_compliant_device_required."
"entra_conditional_access_policy_mdm_compliant_device_required"
)
def build_policy(
*,
included_users=None,
excluded_users=None,
included_applications=None,
excluded_applications=None,
built_in_controls=None,
operator=GrantControlOperator.OR,
authentication_strength=None,
state=ConditionalAccessPolicyState.ENABLED,
display_name="Test",
):
policy_id = str(uuid4())
return ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=included_applications or ["All"],
excluded_applications=excluded_applications or [],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=included_users or ["All"],
excluded_users=excluded_users or [],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=built_in_controls
or [ConditionalAccessGrantControl.COMPLIANT_DEVICE],
operator=operator,
authentication_strength=authentication_strength,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(is_enabled=False, mode="always"),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
application_enforced_restrictions=ApplicationEnforcedRestrictions(
is_enabled=False
),
),
state=state,
)
def build_intune_client(
*,
verification_error=None,
secure_by_default=True,
assignment_counts=None,
managed_devices=None,
):
assignment_counts = assignment_counts if assignment_counts is not None else [1]
return SimpleNamespace(
verification_error=verification_error,
settings=SimpleNamespace(secure_by_default=secure_by_default),
compliance_policies=[
SimpleNamespace(
id=str(uuid4()),
display_name=f"Compliance Policy {index}",
assignment_count=assignment_count,
)
for index, assignment_count in enumerate(assignment_counts, start=1)
],
managed_devices=(
managed_devices
if managed_devices is not None
else [
SimpleNamespace(
id=str(uuid4()),
device_name="Managed Device 1",
compliance_state="compliant",
management_agent="mdm",
)
]
),
)
class Test_entra_conditional_access_policy_mdm_compliant_device_required:
def _run_check(self, conditional_access_policies, intune_client):
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
entra_client.conditional_access_policies = conditional_access_policies
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE}.entra_client", new=entra_client),
mock.patch(f"{CHECK_MODULE}.intune_client", new=intune_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mdm_compliant_device_required.entra_conditional_access_policy_mdm_compliant_device_required import (
entra_conditional_access_policy_mdm_compliant_device_required,
)
check = entra_conditional_access_policy_mdm_compliant_device_required()
return check.execute()
def test_no_conditional_access_policies(self):
result = self._run_check({}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_reporting_only_policy_fails(self):
policy = build_policy(state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING)
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' reports the requirement of an MDM-compliant device for all cloud app access but does not enforce it."
)
assert result[0].resource == policy.dict()
assert result[0].resource_name == policy.display_name
assert result[0].resource_id == policy.id
assert result[0].location == "global"
def test_specific_users_policy_fails(self):
policy = build_policy(included_users=["specific-user-id"])
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
def test_policy_with_excluded_users_passes(self):
policy = build_policy(excluded_users=["break-glass-id"])
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, and Microsoft Intune is configured with assigned compliance policies, secure-by-default compliance evaluation, and at least one compliant MDM-managed device."
)
assert result[0].resource == policy.dict()
def test_policy_with_excluded_applications_fails(self):
policy = build_policy(excluded_applications=["office-app-id"])
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
def test_policy_with_or_mfa_fails(self):
policy = build_policy(
built_in_controls=[
ConditionalAccessGrantControl.COMPLIANT_DEVICE,
ConditionalAccessGrantControl.MFA,
],
operator=GrantControlOperator.OR,
)
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
def test_policy_with_or_authentication_strength_fails(self):
policy = build_policy(
operator=GrantControlOperator.OR,
authentication_strength="Phishing-resistant MFA",
)
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
def test_intune_verification_error_returns_manual(self):
policy = build_policy()
intune_client = build_intune_client(
verification_error=(
"Could not read Microsoft Intune device management settings. "
"Ensure the Service Principal has DeviceManagementServiceConfig.Read.All permission granted."
)
)
result = self._run_check({policy.id: policy}, intune_client)
assert len(result) == 1
assert result[0].status == "MANUAL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, but Microsoft Intune MDM compliance prerequisites could not be verified. {intune_client.verification_error}"
)
assert result[0].resource == policy.dict()
def test_no_intune_compliance_policies_fails(self):
policy = build_policy()
intune_client = build_intune_client()
intune_client.compliance_policies = []
result = self._run_check({policy.id: policy}, intune_client)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, but no Microsoft Intune device compliance policies are configured."
)
assert result[0].resource == policy.dict()
def test_unassigned_intune_compliance_policies_fail(self):
policy = build_policy()
result = self._run_check(
{policy.id: policy},
build_intune_client(assignment_counts=[0, 0]),
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, but no Microsoft Intune device compliance policy is assigned."
)
assert result[0].resource == policy.dict()
def test_secure_by_default_disabled_fails(self):
policy = build_policy()
result = self._run_check(
{policy.id: policy},
build_intune_client(secure_by_default=False),
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, but Microsoft Intune allows devices without an assigned compliance policy to remain compliant."
)
assert result[0].resource == policy.dict()
def test_missing_secure_by_default_does_not_block_pass(self):
policy = build_policy()
result = self._run_check(
{policy.id: policy},
build_intune_client(secure_by_default=None),
)
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, and Microsoft Intune is configured with assigned compliance policies and at least one compliant MDM-managed device. Microsoft Graph did not return device management settings, so secure-by-default compliance evaluation could not be verified."
)
assert result[0].resource == policy.dict()
def test_no_compliant_mdm_managed_devices_fails(self):
policy = build_policy()
result = self._run_check(
{policy.id: policy},
build_intune_client(managed_devices=[]),
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, but Microsoft Intune does not currently report any compliant MDM-managed devices."
)
assert result[0].resource == policy.dict()
def test_non_mdm_compliant_devices_do_not_pass(self):
policy = build_policy()
result = self._run_check(
{policy.id: policy},
build_intune_client(
managed_devices=[
SimpleNamespace(
id=str(uuid4()),
device_name="EAS Device",
compliance_state="compliant",
management_agent="eas",
)
]
),
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, but Microsoft Intune does not currently report any compliant MDM-managed devices."
)
assert result[0].resource == policy.dict()
def test_disabled_policy_is_ignored(self):
"""Disabled policy is properly ignored, resulting in generic FAIL."""
policy = build_policy(state=ConditionalAccessPolicyState.DISABLED)
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
def test_policy_without_compliant_device_grant_is_skipped(self):
"""Policy without COMPLIANT_DEVICE grant control is skipped."""
policy = build_policy(
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
def test_policy_targeting_specific_apps_is_skipped(self):
"""Policy targeting specific apps instead of All is skipped."""
policy = build_policy(included_applications=["specific-app-id"])
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires an MDM-compliant device for all cloud app access."
)
def test_noncompliant_mdm_device_does_not_count(self):
"""MDM-managed device with compliance_state='noncompliant' doesn't count as compliant."""
policy = build_policy()
result = self._run_check(
{policy.id: policy},
build_intune_client(
managed_devices=[
SimpleNamespace(
id=str(uuid4()),
device_name="Noncompliant MDM Device",
compliance_state="noncompliant",
management_agent="mdm",
)
]
),
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, but Microsoft Intune does not currently report any compliant MDM-managed devices."
)
assert result[0].resource == policy.dict()
def test_reporting_policy_ignored_when_enabled_policy_exists(self):
"""Report-only policy is ignored when a valid enabled policy also exists."""
reporting_policy = build_policy(
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
display_name="Reporting Policy",
)
enabled_policy = build_policy(
state=ConditionalAccessPolicyState.ENABLED,
display_name="Enabled Policy",
)
result = self._run_check(
{reporting_policy.id: reporting_policy, enabled_policy.id: enabled_policy},
build_intune_client(),
)
assert len(result) == 1
assert result[0].status == "PASS"
assert "Enabled Policy" in result[0].status_extended
assert result[0].resource == enabled_policy.dict()
def test_mixed_assigned_unassigned_compliance_policies_pass(self):
"""Mixed assigned/unassigned compliance policies (e.g. [0, 1]) still pass."""
policy = build_policy()
result = self._run_check(
{policy.id: policy},
build_intune_client(assignment_counts=[0, 1]),
)
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource == policy.dict()
def test_enabled_policy_with_intune_prerequisites_passes(self):
policy = build_policy(
built_in_controls=[
ConditionalAccessGrantControl.COMPLIANT_DEVICE,
ConditionalAccessGrantControl.MFA,
],
operator=GrantControlOperator.AND,
)
result = self._run_check({policy.id: policy}, build_intune_client())
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{policy.display_name}' requires an MDM-compliant device for all cloud app access, and Microsoft Intune is configured with assigned compliance policies, secure-by-default compliance evaluation, and at least one compliant MDM-managed device."
)
assert result[0].resource == policy.dict()
assert result[0].resource_name == policy.display_name
assert result[0].resource_id == policy.id
assert result[0].location == "global"
@@ -0,0 +1,369 @@
import asyncio
from types import SimpleNamespace
from unittest import mock
from unittest.mock import AsyncMock, patch
from prowler.providers.m365.services.intune.intune_service import (
Intune,
IntuneCompliancePolicy,
IntuneManagedDevice,
IntuneSettings,
)
from tests.providers.m365.m365_fixtures import set_mocked_m365_provider
# --- Mock async helpers for patching Intune methods ---
async def mock_get_settings_with_secure_by_default(_):
return IntuneSettings(secure_by_default=True), None
async def mock_get_settings_null(_):
return IntuneSettings(secure_by_default=None), None
async def mock_get_settings_error(_):
return (
None,
"Could not read Microsoft Intune device management settings. Ensure the Service Principal has DeviceManagementServiceConfig.Read.All permission granted.",
)
async def mock_get_compliance_policies_with_assignments(_):
return [
IntuneCompliancePolicy(
id="policy-1", display_name="Windows Policy", assignment_count=2
),
IntuneCompliancePolicy(
id="policy-2", display_name="iOS Policy", assignment_count=0
),
], None
async def mock_get_compliance_policies_empty(_):
return [], None
async def mock_get_compliance_policies_error(_):
return (
None,
"Could not read Microsoft Intune device compliance policies. Ensure the Service Principal has DeviceManagementConfiguration.Read.All permission granted.",
)
async def mock_get_managed_devices_with_compliant(_):
return [
IntuneManagedDevice(
id="device-1",
device_name="Laptop-1",
compliance_state="compliant",
management_agent="mdm",
),
], None
async def mock_get_managed_devices_empty(_):
return [], None
async def mock_get_managed_devices_error(_):
return (
None,
"Could not read Microsoft Intune managed devices. Ensure the Service Principal has DeviceManagementManagedDevices.Read.All permission granted.",
)
def _build_intune_service(
get_settings_mock=mock_get_settings_with_secure_by_default,
get_compliance_policies_mock=mock_get_compliance_policies_with_assignments,
get_managed_devices_mock=mock_get_managed_devices_with_compliant,
):
"""Instantiate Intune with patched async methods."""
with (
patch(
"prowler.providers.m365.services.intune.intune_service.Intune._get_settings",
new=get_settings_mock,
),
patch(
"prowler.providers.m365.services.intune.intune_service.Intune._get_compliance_policies",
new=get_compliance_policies_mock,
),
patch(
"prowler.providers.m365.services.intune.intune_service.Intune._get_managed_devices",
new=get_managed_devices_mock,
),
):
return Intune(set_mocked_m365_provider())
class Test_Intune_Service:
def test_get_settings_secure_by_default_true(self):
intune = _build_intune_service()
assert intune.settings is not None
assert intune.settings.secure_by_default is True
assert intune.verification_error is None
def test_get_settings_null(self):
intune = _build_intune_service(
get_settings_mock=mock_get_settings_null,
)
assert intune.settings is not None
assert intune.settings.secure_by_default is None
assert intune.verification_error is None
def test_get_settings_error(self):
intune = _build_intune_service(
get_settings_mock=mock_get_settings_error,
)
assert intune.settings is None
assert intune.verification_error is not None
assert "DeviceManagementServiceConfig.Read.All" in intune.verification_error
def test_get_compliance_policies(self):
intune = _build_intune_service()
assert intune.compliance_policies is not None
assert len(intune.compliance_policies) == 2
assert intune.compliance_policies[0].id == "policy-1"
assert intune.compliance_policies[0].display_name == "Windows Policy"
assert intune.compliance_policies[0].assignment_count == 2
assert intune.compliance_policies[1].assignment_count == 0
def test_get_compliance_policies_empty(self):
intune = _build_intune_service(
get_compliance_policies_mock=mock_get_compliance_policies_empty,
)
assert intune.compliance_policies == []
assert intune.verification_error is None
def test_get_compliance_policies_error(self):
intune = _build_intune_service(
get_compliance_policies_mock=mock_get_compliance_policies_error,
)
assert intune.compliance_policies is None
assert intune.verification_error is not None
assert "DeviceManagementConfiguration.Read.All" in intune.verification_error
def test_get_managed_devices(self):
intune = _build_intune_service()
assert intune.managed_devices is not None
assert len(intune.managed_devices) == 1
assert intune.managed_devices[0].id == "device-1"
assert intune.managed_devices[0].device_name == "Laptop-1"
assert intune.managed_devices[0].compliance_state == "compliant"
assert intune.managed_devices[0].management_agent == "mdm"
def test_get_managed_devices_empty(self):
intune = _build_intune_service(
get_managed_devices_mock=mock_get_managed_devices_empty,
)
assert intune.managed_devices == []
assert intune.verification_error is None
def test_get_managed_devices_error(self):
intune = _build_intune_service(
get_managed_devices_mock=mock_get_managed_devices_error,
)
assert intune.managed_devices is None
assert intune.verification_error is not None
assert "DeviceManagementManagedDevices.Read.All" in intune.verification_error
def test_multiple_errors_concatenated(self):
intune = _build_intune_service(
get_settings_mock=mock_get_settings_error,
get_compliance_policies_mock=mock_get_compliance_policies_error,
)
assert intune.verification_error is not None
assert "DeviceManagementServiceConfig.Read.All" in intune.verification_error
assert "DeviceManagementConfiguration.Read.All" in intune.verification_error
def test_is_mdm_managed_device_true(self):
for agent in [
"mdm",
"easMdm",
"intuneClient",
"easIntuneClient",
"configurationManagerClientMdm",
"configurationManagerClientMdmEas",
"microsoft365ManagedMdm",
]:
assert Intune.is_mdm_managed_device(agent) is True
def test_is_mdm_managed_device_false(self):
for agent in ["eas", "googleCloudDevicePolicyController", "", "unknown"]:
assert Intune.is_mdm_managed_device(agent) is False
def test_intune_get_compliance_policies_pagination():
"""Test that _get_compliance_policies handles pagination correctly."""
intune = Intune.__new__(Intune)
policy_page_one = [
SimpleNamespace(id="policy-1", display_name="Policy 1"),
]
policy_page_two = [
SimpleNamespace(id="policy-2", display_name="Policy 2"),
]
response_page_one = SimpleNamespace(
value=policy_page_one,
odata_next_link="next-link",
)
response_page_two = SimpleNamespace(
value=policy_page_two,
odata_next_link=None,
)
assignments_response = SimpleNamespace(
value=[SimpleNamespace()],
odata_next_link=None,
)
mock_client = mock.MagicMock()
mock_policies = mock_client.device_management.device_compliance_policies
mock_policies.get = AsyncMock(return_value=response_page_one)
mock_policies.with_url.return_value.get = AsyncMock(return_value=response_page_two)
mock_policies.by_device_compliance_policy_id.return_value.assignments.get = (
AsyncMock(return_value=assignments_response)
)
intune.client = mock_client
loop = asyncio.new_event_loop()
try:
policies, error = loop.run_until_complete(intune._get_compliance_policies())
finally:
loop.close()
assert error is None
assert len(policies) == 2
assert policies[0].id == "policy-1"
assert policies[1].id == "policy-2"
assert policies[0].assignment_count == 1
assert policies[1].assignment_count == 1
def test_intune_get_managed_devices_pagination():
"""Test that _get_managed_devices handles pagination correctly."""
intune = Intune.__new__(Intune)
device_page_one = [
SimpleNamespace(
id="device-1",
device_name="Laptop-1",
compliance_state="compliant",
management_agent="mdm",
),
]
device_page_two = [
SimpleNamespace(
id="device-2",
device_name="Laptop-2",
compliance_state="noncompliant",
management_agent="eas",
),
]
response_page_one = SimpleNamespace(
value=device_page_one,
odata_next_link="next-link",
)
response_page_two = SimpleNamespace(
value=device_page_two,
odata_next_link=None,
)
mock_client = mock.MagicMock()
mock_managed_devices = mock_client.device_management.managed_devices
mock_managed_devices.get = AsyncMock(return_value=response_page_one)
mock_managed_devices.with_url.return_value.get = AsyncMock(
return_value=response_page_two
)
intune.client = mock_client
loop = asyncio.new_event_loop()
try:
devices, error = loop.run_until_complete(intune._get_managed_devices())
finally:
loop.close()
assert error is None
assert len(devices) == 2
assert devices[0].id == "device-1"
assert devices[0].compliance_state == "compliant"
assert devices[0].management_agent == "mdm"
assert devices[1].id == "device-2"
assert devices[1].compliance_state == "noncompliant"
assert devices[1].management_agent == "eas"
def test_intune_get_settings_with_secure_by_default():
"""Test _get_settings when Graph returns settings with secure_by_default."""
intune = Intune.__new__(Intune)
device_management_response = SimpleNamespace(
settings=SimpleNamespace(secure_by_default=True)
)
mock_client = mock.MagicMock()
mock_client.device_management.get = AsyncMock(
return_value=device_management_response
)
intune.client = mock_client
loop = asyncio.new_event_loop()
try:
settings, error = loop.run_until_complete(intune._get_settings())
finally:
loop.close()
assert error is None
assert settings is not None
assert settings.secure_by_default is True
def test_intune_get_settings_null_settings():
"""Test _get_settings when Graph returns settings = None."""
intune = Intune.__new__(Intune)
device_management_response = SimpleNamespace(settings=None)
mock_client = mock.MagicMock()
mock_client.device_management.get = AsyncMock(
return_value=device_management_response
)
intune.client = mock_client
loop = asyncio.new_event_loop()
try:
settings, error = loop.run_until_complete(intune._get_settings())
finally:
loop.close()
assert error is None
assert settings is not None
assert settings.secure_by_default is None
def test_intune_get_settings_exception():
"""Test _get_settings handles exceptions gracefully."""
intune = Intune.__new__(Intune)
mock_client = mock.MagicMock()
mock_client.device_management.get = AsyncMock(side_effect=Exception("API Error"))
intune.client = mock_client
loop = asyncio.new_event_loop()
try:
settings, error = loop.run_until_complete(intune._get_settings())
finally:
loop.close()
assert settings is None
assert error is not None
assert "DeviceManagementServiceConfig.Read.All" in error