feat(m365): add sharepoint service with 4 checks (#7057)

Co-authored-by: MarioRgzLpz <mariorgzlpz1809@gmail.com>
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
Daniel Barranquero
2025-02-27 18:15:17 +01:00
committed by César Arroba
parent 8ff0c59964
commit d3dd1644e6
22 changed files with 1167 additions and 18 deletions
@@ -26,14 +26,15 @@ class entra_thirdparty_integrated_apps_not_allowed(Check):
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 @@ class entra_thirdparty_integrated_apps_not_allowed(Check):
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
@@ -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())
@@ -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": ""
}
@@ -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
@@ -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": ""
}
@@ -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
@@ -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": ""
}
@@ -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
@@ -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": ""
}
@@ -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
@@ -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
@@ -33,13 +33,7 @@ class Test_entra_thirdparty_integrated_apps_not_allowed:
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())
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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