mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
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:
co-authored by
Daniel Barranquero
parent
dd00d71a07
commit
c651f60e3a
+485
@@ -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
|
||||
Reference in New Issue
Block a user