From 6935c4eb1b7e27967d03ea381a5ef29ee6715b5b Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:53:35 +0100 Subject: [PATCH] feat(m365): add entra_app_enforced_restrictions security check (#10058) --- prowler/CHANGELOG.md | 1 + .../compliance/m365/iso27001_2022_m365.json | 26 +- .../m365/prowler_threatscore_m365.json | 1 + .../__init__.py | 0 ...ra_app_enforced_restrictions.metadata.json | 39 + .../entra_app_enforced_restrictions.py | 108 ++ .../entra_emergency_access_exclusion.py | 39 +- .../m365/services/entra/entra_service.py | 17 + ...a_admin_portals_access_restriction_test.py | 10 + .../entra_admin_users_mfa_enabled_test.py | 25 + ...ers_phishing_resistant_mfa_enabled_test.py | 10 + ...in_users_sign_in_frequency_enabled_test.py | 19 + .../entra_app_enforced_restrictions_test.py | 1032 +++++++++++++++++ .../entra_emergency_access_exclusion_test.py | 90 +- ...ty_protection_sign_in_risk_enabled_test.py | 13 + ...ntity_protection_user_risk_enabled_test.py | 13 + ...lment_sign_in_frequency_every_time_test.py | 13 + ...ntra_legacy_authentication_blocked_test.py | 10 + ...device_required_for_authentication_test.py | 4 + ...vice_required_for_mfa_registration_test.py | 10 + .../entra_users_mfa_enabled_test.py | 10 + .../entra/microsoft365_entra_service_test.py | 7 + 22 files changed, 1418 insertions(+), 79 deletions(-) create mode 100644 prowler/providers/m365/services/entra/entra_app_enforced_restrictions/__init__.py create mode 100644 prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.metadata.json create mode 100644 prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.py create mode 100644 tests/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 4b2cef24ab..a5aecdf9df 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🚀 Added +- `entra_app_enforced_restrictions` check for M365 provider [(#10058)](https://github.com/prowler-cloud/prowler/pull/10058) - `entra_app_registration_no_unused_privileged_permissions` check for m365 provider [(#10080)](https://github.com/prowler-cloud/prowler/pull/10080) - `defenderidentity_health_issues_no_open` check for M365 provider [(#10087)](https://github.com/prowler-cloud/prowler/pull/10087) - `organization_verified_badge` check for GitHub provider [(#10033)](https://github.com/prowler-cloud/prowler/pull/10033) diff --git a/prowler/compliance/m365/iso27001_2022_m365.json b/prowler/compliance/m365/iso27001_2022_m365.json index ac9c677f39..023dbdb230 100644 --- a/prowler/compliance/m365/iso27001_2022_m365.json +++ b/prowler/compliance/m365/iso27001_2022_m365.json @@ -174,16 +174,17 @@ } ], "Checks": [ - "teams_external_file_sharing_restricted", + "entra_app_enforced_restrictions", + "exchange_transport_config_smtp_auth_disabled", + "exchange_transport_rules_mail_forwarding_disabled", + "exchange_transport_rules_whitelist_disabled", "sharepoint_external_sharing_managed", "sharepoint_external_sharing_restricted", "sharepoint_guest_sharing_restricted", "sharepoint_modern_authentication_required", "sharepoint_onedrive_sync_restricted_unmanaged_devices", "teams_external_file_sharing_restricted", - "exchange_transport_config_smtp_auth_disabled", - "exchange_transport_rules_mail_forwarding_disabled", - "exchange_transport_rules_whitelist_disabled" + "teams_external_file_sharing_restricted" ] }, { @@ -612,11 +613,12 @@ ], "Checks": [ "defenderxdr_endpoint_privileged_user_exposed_credentials", - "entra_managed_device_required_for_authentication", - "entra_users_mfa_enabled", - "entra_managed_device_required_for_mfa_registration", "entra_admin_users_phishing_resistant_mfa_enabled", - "entra_users_mfa_capable" + "entra_app_enforced_restrictions", + "entra_managed_device_required_for_authentication", + "entra_managed_device_required_for_mfa_registration", + "entra_users_mfa_capable", + "entra_users_mfa_enabled" ] }, { @@ -665,9 +667,10 @@ } ], "Checks": [ - "sharepoint_external_sharing_restricted", "entra_admin_portals_access_restriction", - "entra_policy_guest_users_access_restrictions" + "entra_app_enforced_restrictions", + "entra_policy_guest_users_access_restrictions", + "sharepoint_external_sharing_restricted" ] }, { @@ -749,7 +752,8 @@ "Checks": [ "defender_antiphishing_policy_configured", "defender_safelinks_policy_enabled", - "entra_admin_users_phishing_resistant_mfa_enabled" + "entra_admin_users_phishing_resistant_mfa_enabled", + "entra_app_enforced_restrictions" ] }, { diff --git a/prowler/compliance/m365/prowler_threatscore_m365.json b/prowler/compliance/m365/prowler_threatscore_m365.json index 512c401c8c..93ced15f1b 100644 --- a/prowler/compliance/m365/prowler_threatscore_m365.json +++ b/prowler/compliance/m365/prowler_threatscore_m365.json @@ -823,6 +823,7 @@ "Id": "1.3.9", "Description": "Ensure OneDrive sync is restricted for unmanaged devices", "Checks": [ + "entra_app_enforced_restrictions", "sharepoint_onedrive_sync_restricted_unmanaged_devices" ], "Attributes": [ diff --git a/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/__init__.py b/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.metadata.json b/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.metadata.json new file mode 100644 index 0000000000..eb25b88d4f --- /dev/null +++ b/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "m365", + "CheckID": "entra_app_enforced_restrictions", + "CheckTitle": "Conditional Access policy enforces application restrictions for unmanaged devices", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Conditional Access Policy", + "ResourceGroup": "IAM", + "Description": "Conditional Access policy with **application enforced restrictions** limits access to SharePoint, OneDrive, and Exchange content from unmanaged devices.\n\nThis control helps prevent data exfiltration by restricting download, print, and sync capabilities on devices that are not managed by the organization.", + "Risk": "Without application enforced restrictions, users accessing SharePoint, OneDrive, and Exchange from unmanaged devices can:\n\n- **Download** sensitive files to personal devices\n- **Print** confidential documents\n- **Sync** corporate data to uncontrolled locations\n\nThis increases the risk of data leakage and unauthorized access to sensitive information.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-policy-app-enforced-restriction", + "https://learn.microsoft.com/en-us/sharepoint/control-access-from-unmanaged-devices" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "1. Navigate to the Microsoft Entra admin center https://entra.microsoft.com.\n2. Expand **Protection** > **Conditional Access** and select **Policies**.\n3. Click **New policy**.\n4. Under **Users**, select **All users**.\n5. Under **Target resources**, select **Office 365** from the cloud apps.\n6. Under **Conditions** > **Client apps**, select **All client apps**.\n7. Under **Session**, check **Use app enforced restrictions**.\n8. Set the policy to **On** and click **Create**.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Configure Conditional Access policies with **application enforced restrictions** to control access from unmanaged devices. Apply this to Office 365 applications (SharePoint, OneDrive, Exchange) to limit download, print, and sync operations.\n\nCombine with SharePoint access control settings for comprehensive protection.", + "Url": "https://hub.prowler.com/check/entra_app_enforced_restrictions" + } + }, + "Categories": [ + "e3" + ], + "DependsOn": [], + "RelatedTo": [ + "entra_managed_device_required_for_authentication" + ], + "Notes": "Application enforced restrictions only work with Exchange Online and SharePoint Online (including OneDrive)." +} diff --git a/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.py b/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.py new file mode 100644 index 0000000000..b49e4e4097 --- /dev/null +++ b/prowler/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions.py @@ -0,0 +1,108 @@ +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.entra.entra_client import entra_client +from prowler.providers.m365.services.entra.entra_service import ( + ClientAppType, + ConditionalAccessPolicyState, +) + + +class entra_app_enforced_restrictions(Check): + """Check if at least one Conditional Access policy enforces application restrictions. + + This check verifies that the tenant has at least one enabled Conditional Access policy + with application enforced restrictions to protect SharePoint, OneDrive, and Exchange + from unmanaged devices. + + - PASS: At least one policy is enabled with application enforced restrictions targeting + all users, all client app types, and either the Office365 suite or + SharePoint Online and Exchange Online individually. + - FAIL: No policy meets the criteria for application enforced restrictions. + """ + + # SharePoint Online / OneDrive for Business + SHAREPOINT_APP_ID = "00000003-0000-0ff1-ce00-000000000000" + # Exchange Online + EXCHANGE_APP_ID = "00000002-0000-0ff1-ce00-000000000000" + # Office 365 suite (includes SharePoint, OneDrive, and Exchange) + OFFICE365_APP_ID = "Office365" + + REQUIRED_APPS = {SHAREPOINT_APP_ID, EXCHANGE_APP_ID} + MODERN_CLIENT_APP_TYPES = { + ClientAppType.BROWSER, + ClientAppType.MOBILE_APPS_AND_DESKTOP_CLIENTS, + } + + def _targets_all_client_apps(self, client_app_types: list[ClientAppType]) -> bool: + """Check if the policy targets all modern client app types. + + Returns True if the policy includes ALL explicitly or both + Browser and Mobile apps and desktop clients. + """ + client_app_set = set(client_app_types) + if ClientAppType.ALL in client_app_set: + return True + return self.MODERN_CLIENT_APP_TYPES.issubset(client_app_set) + + def _targets_required_apps(self, included_applications: list[str]) -> bool: + """Check if the policy targets the required applications. + + Returns True if the policy includes Office365 (the suite) or both + SharePoint Online and Exchange Online individually. + """ + if self.OFFICE365_APP_ID in included_applications: + return True + return self.REQUIRED_APPS.issubset(set(included_applications)) + + def execute(self) -> list[CheckReportM365]: + """Execute the check for application enforced restrictions in Conditional Access policies. + + Returns: + list[CheckReportM365]: A list containing the result of the check. + """ + findings = [] + report = CheckReportM365( + metadata=self.metadata(), + resource={}, + resource_name="Conditional Access Policies", + resource_id="conditionalAccessPolicies", + ) + report.status = "FAIL" + report.status_extended = "No Conditional Access Policy enforces application restrictions for unmanaged devices." + + for policy in entra_client.conditional_access_policies.values(): + if policy.state == ConditionalAccessPolicyState.DISABLED: + continue + + if "All" not in policy.conditions.user_conditions.included_users: + continue + + if not self._targets_all_client_apps(policy.conditions.client_app_types): + continue + + if not self._targets_required_apps( + policy.conditions.application_conditions.included_applications + ): + continue + + if ( + not policy.session_controls.application_enforced_restrictions + or not policy.session_controls.application_enforced_restrictions.is_enabled + ): + continue + + report = CheckReportM365( + metadata=self.metadata(), + resource=policy, + resource_name=policy.display_name, + resource_id=policy.id, + ) + if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING: + report.status = "FAIL" + report.status_extended = f"Conditional Access Policy {policy.display_name} reports application enforced restrictions but does not enforce them." + else: + report.status = "PASS" + report.status_extended = f"Conditional Access Policy {policy.display_name} enforces application restrictions for unmanaged devices." + break + + findings.append(report) + return findings diff --git a/prowler/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion.py b/prowler/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion.py index 9a44d4a62c..1104380292 100644 --- a/prowler/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion.py +++ b/prowler/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion.py @@ -84,28 +84,25 @@ class entra_emergency_access_exclusion(Check): users_excluded_from_all or groups_excluded_from_all ) - for policy in enabled_policies: - report = CheckReportM365( - metadata=self.metadata(), - resource=policy, - resource_name=policy.display_name, - resource_id=policy.id, - ) + report = CheckReportM365( + metadata=self.metadata(), + resource={}, + resource_name="Conditional Access Policies", + resource_id="conditionalAccessPolicies", + ) - if has_emergency_exclusion: - report.status = "PASS" - exclusion_details = [] - if users_excluded_from_all: - exclusion_details.append(f"{len(users_excluded_from_all)} user(s)") - if groups_excluded_from_all: - exclusion_details.append( - f"{len(groups_excluded_from_all)} group(s)" - ) - report.status_extended = f"Conditional Access Policy '{policy.display_name}' has {' and '.join(exclusion_details)} excluded as emergency access across all {total_policy_count} enabled policies." - else: - report.status = "FAIL" - report.status_extended = f"Conditional Access Policy '{policy.display_name}' does not have any user or group excluded as emergency access from all enabled Conditional Access policies." + if has_emergency_exclusion: + report.status = "PASS" + exclusion_details = [] + if users_excluded_from_all: + exclusion_details.append(f"{len(users_excluded_from_all)} user(s)") + if groups_excluded_from_all: + exclusion_details.append(f"{len(groups_excluded_from_all)} group(s)") + report.status_extended = f"{' and '.join(exclusion_details)} excluded as emergency access across all {total_policy_count} enabled Conditional Access policies." + else: + report.status = "FAIL" + report.status_extended = f"No user or group is excluded as emergency access from all {total_policy_count} enabled Conditional Access policies." - findings.append(report) + findings.append(report) return findings diff --git a/prowler/providers/m365/services/entra/entra_service.py b/prowler/providers/m365/services/entra/entra_service.py index fa4a887481..dfd91169e7 100644 --- a/prowler/providers/m365/services/entra/entra_service.py +++ b/prowler/providers/m365/services/entra/entra_service.py @@ -341,6 +341,14 @@ class Entra(M365Service): else None ), ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=( + policy.session_controls.application_enforced_restrictions.is_enabled + if policy.session_controls + and policy.session_controls.application_enforced_restrictions + else False + ), + ), ), state=ConditionalAccessPolicyState( getattr(policy, "state", "disabled") @@ -735,9 +743,18 @@ class SignInFrequency(BaseModel): interval: Optional[SignInFrequencyInterval] +class ApplicationEnforcedRestrictions(BaseModel): + """Model representing application enforced restrictions session control.""" + + is_enabled: bool = False + + class SessionControls(BaseModel): + """Model representing session controls for Conditional Access policies.""" + persistent_browser: PersistentBrowser sign_in_frequency: SignInFrequency + application_enforced_restrictions: Optional[ApplicationEnforcedRestrictions] = None class ConditionalAccessGrantControl(Enum): diff --git a/tests/providers/m365/services/entra/entra_admin_portals_access_restriction/entra_admin_portals_access_restriction_test.py b/tests/providers/m365/services/entra/entra_admin_portals_access_restriction/entra_admin_portals_access_restriction_test.py index 1226f07261..7c1c9757a7 100644 --- a/tests/providers/m365/services/entra/entra_admin_portals_access_restriction/entra_admin_portals_access_restriction_test.py +++ b/tests/providers/m365/services/entra/entra_admin_portals_access_restriction/entra_admin_portals_access_restriction_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicyState, @@ -106,6 +107,9 @@ class Test_entra_admin_portals_access_restriction: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -181,6 +185,9 @@ class Test_entra_admin_portals_access_restriction: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -259,6 +266,9 @@ class Test_entra_admin_portals_access_restriction: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_admin_users_mfa_enabled/entra_admin_users_mfa_enabled_test.py b/tests/providers/m365/services/entra/entra_admin_users_mfa_enabled/entra_admin_users_mfa_enabled_test.py index b25d48dd2c..250e10561c 100644 --- a/tests/providers/m365/services/entra/entra_admin_users_mfa_enabled/entra_admin_users_mfa_enabled_test.py +++ b/tests/providers/m365/services/entra/entra_admin_users_mfa_enabled/entra_admin_users_mfa_enabled_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicy, @@ -108,6 +109,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -184,6 +188,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -260,6 +267,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -341,6 +351,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -436,6 +449,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -530,6 +546,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -626,6 +645,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ), @@ -677,6 +699,9 @@ class Test_entra_admin_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ), diff --git a/tests/providers/m365/services/entra/entra_admin_users_phishing_resistant_mfa_enabled/entra_admin_users_phishing_resistant_mfa_enabled_test.py b/tests/providers/m365/services/entra/entra_admin_users_phishing_resistant_mfa_enabled/entra_admin_users_phishing_resistant_mfa_enabled_test.py index b7ac4c2d12..824e519417 100644 --- a/tests/providers/m365/services/entra/entra_admin_users_phishing_resistant_mfa_enabled/entra_admin_users_phishing_resistant_mfa_enabled_test.py +++ b/tests/providers/m365/services/entra/entra_admin_users_phishing_resistant_mfa_enabled/entra_admin_users_phishing_resistant_mfa_enabled_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicyState, @@ -125,6 +126,9 @@ class Test_entra_admin_users_phishing_resistant_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -217,6 +221,9 @@ class Test_entra_admin_users_phishing_resistant_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -312,6 +319,9 @@ class Test_entra_admin_users_phishing_resistant_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_admin_users_sign_in_frequency_enabled/entra_admin_users_sign_in_frequency_enabled_test.py b/tests/providers/m365/services/entra/entra_admin_users_sign_in_frequency_enabled/entra_admin_users_sign_in_frequency_enabled_test.py index ecfbf2b771..d023b8fbec 100644 --- a/tests/providers/m365/services/entra/entra_admin_users_sign_in_frequency_enabled/entra_admin_users_sign_in_frequency_enabled_test.py +++ b/tests/providers/m365/services/entra/entra_admin_users_sign_in_frequency_enabled/entra_admin_users_sign_in_frequency_enabled_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessPolicyState, Conditions, @@ -108,6 +109,9 @@ class Test_entra_admin_users_sign_in_frequency_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -200,6 +204,9 @@ class Test_entra_admin_users_sign_in_frequency_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -298,6 +305,9 @@ class Test_entra_admin_users_sign_in_frequency_enabled: type=SignInFrequencyType.HOURS, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -393,6 +403,9 @@ class Test_entra_admin_users_sign_in_frequency_enabled: type=SignInFrequencyType.HOURS, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -488,6 +501,9 @@ class Test_entra_admin_users_sign_in_frequency_enabled: type=SignInFrequencyType.HOURS, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -586,6 +602,9 @@ class Test_entra_admin_users_sign_in_frequency_enabled: type=SignInFrequencyType.DAYS, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions_test.py b/tests/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions_test.py new file mode 100644 index 0000000000..5216f84ccd --- /dev/null +++ b/tests/providers/m365/services/entra/entra_app_enforced_restrictions/entra_app_enforced_restrictions_test.py @@ -0,0 +1,1032 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, + ApplicationsConditions, + ClientAppType, + ConditionalAccessGrantControl, + ConditionalAccessPolicyState, + Conditions, + GrantControlOperator, + GrantControls, + PersistentBrowser, + SessionControls, + SignInFrequency, + SignInFrequencyInterval, + UsersConditions, +) +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_entra_app_enforced_restrictions: + def test_entra_no_conditional_access_policies(self): + """Test FAIL when no conditional access policies exist.""" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + + entra_client.conditional_access_policies = {} + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "No Conditional Access Policy enforces application restrictions for unmanaged devices." + ) + 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_entra_app_enforced_restrictions_policy_disabled(self): + """Test FAIL when policy with app enforced restrictions is disabled.""" + id = str(uuid4()) + display_name = "App Enforced Restrictions Policy" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.DISABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "No Conditional Access Policy enforces application restrictions for unmanaged devices." + ) + 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_entra_app_enforced_restrictions_enabled_for_reporting(self): + """Test FAIL when policy is enabled for reporting but not enforcing.""" + id = str(uuid4()) + display_name = "App Enforced Restrictions Reporting" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Conditional Access Policy {display_name} reports application enforced restrictions but does not enforce them." + ) + assert ( + result[0].resource + == entra_client.conditional_access_policies[id].dict() + ) + assert result[0].resource_name == display_name + assert result[0].resource_id == id + assert result[0].location == "global" + + def test_entra_app_enforced_restrictions_not_enabled(self): + """Test FAIL when policy exists but app enforced restrictions is not enabled.""" + id = str(uuid4()) + display_name = "Policy Without App Restrictions" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "No Conditional Access Policy enforces application restrictions for unmanaged devices." + ) + 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_entra_app_enforced_restrictions_missing_all_users(self): + """Test FAIL when policy does not include all users.""" + id = str(uuid4()) + display_name = "Policy Missing All Users" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=["some-group-id"], + excluded_groups=[], + included_users=[], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "No Conditional Access Policy enforces application restrictions for unmanaged devices." + ) + 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_entra_app_enforced_restrictions_missing_all_client_apps(self): + """Test FAIL when policy does not include all client app types.""" + id = str(uuid4()) + display_name = "Policy Missing All Client Apps" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.BROWSER], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "No Conditional Access Policy enforces application restrictions for unmanaged devices." + ) + 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_entra_app_enforced_restrictions_missing_required_apps(self): + """Test FAIL when policy does not include Office365 or the required individual apps.""" + id = str(uuid4()) + display_name = "Policy Missing Required Apps" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["All"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "No Conditional Access Policy enforces application restrictions for unmanaged devices." + ) + 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_entra_app_enforced_restrictions_individual_apps_pass(self): + """Test PASS when policy targets SharePoint and Exchange individually.""" + id = str(uuid4()) + display_name = "Individual Apps Policy" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=[ + "00000003-0000-0ff1-ce00-000000000000", + "00000002-0000-0ff1-ce00-000000000000", + ], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Conditional Access Policy {display_name} enforces application restrictions for unmanaged devices." + ) + assert ( + result[0].resource + == entra_client.conditional_access_policies[id].dict() + ) + assert result[0].resource_name == display_name + assert result[0].resource_id == id + assert result[0].location == "global" + + def test_entra_app_enforced_restrictions_only_sharepoint_fail(self): + """Test FAIL when policy targets only SharePoint but not Exchange.""" + id = str(uuid4()) + display_name = "Only SharePoint Policy" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=[ + "00000003-0000-0ff1-ce00-000000000000", + ], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "No Conditional Access Policy enforces application restrictions for unmanaged devices." + ) + 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_entra_app_enforced_restrictions_browser_and_mobile_pass(self): + """Test PASS when policy uses browser + mobile apps instead of ALL.""" + id = str(uuid4()) + display_name = "Browser and Mobile Apps Policy" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ + ClientAppType.BROWSER, + ClientAppType.MOBILE_APPS_AND_DESKTOP_CLIENTS, + ], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Conditional Access Policy {display_name} enforces application restrictions for unmanaged devices." + ) + assert ( + result[0].resource + == entra_client.conditional_access_policies[id].dict() + ) + assert result[0].resource_name == display_name + assert result[0].resource_id == id + assert result[0].location == "global" + + def test_entra_app_enforced_restrictions_enabled(self): + """Test PASS when a compliant policy with app enforced restrictions is enabled.""" + id = str(uuid4()) + display_name = "App Enforced Restrictions Enabled" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id: ConditionalAccessPolicy( + id=id, + display_name=display_name, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ) + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Conditional Access Policy {display_name} enforces application restrictions for unmanaged devices." + ) + assert ( + result[0].resource + == entra_client.conditional_access_policies[id].dict() + ) + assert result[0].resource_name == display_name + assert result[0].resource_id == id + assert result[0].location == "global" + + def test_entra_app_enforced_restrictions_multiple_policies_one_compliant(self): + """Test PASS when multiple policies exist and at least one is compliant.""" + id1 = str(uuid4()) + id2 = str(uuid4()) + display_name1 = "Non-Compliant Policy" + display_name2 = "Compliant App Enforced Restrictions" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_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.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_app_enforced_restrictions.entra_app_enforced_restrictions import ( + entra_app_enforced_restrictions, + ) + from prowler.providers.m365.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + entra_client.conditional_access_policies = { + id1: ConditionalAccessPolicy( + id=id1, + display_name=display_name1, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["All"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[ConditionalAccessGrantControl.MFA], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=ConditionalAccessPolicyState.ENABLED, + ), + id2: ConditionalAccessPolicy( + id=id2, + display_name=display_name2, + conditions=Conditions( + application_conditions=ApplicationsConditions( + included_applications=["Office365"], + excluded_applications=[], + included_user_actions=[], + ), + user_conditions=UsersConditions( + included_groups=[], + excluded_groups=[], + included_users=["All"], + excluded_users=[], + included_roles=[], + excluded_roles=[], + ), + client_app_types=[ClientAppType.ALL], + user_risk_levels=[], + ), + grant_controls=GrantControls( + built_in_controls=[], + operator=GrantControlOperator.AND, + authentication_strength=None, + ), + 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=True + ), + ), + state=ConditionalAccessPolicyState.ENABLED, + ), + } + + check = entra_app_enforced_restrictions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Conditional Access Policy {display_name2} enforces application restrictions for unmanaged devices." + ) + assert result[0].resource_name == display_name2 + assert result[0].resource_id == id2 + assert result[0].location == "global" diff --git a/tests/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion_test.py b/tests/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion_test.py index 575cdb8445..56dc2ad3c1 100644 --- a/tests/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion_test.py +++ b/tests/providers/m365/services/entra/entra_emergency_access_exclusion/entra_emergency_access_exclusion_test.py @@ -225,17 +225,14 @@ class Test_entra_emergency_access_exclusion: check = entra_emergency_access_exclusion() result = check.execute() - assert len(result) == 2 - for finding in result: - assert finding.status == "FAIL" - assert ( - "does not have any user or group excluded as emergency access" - in finding.status_extended - ) - assert result[0].resource_name == "Policy 1" - assert result[0].resource_id == policy_id_1 - assert result[1].resource_name == "Policy 2" - assert result[1].resource_id == policy_id_2 + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + "No user or group is excluded as emergency access from all 2 enabled Conditional Access policies" + in result[0].status_extended + ) + assert result[0].resource_name == "Conditional Access Policies" + assert result[0].resource_id == "conditionalAccessPolicies" def test_entra_user_excluded_from_all_policies(self): """Test when a user is excluded from all enabled policies.""" @@ -339,17 +336,14 @@ class Test_entra_emergency_access_exclusion: check = entra_emergency_access_exclusion() result = check.execute() - assert len(result) == 2 - for finding in result: - assert finding.status == "PASS" - assert ( - "1 user(s) excluded as emergency access across all 2 enabled policies" - in finding.status_extended - ) - assert result[0].resource_name == "Policy 1" - assert result[0].resource_id == policy_id_1 - assert result[1].resource_name == "Policy 2" - assert result[1].resource_id == policy_id_2 + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + "1 user(s) excluded as emergency access across all 2 enabled Conditional Access policies" + in result[0].status_extended + ) + assert result[0].resource_name == "Conditional Access Policies" + assert result[0].resource_id == "conditionalAccessPolicies" def test_entra_group_excluded_from_all_policies(self): """Test when a group is excluded from all enabled policies.""" @@ -453,13 +447,14 @@ class Test_entra_emergency_access_exclusion: check = entra_emergency_access_exclusion() result = check.execute() - assert len(result) == 2 - for finding in result: - assert finding.status == "PASS" - assert ( - "1 group(s) excluded as emergency access across all 2 enabled policies" - in finding.status_extended - ) + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + "1 group(s) excluded as emergency access across all 2 enabled Conditional Access policies" + in result[0].status_extended + ) + assert result[0].resource_name == "Conditional Access Policies" + assert result[0].resource_id == "conditionalAccessPolicies" def test_entra_user_and_group_excluded_from_all_policies(self): """Test when both a user and group are excluded from all enabled policies.""" @@ -564,13 +559,14 @@ class Test_entra_emergency_access_exclusion: check = entra_emergency_access_exclusion() result = check.execute() - assert len(result) == 2 - for finding in result: - assert finding.status == "PASS" - assert ( - "1 user(s) and 1 group(s) excluded as emergency access across all 2 enabled policies" - in finding.status_extended - ) + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + "1 user(s) and 1 group(s) excluded as emergency access across all 2 enabled Conditional Access policies" + in result[0].status_extended + ) + assert result[0].resource_name == "Conditional Access Policies" + assert result[0].resource_id == "conditionalAccessPolicies" def test_entra_disabled_policies_ignored(self): """Test that disabled policies are ignored when checking exclusions.""" @@ -674,13 +670,12 @@ class Test_entra_emergency_access_exclusion: check = entra_emergency_access_exclusion() result = check.execute() - # Only 1 enabled policy, so only 1 finding assert len(result) == 1 assert result[0].status == "PASS" - assert result[0].resource_name == "Enabled Policy" - assert result[0].resource_id == policy_id_1 + assert result[0].resource_name == "Conditional Access Policies" + assert result[0].resource_id == "conditionalAccessPolicies" assert ( - "1 user(s) excluded as emergency access across all 1 enabled policies" + "1 user(s) excluded as emergency access across all 1 enabled Conditional Access policies" in result[0].status_extended ) @@ -787,10 +782,11 @@ class Test_entra_emergency_access_exclusion: check = entra_emergency_access_exclusion() result = check.execute() - assert len(result) == 2 - for finding in result: - assert finding.status == "PASS" - assert ( - "1 user(s) excluded as emergency access across all 2 enabled policies" - in finding.status_extended - ) + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + "1 user(s) excluded as emergency access across all 2 enabled Conditional Access policies" + in result[0].status_extended + ) + assert result[0].resource_name == "Conditional Access Policies" + assert result[0].resource_id == "conditionalAccessPolicies" diff --git a/tests/providers/m365/services/entra/entra_identity_protection_sign_in_risk_enabled/entra_identity_protection_sign_in_risk_enabled_test.py b/tests/providers/m365/services/entra/entra_identity_protection_sign_in_risk_enabled/entra_identity_protection_sign_in_risk_enabled_test.py index 2bd84694ea..9dba208ae4 100644 --- a/tests/providers/m365/services/entra/entra_identity_protection_sign_in_risk_enabled/entra_identity_protection_sign_in_risk_enabled_test.py +++ b/tests/providers/m365/services/entra/entra_identity_protection_sign_in_risk_enabled/entra_identity_protection_sign_in_risk_enabled_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicyState, @@ -109,6 +110,9 @@ class Test_entra_identity_protection_sign_in_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -189,6 +193,9 @@ class Test_entra_identity_protection_sign_in_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -272,6 +279,9 @@ class Test_entra_identity_protection_sign_in_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -355,6 +365,9 @@ class Test_entra_identity_protection_sign_in_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_identity_protection_user_risk_enabled/entra_identity_protection_user_risk_enabled_test.py b/tests/providers/m365/services/entra/entra_identity_protection_user_risk_enabled/entra_identity_protection_user_risk_enabled_test.py index 4336820faf..bc60bb4bf4 100644 --- a/tests/providers/m365/services/entra/entra_identity_protection_user_risk_enabled/entra_identity_protection_user_risk_enabled_test.py +++ b/tests/providers/m365/services/entra/entra_identity_protection_user_risk_enabled/entra_identity_protection_user_risk_enabled_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicyState, @@ -108,6 +109,9 @@ class Test_entra_identity_protection_user_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -187,6 +191,9 @@ class Test_entra_identity_protection_user_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -269,6 +276,9 @@ class Test_entra_identity_protection_user_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -351,6 +361,9 @@ class Test_entra_identity_protection_user_risk_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_intune_enrollment_sign_in_frequency_every_time/entra_intune_enrollment_sign_in_frequency_every_time_test.py b/tests/providers/m365/services/entra/entra_intune_enrollment_sign_in_frequency_every_time/entra_intune_enrollment_sign_in_frequency_every_time_test.py index d58f457e41..32fca532d2 100644 --- a/tests/providers/m365/services/entra/entra_intune_enrollment_sign_in_frequency_every_time/entra_intune_enrollment_sign_in_frequency_every_time_test.py +++ b/tests/providers/m365/services/entra/entra_intune_enrollment_sign_in_frequency_every_time/entra_intune_enrollment_sign_in_frequency_every_time_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessPolicyState, Conditions, @@ -108,6 +109,9 @@ class Test_entra_intune_enrollment_sign_in_frequency_every_time: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -184,6 +188,9 @@ class Test_entra_intune_enrollment_sign_in_frequency_every_time: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -258,6 +265,9 @@ class Test_entra_intune_enrollment_sign_in_frequency_every_time: type=SignInFrequencyType.HOURS, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) @@ -334,6 +344,9 @@ class Test_entra_intune_enrollment_sign_in_frequency_every_time: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) diff --git a/tests/providers/m365/services/entra/entra_legacy_authentication_blocked/entra_legacy_authentication_blocked_test.py b/tests/providers/m365/services/entra/entra_legacy_authentication_blocked/entra_legacy_authentication_blocked_test.py index b3b1a38550..bfd2c1d001 100644 --- a/tests/providers/m365/services/entra/entra_legacy_authentication_blocked/entra_legacy_authentication_blocked_test.py +++ b/tests/providers/m365/services/entra/entra_legacy_authentication_blocked/entra_legacy_authentication_blocked_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ClientAppType, ConditionalAccessGrantControl, @@ -116,6 +117,9 @@ class Test_entra_legacy_authentication_blocked: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -198,6 +202,9 @@ class Test_entra_legacy_authentication_blocked: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -283,6 +290,9 @@ class Test_entra_legacy_authentication_blocked: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_managed_device_required_for_authentication/entra_managed_device_required_for_authentication_test.py b/tests/providers/m365/services/entra/entra_managed_device_required_for_authentication/entra_managed_device_required_for_authentication_test.py index 4f58579208..bfd898cb26 100644 --- a/tests/providers/m365/services/entra/entra_managed_device_required_for_authentication/entra_managed_device_required_for_authentication_test.py +++ b/tests/providers/m365/services/entra/entra_managed_device_required_for_authentication/entra_managed_device_required_for_authentication_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicyState, @@ -106,6 +107,7 @@ class Test_entra_managed_device_required_for_authentication: type=None, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions(is_enabled=False), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -184,6 +186,7 @@ class Test_entra_managed_device_required_for_authentication: type=None, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions(is_enabled=False), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -266,6 +269,7 @@ class Test_entra_managed_device_required_for_authentication: type=None, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions(is_enabled=False), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_managed_device_required_for_mfa_registration/entra_managed_device_required_for_mfa_registration_test.py b/tests/providers/m365/services/entra/entra_managed_device_required_for_mfa_registration/entra_managed_device_required_for_mfa_registration_test.py index a127e9859b..9a5384b299 100644 --- a/tests/providers/m365/services/entra/entra_managed_device_required_for_mfa_registration/entra_managed_device_required_for_mfa_registration_test.py +++ b/tests/providers/m365/services/entra/entra_managed_device_required_for_mfa_registration/entra_managed_device_required_for_mfa_registration_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicyState, @@ -107,6 +108,9 @@ class Test_entra_managed_device_required_for_mfa_registration: type=None, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -185,6 +189,9 @@ class Test_entra_managed_device_required_for_mfa_registration: type=None, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -267,6 +274,9 @@ class Test_entra_managed_device_required_for_mfa_registration: type=None, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/entra_users_mfa_enabled/entra_users_mfa_enabled_test.py b/tests/providers/m365/services/entra/entra_users_mfa_enabled/entra_users_mfa_enabled_test.py index 07699fdecf..90f2508852 100644 --- a/tests/providers/m365/services/entra/entra_users_mfa_enabled/entra_users_mfa_enabled_test.py +++ b/tests/providers/m365/services/entra/entra_users_mfa_enabled/entra_users_mfa_enabled_test.py @@ -2,6 +2,7 @@ from unittest import mock from uuid import uuid4 from prowler.providers.m365.services.entra.entra_service import ( + ApplicationEnforcedRestrictions, ApplicationsConditions, ConditionalAccessGrantControl, ConditionalAccessPolicy, @@ -108,6 +109,9 @@ class Test_entra_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.DISABLED, ) @@ -189,6 +193,9 @@ class Test_entra_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -268,6 +275,9 @@ class Test_entra_users_mfa_enabled: type=None, interval=SignInFrequencyInterval.EVERY_TIME, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED, ) diff --git a/tests/providers/m365/services/entra/microsoft365_entra_service_test.py b/tests/providers/m365/services/entra/microsoft365_entra_service_test.py index b56939c2cb..e3ea887c96 100644 --- a/tests/providers/m365/services/entra/microsoft365_entra_service_test.py +++ b/tests/providers/m365/services/entra/microsoft365_entra_service_test.py @@ -6,6 +6,7 @@ from prowler.providers.m365.models import M365IdentityInfo from prowler.providers.m365.services.entra.entra_service import ( AdminConsentPolicy, AdminRoles, + ApplicationEnforcedRestrictions, ApplicationsConditions, AuthorizationPolicy, AuthPolicyRoles, @@ -87,6 +88,9 @@ async def mock_entra_get_conditional_access_policies(_): type=SignInFrequencyType.HOURS, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, ) @@ -238,6 +242,9 @@ class Test_Entra_Service: type=SignInFrequencyType.HOURS, interval=SignInFrequencyInterval.TIME_BASED, ), + application_enforced_restrictions=ApplicationEnforcedRestrictions( + is_enabled=False + ), ), state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING, )