From d3dd1644e6258c53981e7108bfc15cf2e4f8855f Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Thu, 27 Feb 2025 18:15:17 +0100 Subject: [PATCH] feat(m365): add sharepoint service with 4 checks (#7057) Co-authored-by: MarioRgzLpz Co-authored-by: HugoPBrito Co-authored-by: MrCloudSec --- ..._thirdparty_integrated_apps_not_allowed.py | 20 +- .../services/sharepoint/__init__.py | 0 .../services/sharepoint/sharepoint_client.py | 6 + .../__init__.py | 0 ...int_external_sharing_managed.metadata.json | 30 ++ .../sharepoint_external_sharing_managed.py | 59 ++++ .../__init__.py | 0 ..._external_sharing_restricted.metadata.json | 30 ++ .../sharepoint_external_sharing_restricted.py | 51 +++ .../__init__.py | 0 ...int_guest_sharing_restricted.metadata.json | 30 ++ .../sharepoint_guest_sharing_restricted.py | 45 +++ .../__init__.py | 0 ...dern_authentication_required.metadata.json | 30 ++ ...arepoint_modern_authentication_required.py | 50 +++ .../services/sharepoint/sharepoint_service.py | 62 ++++ ...dparty_integrated_apps_not_allowed_test.py | 8 +- ...harepoint_external_sharing_managed_test.py | 298 ++++++++++++++++++ ...epoint_external_sharing_restricted_test.py | 138 ++++++++ ...harepoint_guest_sharing_restricted_test.py | 141 +++++++++ ...int_modern_authentication_required_test.py | 141 +++++++++ .../sharepoint/sharepoint_service_test.py | 46 +++ 22 files changed, 1167 insertions(+), 18 deletions(-) create mode 100644 prowler/providers/microsoft365/services/sharepoint/__init__.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_client.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/__init__.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.metadata.json create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/__init__.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.metadata.json create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/__init__.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.metadata.json create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/__init__.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.metadata.json create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.py create mode 100644 prowler/providers/microsoft365/services/sharepoint/sharepoint_service.py create mode 100644 tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py create mode 100644 tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py create mode 100644 tests/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py create mode 100644 tests/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py create mode 100644 tests/providers/microsoft365/services/sharepoint/sharepoint_service_test.py diff --git a/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py b/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py index 439fb0ba73..36ce95286c 100644 --- a/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py +++ b/prowler/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed.py @@ -26,14 +26,15 @@ def execute(self) -> List[CheckReportMicrosoft365]: findings = [] auth_policy = entra_client.authorization_policy - report = CheckReportMicrosoft365( - metadata=self.metadata(), - resource=auth_policy if auth_policy else {}, - resource_name=auth_policy.name if auth_policy else "Authorization Policy", - resource_id=auth_policy.id if auth_policy else "authorizationPolicy", - ) - if auth_policy: + report = CheckReportMicrosoft365( + metadata=self.metadata(), + resource=auth_policy if auth_policy else {}, + resource_name=( + auth_policy.name if auth_policy else "Authorization Policy" + ), + resource_id=auth_policy.id if auth_policy else "authorizationPolicy", + ) if getattr( auth_policy, "default_user_role_permissions", None ) and not getattr( @@ -48,10 +49,7 @@ def execute(self) -> List[CheckReportMicrosoft365]: report.status_extended = ( "App creation is not disabled for non-admin users." ) - else: - report.status = "FAIL" - report.status_extended = "Authorization Policy was not found." - findings.append(report) + findings.append(report) return findings diff --git a/prowler/providers/microsoft365/services/sharepoint/__init__.py b/prowler/providers/microsoft365/services/sharepoint/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_client.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_client.py new file mode 100644 index 0000000000..fc547bc167 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_client.py @@ -0,0 +1,6 @@ +from prowler.providers.common.provider import Provider +from prowler.providers.microsoft365.services.sharepoint.sharepoint_service import ( + SharePoint, +) + +sharepoint_client = SharePoint(Provider.get_global_provider()) diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/__init__.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.metadata.json b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.metadata.json new file mode 100644 index 0000000000..ee559a57a0 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "microsoft365", + "CheckID": "sharepoint_external_sharing_managed", + "CheckTitle": "Ensure SharePoint external sharing is managed through domain whitelists/blacklists.", + "CheckType": [], + "ServiceName": "sharepoint", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Sharepoint Settings", + "Description": "Control the sharing of documents to external domains by either blocking specific domains or only allowing sharing with named trusted domains.", + "Risk": "If domain-based sharing restrictions are not enforced, users may share documents with untrusted external entities, increasing the risk of data exfiltration or unauthorized access.", + "RelatedUrl": "https://learn.microsoft.com/en-us/sharepoint/turn-external-sharing-on-or-off", + "Remediation": { + "Code": { + "CLI": "Set-SPOTenant -SharingDomainRestrictionMode AllowList -SharingAllowedDomainList 'domain1.com domain2.com'", + "NativeIaC": "", + "Other": "1. Navigate to SharePoint admin center https://admin.microsoft.com/sharepoint. 2. Expand Policies then click Sharing. 3. Expand More external sharing settings and check 'Limit external sharing by domain'. 4. Select 'Add domains' to configure a list of approved domains. 5. Click Save.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enforce domain-based restrictions for SharePoint external sharing to control document sharing with trusted domains.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-spotenant?view=sharepoint-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.py new file mode 100644 index 0000000000..9fd8c432a1 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed.py @@ -0,0 +1,59 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 +from prowler.providers.microsoft365.services.sharepoint.sharepoint_client import ( + sharepoint_client, +) + + +class sharepoint_external_sharing_managed(Check): + """ + Check if Microsoft 365 SharePoint external sharing is managed through domain whitelists/blacklists. + + This check verifies that SharePoint external sharing settings are configured to restrict document sharing + to external domains by enforcing domain-based restrictions. This means that the setting + 'sharingDomainRestrictionMode' must be set to either "AllowList" or "BlockList". If it is not, then + external sharing is not managed via domain restrictions, increasing the risk of unauthorized access. + + Note: This check only evaluates the domain restriction mode and does not enforce the optional check + of verifying that the allowed/blocked domain list is not empty. + """ + + def execute(self) -> List[CheckReportMicrosoft365]: + """ + Execute the SharePoint external sharing management check. + + Iterates over the SharePoint settings retrieved from the Microsoft 365 SharePoint client and + generates a report indicating whether external sharing is managed via domain restrictions. + + Returns: + List[CheckReportMicrosoft365]: A list containing a report with the result of the check. + """ + findings = [] + settings = sharepoint_client.settings + if settings: + report = CheckReportMicrosoft365( + self.metadata(), + resource=settings if settings else {}, + resource_name="SharePoint Settings", + resource_id=sharepoint_client.tenant_domain, + ) + report.status = "FAIL" + report.status_extended = "SharePoint external sharing is not managed through domain restrictions." + if settings.sharingDomainRestrictionMode in ["allowList", "blockList"]: + report.status_extended = f"SharePoint external sharing is managed through domain restrictions with mode '{settings.sharingDomainRestrictionMode}' but the list is empty." + if ( + settings.sharingDomainRestrictionMode == "allowList" + and settings.sharingAllowedDomainList + ): + report.status = "PASS" + report.status_extended = f"SharePoint external sharing is managed through domain restrictions with mode '{settings.sharingDomainRestrictionMode}'." + elif ( + settings.sharingDomainRestrictionMode == "blockList" + and settings.sharingBlockedDomainList + ): + report.status = "PASS" + report.status_extended = f"SharePoint external sharing is managed through domain restrictions with mode '{settings.sharingDomainRestrictionMode}'." + + findings.append(report) + return findings diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/__init__.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.metadata.json b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.metadata.json new file mode 100644 index 0000000000..63b040672d --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "microsoft365", + "CheckID": "sharepoint_external_sharing_restricted", + "CheckTitle": "Ensure external content sharing is restricted.", + "CheckType": [], + "ServiceName": "sharepoint", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Sharepoint Settings", + "Description": "Ensure that external sharing settings in SharePoint are restricted to 'New and existing guests' or a less permissive level to enforce authentication and control over shared content.", + "Risk": "If external sharing is not restricted, unauthorized users may gain access to sensitive information, increasing the risk of data breaches and compliance violations.", + "RelatedUrl": "https://learn.microsoft.com/en-us/sharepoint/turn-external-sharing-on-or-off", + "Remediation": { + "Code": { + "CLI": "Set-SPOTenant -SharingCapability ExternalUserSharingOnly", + "NativeIaC": "", + "Other": "1. Navigate to SharePoint admin center https://admin.microsoft.com/sharepoint. 2. Click to expand Policies > Sharing. 3. Locate the External sharing section. 4. Under SharePoint, move the slider bar to 'New and existing guests' or a less permissive level.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Restrict external sharing in SharePoint to 'New and existing guests' or a more restrictive setting to enhance security.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-spotenant?view=sharepoint-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.py new file mode 100644 index 0000000000..639ce00df3 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted.py @@ -0,0 +1,51 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 +from prowler.providers.microsoft365.services.sharepoint.sharepoint_client import ( + sharepoint_client, +) + + +class sharepoint_external_sharing_restricted(Check): + """ + Check if Microsoft 365 SharePoint restricts external sharing at organization level. + + This check verifies that external sharing settings in SharePoint are configured to allow only "New and existing guests" + (i.e., ExternalUserSharingOnly), which enforces authentication and limits access to external users. If a more permissive + setting is used, legacy sharing may be allowed, increasing the risk of unauthorized data access. + """ + + def execute(self) -> List[CheckReportMicrosoft365]: + """ + Execute the SharePoint external sharing restriction check. + + Iterates over the SharePoint settings retrieved from the Microsoft 365 SharePoint client and generates a report + indicating whether external sharing is restricted to 'New and existing guests' (ExternalUserSharingOnly). + + Returns: + List[Check_Report_Microsoft365]: A list containing a report with the result of the check. + """ + findings = [] + settings = sharepoint_client.settings + if settings: + report = CheckReportMicrosoft365( + self.metadata(), + resource=settings if settings else {}, + resource_name="SharePoint Settings", + resource_id=sharepoint_client.tenant_domain, + ) + report.status = "FAIL" + report.status_extended = ( + "External sharing is not restricted and guests users can access." + ) + + if settings.sharingCapability in [ + "ExistingExternalUserSharingOnly", + "ExternalUserSharingOnly", + "Disabled", + ]: + report.status = "PASS" + report.status_extended = "External sharing is restricted to external user sharing or more restrictive." + + findings.append(report) + return findings diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/__init__.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.metadata.json b/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.metadata.json new file mode 100644 index 0000000000..89acd7e46a --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "microsoft365", + "CheckID": "sharepoint_guest_sharing_restricted", + "CheckTitle": "Ensure that SharePoint guest users cannot share items they don't own.", + "CheckType": [], + "ServiceName": "sharepoint", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Sharepoint Settings", + "Description": "Ensure that guest users in SharePoint cannot share items they do not own, preventing unauthorized disclosure of shared content.", + "Risk": "If guest users are allowed to share items they don't own, there is a higher risk of unauthorized data exposure, as external users could share content beyond intended recipients.", + "RelatedUrl": "https://learn.microsoft.com/en-us/sharepoint/external-sharing-overview", + "Remediation": { + "Code": { + "CLI": "Set-SPOTenant -PreventExternalUsersFromResharing $True", + "NativeIaC": "", + "Other": "1. Navigate to SharePoint admin center https://admin.microsoft.com/sharepoint. 2. Click to expand Policies then select Sharing. 3. Expand More external sharing settings and uncheck 'Allow guests to share items they don't own'. 4. Click Save.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Restrict guest users from sharing items they don't own to enhance security and prevent unauthorized access.", + "Url": "https://learn.microsoft.com/en-us/sharepoint/turn-external-sharing-on-or-off" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.py new file mode 100644 index 0000000000..3666a837b1 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted.py @@ -0,0 +1,45 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 +from prowler.providers.microsoft365.services.sharepoint.sharepoint_client import ( + sharepoint_client, +) + + +class sharepoint_guest_sharing_restricted(Check): + """ + Check if Microsoft 365 SharePoint guest sharing is restricted. + + This check verifies that guest users in SharePoint cannot share items they do not own. + When guest resharing is enabled, external users might share content they don't own, + increasing the risk of unauthorized data exposure. This control ensures that the setting + to prevent external users from resharing is enabled. + """ + + def execute(self) -> List[CheckReportMicrosoft365]: + """ + Execute the SharePoint guest sharing restriction check. + + Iterates over the SharePoint settings retrieved from the Microsoft 365 SharePoint client + and generates a report indicating whether guest users are prevented from sharing items they do not own. + + Returns: + List[CheckReportMicrosoft365]: A list containing a report with the result of the check. + """ + findings = [] + settings = sharepoint_client.settings + if settings: + report = CheckReportMicrosoft365( + self.metadata(), + resource=settings if settings else {}, + resource_name="SharePoint Settings", + resource_id=sharepoint_client.tenant_domain, + ) + report.status = "FAIL" + report.status_extended = "Guest sharing is not restricted; guest users can share items they do not own." + if not settings.resharingEnabled: + report.status = "PASS" + report.status_extended = "Guest sharing is restricted; guest users cannot share items they do not own." + + findings.append(report) + return findings diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/__init__.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.metadata.json b/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.metadata.json new file mode 100644 index 0000000000..e9c0047115 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "microsoft365", + "CheckID": "sharepoint_modern_authentication_required", + "CheckTitle": "Ensure modern authentication for SharePoint applications is required.", + "CheckType": [], + "ServiceName": "sharepoint", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "Sharepoint Settings", + "Description": "Ensure that modern authentication is required for SharePoint applications in Microsoft 365, preventing the use of legacy authentication protocols and blocking access to apps that don't use modern authentication.", + "Risk": "If modern authentication is not enforced, SharePoint applications may rely on basic authentication, which lacks strong security measures like MFA and increases the risk of credential theft.", + "RelatedUrl": "https://learn.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0", + "Remediation": { + "Code": { + "CLI": "Set-SPOTenant -LegacyAuthProtocolsEnabled $false", + "NativeIaC": "", + "Other": "1. Navigate to SharePoint admin center https://admin.microsoft.com/sharepoint. 2. Click to expand Policies select Access control. 3. Select Apps that don't use modern authentication. 4. Select the radio button for Block access. 5. Click Save.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Block access for SharePoint applications that don't use modern authentication to ensure secure authentication mechanisms.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-spotenant?view=sharepoint-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.py new file mode 100644 index 0000000000..04f61084b2 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required.py @@ -0,0 +1,50 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportMicrosoft365 +from prowler.providers.microsoft365.services.sharepoint.sharepoint_client import ( + sharepoint_client, +) + + +class sharepoint_modern_authentication_required(Check): + """ + Check if Microsoft 365 SharePoint requires modern authentication. + + This check verifies that modern authentication is enforced for SharePoint applications in Microsoft 365. + Modern authentication leverages OAuth 2.0 and supports advanced security features such as multi-factor + authentication (MFA) and conditional access. Legacy authentication protocols (e.g., basic authentication) + do not support these features and increase the risk of credential compromise. + + The check fails if modern authentication is not enforced, indicating that legacy protocols may be used. + """ + + def execute(self) -> List[CheckReportMicrosoft365]: + """ + Execute the SharePoint modern authentication requirement check. + + Iterates over the SharePoint configuration retrieved from the Microsoft 365 SharePoint client and + generates a report indicating whether modern authentication is required for SharePoint applications. + + Returns: + List[CheckReportMicrosoft365]: A list containing the report object with the result of the check. + """ + findings = [] + settings = sharepoint_client.settings + if settings: + report = CheckReportMicrosoft365( + self.metadata(), + resource=settings if settings else {}, + resource_name="SharePoint Settings", + resource_id=sharepoint_client.tenant_domain, + ) + report.status = "PASS" + report.status_extended = "Microsoft 365 SharePoint does not allow access to apps that don't use modern authentication." + + # Legacy Auth being True means that SharePoint allow access to apps that do NOT use modern authentication + if settings.legacyAuth: + report.status = "FAIL" + report.status_extended = "Microsoft 365 SharePoint allows access to apps that don't use modern authentication." + + findings.append(report) + + return findings diff --git a/prowler/providers/microsoft365/services/sharepoint/sharepoint_service.py b/prowler/providers/microsoft365/services/sharepoint/sharepoint_service.py new file mode 100644 index 0000000000..bb8f2c6903 --- /dev/null +++ b/prowler/providers/microsoft365/services/sharepoint/sharepoint_service.py @@ -0,0 +1,62 @@ +from asyncio import gather, get_event_loop +from typing import List, Optional + +from msgraph.generated.models.o_data_errors.o_data_error import ODataError +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.microsoft365.lib.service.service import Microsoft365Service +from prowler.providers.microsoft365.microsoft365_provider import Microsoft365Provider + + +class SharePoint(Microsoft365Service): + def __init__(self, provider: Microsoft365Provider): + super().__init__(provider) + loop = get_event_loop() + self.tenant_domain = provider.identity.tenant_domain + attributes = loop.run_until_complete( + gather( + self._get_settings(), + ) + ) + self.settings = attributes[0] + + async def _get_settings(self): + logger.info("Microsoft365 - Getting SharePoint global settings...") + settings = None + try: + global_settings = await self.client.admin.sharepoint.settings.get() + + settings = SharePointSettings( + sharingCapability=( + str(global_settings.sharing_capability).split(".")[-1] + if global_settings.sharing_capability + else None + ), + sharingAllowedDomainList=global_settings.sharing_allowed_domain_list, + sharingBlockedDomainList=global_settings.sharing_blocked_domain_list, + sharingDomainRestrictionMode=global_settings.sharing_domain_restriction_mode, + legacyAuth=global_settings.is_legacy_auth_protocols_enabled, + resharingEnabled=global_settings.is_resharing_by_external_users_enabled, + ) + + except ODataError as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return None + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return None + return settings + + +class SharePointSettings(BaseModel): + sharingCapability: str + sharingAllowedDomainList: Optional[List[str]] + sharingBlockedDomainList: Optional[List[str]] + sharingDomainRestrictionMode: str + resharingEnabled: bool + legacyAuth: bool diff --git a/tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py b/tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py index 5eeb10fa6e..60eefc42da 100644 --- a/tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py +++ b/tests/providers/microsoft365/services/entra/entra_thirdparty_integrated_apps_not_allowed/entra_thirdparty_integrated_apps_not_allowed_test.py @@ -33,13 +33,7 @@ def test_entra_no_authorization_policy(self): check = entra_thirdparty_integrated_apps_not_allowed() result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert result[0].resource == {} - assert result[0].resource_name == "Authorization Policy" - assert result[0].resource_id == "authorizationPolicy" - assert result[0].status_extended == "Authorization Policy was not found." - assert result[0].location == "global" + assert len(result) == 0 def test_entra_default_user_role_permissions_not_allowed_to_create_apps(self): id = str(uuid4()) diff --git a/tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py b/tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py new file mode 100644 index 0000000000..60e92f719d --- /dev/null +++ b/tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py @@ -0,0 +1,298 @@ +from unittest import mock + +from prowler.providers.microsoft365.services.sharepoint.sharepoint_service import ( + SharePointSettings, +) +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +class Test_sharepoint_external_sharing_managed: + def test_external_sharing_invalid_mode(self): + """ + Test when sharingDomainRestrictionMode is set to an invalid value (not "allowList" ni "blockList"): + The check should FAIL with the default message. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed import ( + sharepoint_external_sharing_managed, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + legacyAuth=True, + resharingEnabled=False, + sharingDomainRestrictionMode="none", + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_external_sharing_managed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "SharePoint external sharing is not managed through domain restrictions." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "none", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_allow_list_empty(self): + """ + Test when sharingDomainRestrictionMode is "allowList" but AllowedDomainList is empty: + The check should FAIL with a message indicating the list is empty. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed import ( + sharepoint_external_sharing_managed, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=[], + sharingBlockedDomainList=["blocked-domain.com"], + legacyAuth=True, + resharingEnabled=False, + sharingDomainRestrictionMode="allowList", + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_external_sharing_managed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "SharePoint external sharing is managed through domain restrictions with mode 'allowList' but the list is empty." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": [], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_block_list_empty(self): + """ + Test when sharingDomainRestrictionMode is "blockList" but BlockedDomainList is empty: + The check should FAIL with a message indicating the list is empty. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed import ( + sharepoint_external_sharing_managed, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=[], + legacyAuth=True, + resharingEnabled=False, + sharingDomainRestrictionMode="blockList", + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_external_sharing_managed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "SharePoint external sharing is managed through domain restrictions with mode 'blockList' but the list is empty." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": [], + "sharingDomainRestrictionMode": "blockList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_allow_list_non_empty(self): + """ + Test when sharingDomainRestrictionMode is "allowList" and AllowedDomainList is not empty: + The check should PASS. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed import ( + sharepoint_external_sharing_managed, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + legacyAuth=True, + resharingEnabled=False, + sharingDomainRestrictionMode="allowList", + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_external_sharing_managed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "SharePoint external sharing is managed through domain restrictions with mode 'allowList'." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_block_list_non_empty(self): + """ + Test when sharingDomainRestrictionMode is "blockList" and BlockedDomainList is not empty: + The check should PASS. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed import ( + sharepoint_external_sharing_managed, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + legacyAuth=True, + resharingEnabled=False, + sharingDomainRestrictionMode="blockList", + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_external_sharing_managed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "SharePoint external sharing is managed through domain restrictions with mode 'blockList'." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "blockList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_empty_settings(self): + """ + Test when sharepoint_client.settings is empty: + The check should return an empty list of findings. + """ + sharepoint_client = mock.MagicMock + sharepoint_client.settings = {} + sharepoint_client.tenant_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_managed.sharepoint_external_sharing_managed import ( + sharepoint_external_sharing_managed, + ) + + check = sharepoint_external_sharing_managed() + result = check.execute() + assert len(result) == 0 diff --git a/tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py b/tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py new file mode 100644 index 0000000000..e6461cb3ce --- /dev/null +++ b/tests/providers/microsoft365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py @@ -0,0 +1,138 @@ +from unittest import mock + +from prowler.providers.microsoft365.services.sharepoint.sharepoint_service import ( + SharePointSettings, +) +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +class Test_sharepoint_external_sharing_restricted: + def test_external_sharing_restricted(self): + """ + Test when sharingCapability is set to an allowed value (e.g. "ExternalUserSharingOnly"): + The check should PASS because external sharing is restricted. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_restricted.sharepoint_external_sharing_restricted.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_restricted.sharepoint_external_sharing_restricted import ( + sharepoint_external_sharing_restricted, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + sharingDomainRestrictionMode="allowList", + resharingEnabled=False, + legacyAuth=True, + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_external_sharing_restricted() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "External sharing is restricted to external user sharing or more restrictive." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_external_sharing_not_restricted(self): + """ + Test when sharingCapability is set to a non-restricted value (e.g. "ExternalUserAndGuestSharing"): + The check should FAIL because external sharing is not restricted. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_restricted.sharepoint_external_sharing_restricted.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_restricted.sharepoint_external_sharing_restricted import ( + sharepoint_external_sharing_restricted, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserAndGuestSharing", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + sharingDomainRestrictionMode="allowList", + resharingEnabled=False, + legacyAuth=True, + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_external_sharing_restricted() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "External sharing is not restricted and guests users can access." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserAndGuestSharing", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_empty_settings(self): + """ + Test when sharepoint_client.settings is empty: + The check should return an empty list of findings. + """ + sharepoint_client = mock.MagicMock + sharepoint_client.settings = {} + sharepoint_client.tenant_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_restricted.sharepoint_external_sharing_restricted.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_external_sharing_restricted.sharepoint_external_sharing_restricted import ( + sharepoint_external_sharing_restricted, + ) + + check = sharepoint_external_sharing_restricted() + result = check.execute() + assert len(result) == 0 diff --git a/tests/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py b/tests/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py new file mode 100644 index 0000000000..08bcc813db --- /dev/null +++ b/tests/providers/microsoft365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py @@ -0,0 +1,141 @@ +from unittest import mock + +from prowler.providers.microsoft365.services.sharepoint.sharepoint_service import ( + SharePointSettings, +) +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +class Test_sharepoint_guest_sharing_restricted: + def test_guest_sharing_restricted(self): + """ + Test when resharingEnabled is False: + The check should PASS because guest sharing is restricted. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_guest_sharing_restricted.sharepoint_guest_sharing_restricted.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_guest_sharing_restricted.sharepoint_guest_sharing_restricted import ( + sharepoint_guest_sharing_restricted, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + sharingDomainRestrictionMode="allowList", + legacyAuth=True, + resharingEnabled=False, + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_guest_sharing_restricted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "Guest sharing is restricted; guest users cannot share items they do not own." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_guest_sharing_not_restricted(self): + """ + Test when resharingEnabled is True: + The check should FAIL because guest sharing is not restricted. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_guest_sharing_restricted.sharepoint_guest_sharing_restricted.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_guest_sharing_restricted.sharepoint_guest_sharing_restricted import ( + sharepoint_guest_sharing_restricted, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + sharingDomainRestrictionMode="allowList", + legacyAuth=True, + resharingEnabled=True, + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_guest_sharing_restricted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "Guest sharing is not restricted; guest users can share items they do not own." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserSharingOnly", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": True, + "legacyAuth": True, + } + + def test_empty_settings(self): + """ + Test when sharepoint_client.settings is empty: + The check should return an empty list of findings. + """ + sharepoint_client = mock.MagicMock + sharepoint_client.settings = {} + sharepoint_client.tenant_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_guest_sharing_restricted.sharepoint_guest_sharing_restricted.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_guest_sharing_restricted.sharepoint_guest_sharing_restricted import ( + sharepoint_guest_sharing_restricted, + ) + + check = sharepoint_guest_sharing_restricted() + result = check.execute() + + assert len(result) == 0 diff --git a/tests/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py b/tests/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py new file mode 100644 index 0000000000..b003932edc --- /dev/null +++ b/tests/providers/microsoft365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py @@ -0,0 +1,141 @@ +from unittest import mock + +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +class Test_sharepoint_modern_authentication_required: + def test_sharepoint_modern_authentication_disabled(self): + """ + Test when legacyAuth is False: + The check should PASS, as SharePoint does not allow access to apps that don't use modern authentication. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_modern_authentication_required.sharepoint_modern_authentication_required.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_modern_authentication_required.sharepoint_modern_authentication_required import ( + sharepoint_modern_authentication_required, + ) + from prowler.providers.microsoft365.services.sharepoint.sharepoint_service import ( + SharePointSettings, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserAndGuestSharing", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + sharingDomainRestrictionMode="allowList", + resharingEnabled=False, + legacyAuth=False, + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_modern_authentication_required() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "Microsoft 365 SharePoint does not allow access to apps that don't use modern authentication." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserAndGuestSharing", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": False, + "legacyAuth": False, + } + + def test_sharepoint_modern_authentication_enabled(self): + """ + Test when legacyAuth is True: + The check should FAIL, as SharePoint allows access to apps that don't use modern authentication. + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_modern_authentication_required.sharepoint_modern_authentication_required.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_modern_authentication_required.sharepoint_modern_authentication_required import ( + sharepoint_modern_authentication_required, + ) + from prowler.providers.microsoft365.services.sharepoint.sharepoint_service import ( + SharePointSettings, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserAndGuestSharing", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + sharingDomainRestrictionMode="allowList", + resharingEnabled=False, + legacyAuth=True, + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_modern_authentication_required() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "Microsoft 365 SharePoint allows access to apps that don't use modern authentication." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == { + "sharingCapability": "ExternalUserAndGuestSharing", + "sharingAllowedDomainList": ["allowed-domain.com"], + "sharingBlockedDomainList": ["blocked-domain.com"], + "sharingDomainRestrictionMode": "allowList", + "resharingEnabled": False, + "legacyAuth": True, + } + + def test_sharepoint_empty_settings(self): + """ + Test when sharepoint_client.settings is empty: + The check should return an empty list of findings. + """ + sharepoint_client = mock.MagicMock + sharepoint_client.settings = {} + sharepoint_client.tenant_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_modern_authentication_required.sharepoint_modern_authentication_required.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.microsoft365.services.sharepoint.sharepoint_modern_authentication_required.sharepoint_modern_authentication_required import ( + sharepoint_modern_authentication_required, + ) + + check = sharepoint_modern_authentication_required() + result = check.execute() + assert len(result) == 0 diff --git a/tests/providers/microsoft365/services/sharepoint/sharepoint_service_test.py b/tests/providers/microsoft365/services/sharepoint/sharepoint_service_test.py new file mode 100644 index 0000000000..06a220f2c8 --- /dev/null +++ b/tests/providers/microsoft365/services/sharepoint/sharepoint_service_test.py @@ -0,0 +1,46 @@ +from unittest.mock import patch + +from prowler.providers.microsoft365.models import Microsoft365IdentityInfo +from prowler.providers.microsoft365.services.sharepoint.sharepoint_service import ( + SharePoint, + SharePointSettings, +) +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +async def mock_sharepoint_get_settings(_): + return SharePointSettings( + sharingCapability="ExternalUserAndGuestSharing", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + sharingDomainRestrictionMode="allowList", + resharingEnabled=False, + legacyAuth=True, + ) + + +@patch( + "prowler.providers.microsoft365.services.sharepoint.sharepoint_service.SharePoint._get_settings", + new=mock_sharepoint_get_settings, +) +class Test_SharePoint_Service: + def test_get_client(self): + sharepoint_client = SharePoint( + set_mocked_microsoft365_provider( + identity=Microsoft365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + assert sharepoint_client.client.__class__.__name__ == "GraphServiceClient" + + def test_get_settings(self): + sharepoint_client = SharePoint(set_mocked_microsoft365_provider()) + settings = sharepoint_client.settings + assert settings.sharingCapability == "ExternalUserAndGuestSharing" + assert settings.sharingAllowedDomainList == ["allowed-domain.com"] + assert settings.sharingBlockedDomainList == ["blocked-domain.com"] + assert settings.sharingDomainRestrictionMode == "allowList" + assert settings.resharingEnabled is False + assert settings.legacyAuth is True