Compare commits

...

1 Commits

Author SHA1 Message Date
lydiavilchez 9894ac7bc3 feat(googleworkspace): implement Chat service with 6 CIS checks (#11126) 2026-05-14 17:19:11 +02:00
32 changed files with 1980 additions and 13 deletions
@@ -18,7 +18,7 @@ Prowler requests the following read-only OAuth 2.0 scopes:
| `https://www.googleapis.com/auth/admin.directory.domain.readonly` | Read access to domain information |
| `https://www.googleapis.com/auth/admin.directory.customer.readonly` | Read access to customer information (Customer ID) |
| `https://www.googleapis.com/auth/admin.directory.orgunit.readonly` | Read access to organizational unit hierarchy (identifies the root OU for policy filtering) |
| `https://www.googleapis.com/auth/cloud-identity.policies.readonly` | Read access to domain-level application policies (required for Calendar service checks) |
| `https://www.googleapis.com/auth/cloud-identity.policies.readonly` | Read access to domain-level application policies (required for Calendar, Gmail, Chat, and Drive service checks) |
| `https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly` | Read access to admin roles and role assignments |
<Warning>
@@ -40,7 +40,7 @@ In the [Google Cloud Console](https://console.cloud.google.com), select the targ
| API | Required For |
|-----|--------------|
| **Admin SDK API** | Directory service checks (users, roles, domains) |
| **Cloud Identity API** | Calendar service checks (domain-level sharing and invitation policies) |
| **Cloud Identity API** | Calendar, Gmail, Chat, and Drive service checks (domain-level application policies) |
For each API:
@@ -49,7 +49,7 @@ For each API:
3. Click **Enable**
<Note>
Both APIs must be enabled in the same GCP project that hosts the Service Account. Calendar checks will return no findings if the Cloud Identity API is not enabled.
Both APIs must be enabled in the same GCP project that hosts the Service Account. Calendar, Gmail, Chat, and Drive checks will return no findings if the Cloud Identity API is not enabled.
</Note>
### Step 3: Create a Service Account
@@ -176,9 +176,9 @@ If Prowler connects but returns empty results or permission errors for specific
- Verify all scopes are authorized in the Admin Console
- Ensure the delegated user is an active super administrator
### Calendar Checks Return No Findings
### Policy API Checks Return No Findings
If the Directory checks run successfully but the Calendar checks (e.g., `calendar_external_sharing_primary_calendar`) return no findings, the Cloud Identity Policy API is not reachable for this Service Account. Verify:
If the Directory checks run successfully but the Calendar, Gmail, Chat, or Drive checks return no findings, the Cloud Identity Policy API is not reachable for this Service Account. Verify:
- The **Cloud Identity API** is enabled in the GCP project hosting the Service Account (Step 2)
- The scope `https://www.googleapis.com/auth/cloud-identity.policies.readonly` is included in the Domain-Wide Delegation OAuth scopes list in the Admin Console (Step 5)
+1
View File
@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🚀 Added
- 6 Chat file sharing, external messaging, spaces, and apps access checks for Google Workspace provider using the Cloud Identity Policy API [(#11126)](https://github.com/prowler-cloud/prowler/pull/11126)
- `entra_service_principal_no_secrets_for_permanent_tier0_roles` check for M365 provider [(#10788)](https://github.com/prowler-cloud/prowler/pull/10788)
- `iam_user_access_not_stale_to_sagemaker` check for AWS provider with configurable `max_unused_sagemaker_access_days` (default 90) [(#11000)](https://github.com/prowler-cloud/prowler/pull/11000)
- `cloudtrail_bedrock_logging_enabled` check for AWS provider [(#10858)](https://github.com/prowler-cloud/prowler/pull/10858)
@@ -1084,7 +1084,9 @@
{
"Id": "3.1.4.1.1",
"Description": "Ensure external filesharing in Google Chat and Hangouts is disabled",
"Checks": [],
"Checks": [
"chat_external_file_sharing_disabled"
],
"Attributes": [
{
"Section": "3 Apps",
@@ -1105,7 +1107,9 @@
{
"Id": "3.1.4.1.2",
"Description": "Ensure internal filesharing in Google Chat and Hangouts is disabled",
"Checks": [],
"Checks": [
"chat_internal_file_sharing_disabled"
],
"Attributes": [
{
"Section": "3 Apps",
@@ -1126,7 +1130,9 @@
{
"Id": "3.1.4.2.1",
"Description": "Ensure Google Chat externally is restricted to allowed domains",
"Checks": [],
"Checks": [
"chat_external_messaging_restricted"
],
"Attributes": [
{
"Section": "3 Apps",
@@ -1147,7 +1153,9 @@
{
"Id": "3.1.4.3.1",
"Description": "Ensure external spaces in Google Chat and Hangouts are restricted",
"Checks": [],
"Checks": [
"chat_external_spaces_restricted"
],
"Attributes": [
{
"Section": "3 Apps",
@@ -1168,7 +1176,9 @@
{
"Id": "3.1.4.4.1",
"Description": "Ensure allow users to install Chat apps is disabled",
"Checks": [],
"Checks": [
"chat_apps_installation_disabled"
],
"Attributes": [
{
"Section": "3 Apps",
@@ -1189,7 +1199,9 @@
{
"Id": "3.1.4.4.2",
"Description": "Ensure allow users to add and use incoming webhooks is disabled",
"Checks": [],
"Checks": [
"chat_incoming_webhooks_disabled"
],
"Attributes": [
{
"Section": "3 Apps",
@@ -1466,7 +1466,9 @@
{
"Id": "GWS.CHAT.2.1",
"Description": "External file sharing SHALL be disabled to protect sensitive information from unauthorized or accidental sharing",
"Checks": [],
"Checks": [
"chat_external_file_sharing_disabled"
],
"Attributes": [
{
"Section": "Chat",
@@ -1492,7 +1494,9 @@
{
"Id": "GWS.CHAT.4.1",
"Description": "External chat messaging SHALL be restricted to allowlisted domains only",
"Checks": [],
"Checks": [
"chat_external_messaging_restricted"
],
"Attributes": [
{
"Section": "Chat",
@@ -0,0 +1,39 @@
{
"Provider": "googleworkspace",
"CheckID": "chat_apps_installation_disabled",
"CheckTitle": "Chat apps installation is disabled for users",
"CheckType": [],
"ServiceName": "chat",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
"Description": "Google Chat apps connect to external services to look up information, schedule meetings, or complete tasks. Apps are accounts created by Google, users in the organization, or third parties that can access user data including **email addresses**, **conversation content**, and **organizational information**.",
"Risk": "Unrestricted Chat app installation allows **unvetted third-party applications** to access user data including conversation content and organizational information. An attacker could distribute a malicious Chat app to **exfiltrate confidential data** or establish **persistent access** to internal communications.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://support.google.com/a/answer/6089179",
"https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Google **Admin console** at https://admin.google.com\n2. Navigate to **Apps** > **Google Workspace** > **Google Chat and classic Hangouts**\n3. Click **Chat apps**\n4. Under Chat apps access settings, set **Allow users to install Chat apps** to **OFF**\n5. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable Chat apps installation to prevent **unvetted third-party applications** from accessing organizational data through the Chat platform.",
"Url": "https://hub.prowler.com/check/chat_apps_installation_disabled"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [
"chat_incoming_webhooks_disabled"
],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGoogleWorkspace
from prowler.providers.googleworkspace.services.chat.chat_client import chat_client
class chat_apps_installation_disabled(Check):
"""Check that users cannot install Chat apps.
This check verifies that the domain-level Chat policy prevents users
from installing Chat apps, reducing the risk of data exposure through
third-party or unvetted applications.
"""
def execute(self) -> List[CheckReportGoogleWorkspace]:
findings = []
if chat_client.policies_fetched:
report = CheckReportGoogleWorkspace(
metadata=self.metadata(),
resource=chat_client.policies,
resource_id="chatPolicies",
resource_name="Chat Policies",
customer_id=chat_client.provider.identity.customer_id,
)
apps_enabled = chat_client.policies.enable_apps
if apps_enabled is False:
report.status = "PASS"
report.status_extended = (
f"Chat apps installation is disabled "
f"in domain {chat_client.provider.identity.domain}."
)
elif apps_enabled is None:
report.status = "PASS"
report.status_extended = (
f"Chat apps installation uses Google's secure default "
f"configuration (disabled) "
f"in domain {chat_client.provider.identity.domain}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Chat apps installation is enabled "
f"in domain {chat_client.provider.identity.domain}. "
f"Chat apps installation should be disabled to prevent unvetted apps."
)
findings.append(report)
return findings
@@ -0,0 +1,4 @@
from prowler.providers.common.provider import Provider
from prowler.providers.googleworkspace.services.chat.chat_service import Chat
chat_client = Chat(Provider.get_global_provider())
@@ -0,0 +1,40 @@
{
"Provider": "googleworkspace",
"CheckID": "chat_external_file_sharing_disabled",
"CheckTitle": "External file sharing in Chat is set to no files",
"CheckType": [],
"ServiceName": "chat",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
"Description": "Google Chat **external file sharing** controls whether users can share files with people outside the organization via Chat conversations. Files often contain **confidential information**, and organizations in regulated industries need to control the flow of this information outside their boundaries.",
"Risk": "Enabled external file sharing allows users to send files containing **confidential information** to external parties through Chat. This creates a **data leakage** channel that bypasses DLP controls, particularly dangerous for organizations handling **regulated data** such as PII, PHI, or financial records.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://support.google.com/a/answer/9540647",
"https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Google **Admin console** at https://admin.google.com\n2. Navigate to **Apps** > **Google Workspace** > **Google Chat and classic Hangouts**\n3. Click **Chat File Sharing**\n4. Under Setting, set **External filesharing** to **No files**\n5. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable **external file sharing** in Chat to prevent users from sharing files with people outside the organization through Chat conversations.",
"Url": "https://hub.prowler.com/check/chat_external_file_sharing_disabled"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [
"chat_internal_file_sharing_disabled",
"drive_sharing_allowlisted_domains"
],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGoogleWorkspace
from prowler.providers.googleworkspace.services.chat.chat_client import chat_client
class chat_external_file_sharing_disabled(Check):
"""Check that external file sharing in Google Chat is disabled.
This check verifies that the domain-level Chat policy prevents users
from sharing files with people outside the organization via Chat,
protecting sensitive information from unauthorized external access.
"""
def execute(self) -> List[CheckReportGoogleWorkspace]:
findings = []
if chat_client.policies_fetched:
report = CheckReportGoogleWorkspace(
metadata=self.metadata(),
resource=chat_client.policies,
resource_id="chatPolicies",
resource_name="Chat Policies",
customer_id=chat_client.provider.identity.customer_id,
)
external_sharing = chat_client.policies.external_file_sharing
if external_sharing == "NO_FILES":
report.status = "PASS"
report.status_extended = (
f"External file sharing in Chat is disabled "
f"in domain {chat_client.provider.identity.domain}."
)
else:
report.status = "FAIL"
if external_sharing is None:
report.status_extended = (
f"External file sharing in Chat is not explicitly configured "
f"in domain {chat_client.provider.identity.domain}. "
f"External file sharing should be set to No files."
)
else:
report.status_extended = (
f"External file sharing in Chat is set to {external_sharing} "
f"in domain {chat_client.provider.identity.domain}. "
f"External file sharing should be set to No files."
)
findings.append(report)
return findings
@@ -0,0 +1,40 @@
{
"Provider": "googleworkspace",
"CheckID": "chat_external_messaging_restricted",
"CheckTitle": "External Chat messaging is restricted to allowed domains",
"CheckType": [],
"ServiceName": "chat",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
"Description": "Google Chat **external messaging** controls whether users can send messages to people outside the organization. If external messaging is allowed, it can optionally be restricted to only **allowlisted domains** to limit the scope of external communication.",
"Risk": "Unrestricted external messaging allows users to communicate freely with **any external party**, increasing the risk of **data exfiltration** through conversation content and **social engineering attacks** from untrusted domains targeting internal users.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://support.google.com/a/answer/9540647",
"https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Google **Admin console** at https://admin.google.com\n2. Navigate to **Apps** > **Google Workspace** > **Google Chat and classic Hangouts**\n3. Click **External Chat Settings**\n4. Select **Chat externally**\n5. Set **Allow users to send messages outside the organization** to **ON**\n6. Check **Only allow this for allowlisted domains**\n7. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Restrict **external Chat messaging** to **allowlisted domains** only to limit information flow to trusted parties and reduce exposure to external threats.",
"Url": "https://hub.prowler.com/check/chat_external_messaging_restricted"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [
"chat_external_spaces_restricted",
"drive_sharing_allowlisted_domains"
],
"Notes": ""
}
@@ -0,0 +1,59 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGoogleWorkspace
from prowler.providers.googleworkspace.services.chat.chat_client import chat_client
class chat_external_messaging_restricted(Check):
"""Check that external Chat messaging is restricted to allowed domains.
This check verifies that external Chat messaging is either disabled
entirely or restricted to allowlisted domains only, preventing
unrestricted communication with external users.
"""
def execute(self) -> List[CheckReportGoogleWorkspace]:
findings = []
if chat_client.policies_fetched:
report = CheckReportGoogleWorkspace(
metadata=self.metadata(),
resource=chat_client.policies,
resource_id="chatPolicies",
resource_name="Chat Policies",
customer_id=chat_client.provider.identity.customer_id,
)
allow_external = chat_client.policies.allow_external_chat
restriction = chat_client.policies.external_chat_restriction
if allow_external is False:
report.status = "PASS"
report.status_extended = (
f"External Chat messaging is disabled "
f"in domain {chat_client.provider.identity.domain}."
)
elif allow_external is None and restriction is None:
report.status = "PASS"
report.status_extended = (
f"External Chat messaging uses Google's secure default "
f"configuration (disabled) "
f"in domain {chat_client.provider.identity.domain}."
)
elif restriction == "TRUSTED_DOMAINS":
report.status = "PASS"
report.status_extended = (
f"External Chat messaging is restricted to allowed domains "
f"in domain {chat_client.provider.identity.domain}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"External Chat messaging is not restricted to allowed domains "
f"in domain {chat_client.provider.identity.domain}. "
f"External messaging should be restricted to allowed domains only."
)
findings.append(report)
return findings
@@ -0,0 +1,40 @@
{
"Provider": "googleworkspace",
"CheckID": "chat_external_spaces_restricted",
"CheckTitle": "External spaces in Chat are restricted to allowed domains",
"CheckType": [],
"ServiceName": "chat",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
"Description": "Google Chat **external spaces** allow users to create or join collaborative spaces that include people outside the organization. If external spaces are allowed, they can optionally be restricted to only **allowlisted domains** to limit external participation.",
"Risk": "Unrestricted external spaces allow users to add **anyone from any domain** to persistent group conversations. This increases the risk of **confidential information exposure** in shared spaces and enables **unauthorized external access** to ongoing organizational discussions.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://support.google.com/a/answer/9540647",
"https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Google **Admin console** at https://admin.google.com\n2. Navigate to **Apps** > **Google Workspace** > **Google Chat and classic Hangouts**\n3. Click **External Spaces**\n4. Set **Allow users to create and join spaces with people outside their organization** to **ON**\n5. Check **Only allow users to add people from allowlisted domains**\n6. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Restrict **external spaces** to **allowlisted domains** only to control which external parties can participate in organizational Chat spaces.",
"Url": "https://hub.prowler.com/check/chat_external_spaces_restricted"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [
"chat_external_messaging_restricted",
"drive_sharing_allowlisted_domains"
],
"Notes": ""
}
@@ -0,0 +1,59 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGoogleWorkspace
from prowler.providers.googleworkspace.services.chat.chat_client import chat_client
class chat_external_spaces_restricted(Check):
"""Check that external spaces in Google Chat are restricted.
This check verifies that external spaces are either disabled entirely
or restricted to allowlisted domains only, preventing users from
creating or joining spaces with unrestricted external participants.
"""
def execute(self) -> List[CheckReportGoogleWorkspace]:
findings = []
if chat_client.policies_fetched:
report = CheckReportGoogleWorkspace(
metadata=self.metadata(),
resource=chat_client.policies,
resource_id="chatPolicies",
resource_name="Chat Policies",
customer_id=chat_client.provider.identity.customer_id,
)
spaces_enabled = chat_client.policies.external_spaces_enabled
allowlist_mode = chat_client.policies.external_spaces_domain_allowlist_mode
if spaces_enabled is False:
report.status = "PASS"
report.status_extended = (
f"External spaces are disabled "
f"in domain {chat_client.provider.identity.domain}."
)
elif allowlist_mode == "TRUSTED_DOMAINS":
report.status = "PASS"
report.status_extended = (
f"External spaces are restricted to allowed domains "
f"in domain {chat_client.provider.identity.domain}."
)
else:
report.status = "FAIL"
if spaces_enabled is None and allowlist_mode is None:
report.status_extended = (
f"External spaces restriction is not explicitly configured "
f"in domain {chat_client.provider.identity.domain}. "
f"External spaces should be restricted to allowed domains only."
)
else:
report.status_extended = (
f"External spaces are not restricted to allowed domains "
f"in domain {chat_client.provider.identity.domain}. "
f"External spaces should be restricted to allowed domains only."
)
findings.append(report)
return findings
@@ -0,0 +1,39 @@
{
"Provider": "googleworkspace",
"CheckID": "chat_incoming_webhooks_disabled",
"CheckTitle": "Incoming webhooks in Chat are disabled for users",
"CheckType": [],
"ServiceName": "chat",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
"Description": "**Incoming webhooks** let external applications post asynchronous messages into Google Chat spaces without being a Chat app. When enabled, users can configure webhooks and developers can call them to send content from **external applications**.",
"Risk": "Exposed webhook URLs allow **unauthorized content injection** into Chat spaces. Attackers can send **fraudulent or misleading messages** that appear to come from trusted services, creating a vector for **social engineering** and **phishing** within internal communications.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://support.google.com/a/answer/6089179",
"https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Google **Admin console** at https://admin.google.com\n2. Navigate to **Apps** > **Google Workspace** > **Google Chat and classic Hangouts**\n3. Click **Chat apps**\n4. Under Chat apps access settings, set **Allow users to add and use incoming webhooks** to **OFF**\n5. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable **incoming webhooks** to prevent unauthenticated external applications from **injecting content** into internal Chat spaces.",
"Url": "https://hub.prowler.com/check/chat_incoming_webhooks_disabled"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [
"chat_apps_installation_disabled"
],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGoogleWorkspace
from prowler.providers.googleworkspace.services.chat.chat_client import chat_client
class chat_incoming_webhooks_disabled(Check):
"""Check that incoming webhooks are disabled in Google Chat.
This check verifies that the domain-level Chat policy prevents users
from adding and using incoming webhooks, reducing the risk of
unauthorized content being posted into Chat spaces.
"""
def execute(self) -> List[CheckReportGoogleWorkspace]:
findings = []
if chat_client.policies_fetched:
report = CheckReportGoogleWorkspace(
metadata=self.metadata(),
resource=chat_client.policies,
resource_id="chatPolicies",
resource_name="Chat Policies",
customer_id=chat_client.provider.identity.customer_id,
)
webhooks_enabled = chat_client.policies.enable_webhooks
if webhooks_enabled is False:
report.status = "PASS"
report.status_extended = (
f"Incoming webhooks are disabled "
f"in domain {chat_client.provider.identity.domain}."
)
elif webhooks_enabled is None:
report.status = "PASS"
report.status_extended = (
f"Incoming webhooks use Google's secure default "
f"configuration (disabled) "
f"in domain {chat_client.provider.identity.domain}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Incoming webhooks are enabled "
f"in domain {chat_client.provider.identity.domain}. "
f"Incoming webhooks should be disabled to prevent unauthorized content."
)
findings.append(report)
return findings
@@ -0,0 +1,39 @@
{
"Provider": "googleworkspace",
"CheckID": "chat_internal_file_sharing_disabled",
"CheckTitle": "Internal file sharing in Chat is set to no files",
"CheckType": [],
"ServiceName": "chat",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
"Description": "Google Chat **internal file sharing** controls whether users can share files with other people inside the organization via Chat conversations. Organizations in regulated industries may need to **control and audit** all file sharing, even between internal users.",
"Risk": "Unrestricted internal file sharing in Chat allows files with **sensitive information** to be distributed freely without passing through approved channels. This undermines **data governance** and **audit trail** requirements, making it harder to track data movement within the organization.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://support.google.com/a/answer/9540647",
"https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Google **Admin console** at https://admin.google.com\n2. Navigate to **Apps** > **Google Workspace** > **Google Chat and classic Hangouts**\n3. Click **Chat File Sharing**\n4. Under Setting, set **Internal filesharing** to **No files**\n5. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable **internal file sharing** in Chat to enforce file distribution through **approved channels** with proper audit trails and governance controls.",
"Url": "https://hub.prowler.com/check/chat_internal_file_sharing_disabled"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [
"chat_external_file_sharing_disabled"
],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGoogleWorkspace
from prowler.providers.googleworkspace.services.chat.chat_client import chat_client
class chat_internal_file_sharing_disabled(Check):
"""Check that internal file sharing in Google Chat is disabled.
This check verifies that the domain-level Chat policy prevents users
from sharing files internally via Chat, providing maximum control over
file distribution within the organization.
"""
def execute(self) -> List[CheckReportGoogleWorkspace]:
findings = []
if chat_client.policies_fetched:
report = CheckReportGoogleWorkspace(
metadata=self.metadata(),
resource=chat_client.policies,
resource_id="chatPolicies",
resource_name="Chat Policies",
customer_id=chat_client.provider.identity.customer_id,
)
internal_sharing = chat_client.policies.internal_file_sharing
if internal_sharing == "NO_FILES":
report.status = "PASS"
report.status_extended = (
f"Internal file sharing in Chat is disabled "
f"in domain {chat_client.provider.identity.domain}."
)
else:
report.status = "FAIL"
if internal_sharing is None:
report.status_extended = (
f"Internal file sharing in Chat is not explicitly configured "
f"in domain {chat_client.provider.identity.domain}. "
f"Internal file sharing should be set to No files."
)
else:
report.status_extended = (
f"Internal file sharing in Chat is set to {internal_sharing} "
f"in domain {chat_client.provider.identity.domain}. "
f"Internal file sharing should be set to No files."
)
findings.append(report)
return findings
@@ -0,0 +1,125 @@
from typing import Optional
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.googleworkspace.lib.service.service import GoogleWorkspaceService
class Chat(GoogleWorkspaceService):
"""Google Workspace Chat service for auditing domain-level Chat policies.
Uses the Cloud Identity Policy API v1 to read Chat file sharing, external
messaging, spaces, and apps access settings configured in the Admin Console.
"""
def __init__(self, provider):
super().__init__(provider)
self.policies = ChatPolicies()
self.policies_fetched = False
self._fetch_chat_policies()
def _fetch_chat_policies(self):
"""Fetch Chat policies from the Cloud Identity Policy API v1."""
logger.info("Chat - Fetching Chat policies...")
try:
service = self._build_service("cloudidentity", "v1")
if not service:
logger.error("Failed to build Cloud Identity service")
return
request = service.policies().list(
pageSize=100,
filter='setting.type.matches("chat.*")',
)
fetch_succeeded = True
while request is not None:
try:
response = request.execute()
for policy in response.get("policies", []):
if not self._is_customer_level_policy(policy):
continue
setting = policy.get("setting", {})
setting_type = setting.get("type", "").removeprefix("settings/")
logger.debug(f"Processing setting type: {setting_type}")
value = setting.get("value", {})
if setting_type == "chat.chat_file_sharing":
self.policies.external_file_sharing = value.get(
"externalFileSharing"
)
self.policies.internal_file_sharing = value.get(
"internalFileSharing"
)
logger.debug("Chat file sharing settings fetched.")
elif setting_type == "chat.external_chat_restriction":
self.policies.allow_external_chat = value.get(
"allowExternalChat"
)
self.policies.external_chat_restriction = value.get(
"externalChatRestriction"
)
logger.debug(
"Chat external chat restriction settings fetched."
)
elif setting_type == "chat.chat_external_spaces":
self.policies.external_spaces_enabled = value.get("enabled")
self.policies.external_spaces_domain_allowlist_mode = (
value.get("domainAllowlistMode")
)
logger.debug("Chat external spaces settings fetched.")
elif setting_type == "chat.chat_apps_access":
self.policies.enable_apps = value.get("enableApps")
self.policies.enable_webhooks = value.get("enableWebhooks")
logger.debug("Chat apps access settings fetched.")
request = service.policies().list_next(request, response)
except Exception as error:
self._handle_api_error(
error,
"fetching Chat policies",
self.provider.identity.customer_id,
)
fetch_succeeded = False
break
self.policies_fetched = fetch_succeeded
logger.info("Chat policies fetched successfully.")
except Exception as error:
self._handle_api_error(
error,
"fetching Chat policies",
self.provider.identity.customer_id,
)
self.policies_fetched = False
class ChatPolicies(BaseModel):
"""Model for domain-level Chat policy settings."""
# chat.chat_file_sharing
external_file_sharing: Optional[str] = None
internal_file_sharing: Optional[str] = None
# chat.external_chat_restriction
allow_external_chat: Optional[bool] = None
external_chat_restriction: Optional[str] = None
# chat.chat_external_spaces
external_spaces_enabled: Optional[bool] = None
external_spaces_domain_allowlist_mode: Optional[str] = None
# chat.chat_apps_access
enable_apps: Optional[bool] = None
enable_webhooks: Optional[bool] = None
@@ -0,0 +1,119 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.chat.chat_service import ChatPolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestChatAppsInstallationDisabled:
def test_pass(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled import (
chat_apps_installation_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(enable_apps=False)
check = chat_apps_installation_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Chat Policies"
assert findings[0].resource_id == "chatPolicies"
assert findings[0].customer_id == CUSTOMER_ID
assert findings[0].resource == ChatPolicies(enable_apps=False).dict()
def test_fail_enabled(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled import (
chat_apps_installation_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(enable_apps=True)
check = chat_apps_installation_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enabled" in findings[0].status_extended
def test_pass_no_policy_set(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled import (
chat_apps_installation_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(enable_apps=None)
check = chat_apps_installation_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "secure default" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_apps_installation_disabled.chat_apps_installation_disabled import (
chat_apps_installation_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = ChatPolicies()
check = chat_apps_installation_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,149 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.chat.chat_service import ChatPolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestChatExternalFileSharingDisabled:
def test_pass(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled import (
chat_external_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(external_file_sharing="NO_FILES")
check = chat_external_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Chat Policies"
assert findings[0].resource_id == "chatPolicies"
assert findings[0].customer_id == CUSTOMER_ID
assert (
findings[0].resource
== ChatPolicies(external_file_sharing="NO_FILES").dict()
)
def test_fail_all_files(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled import (
chat_external_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(external_file_sharing="ALL_FILES")
check = chat_external_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "ALL_FILES" in findings[0].status_extended
def test_fail_images_only(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled import (
chat_external_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(external_file_sharing="IMAGES_ONLY")
check = chat_external_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "IMAGES_ONLY" in findings[0].status_extended
def test_fail_no_policy_set(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled import (
chat_external_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(external_file_sharing=None)
check = chat_external_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not explicitly configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_file_sharing_disabled.chat_external_file_sharing_disabled import (
chat_external_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = ChatPolicies()
check = chat_external_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,154 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.chat.chat_service import ChatPolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestChatExternalMessagingRestricted:
def test_pass_external_chat_disabled(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted import (
chat_external_messaging_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(allow_external_chat=False)
check = chat_external_messaging_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Chat Policies"
assert findings[0].resource_id == "chatPolicies"
assert findings[0].customer_id == CUSTOMER_ID
assert (
findings[0].resource == ChatPolicies(allow_external_chat=False).dict()
)
def test_pass_trusted_domains(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted import (
chat_external_messaging_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(
allow_external_chat=True,
external_chat_restriction="TRUSTED_DOMAINS",
)
check = chat_external_messaging_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "restricted to allowed domains" in findings[0].status_extended
def test_fail_no_restriction(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted import (
chat_external_messaging_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(
allow_external_chat=True,
external_chat_restriction="NO_RESTRICTION",
)
check = chat_external_messaging_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not restricted" in findings[0].status_extended
def test_pass_no_policy_set(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted import (
chat_external_messaging_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies()
check = chat_external_messaging_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "secure default" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_messaging_restricted.chat_external_messaging_restricted import (
chat_external_messaging_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = ChatPolicies()
check = chat_external_messaging_restricted()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,155 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.chat.chat_service import ChatPolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestChatExternalSpacesRestricted:
def test_pass_spaces_disabled(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted import (
chat_external_spaces_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(external_spaces_enabled=False)
check = chat_external_spaces_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Chat Policies"
assert findings[0].resource_id == "chatPolicies"
assert findings[0].customer_id == CUSTOMER_ID
assert (
findings[0].resource
== ChatPolicies(external_spaces_enabled=False).dict()
)
def test_pass_trusted_domains(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted import (
chat_external_spaces_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(
external_spaces_enabled=True,
external_spaces_domain_allowlist_mode="TRUSTED_DOMAINS",
)
check = chat_external_spaces_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "restricted to allowed domains" in findings[0].status_extended
def test_fail_all_domains(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted import (
chat_external_spaces_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(
external_spaces_enabled=True,
external_spaces_domain_allowlist_mode="ALL_DOMAINS",
)
check = chat_external_spaces_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not restricted" in findings[0].status_extended
def test_fail_no_policy_set(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted import (
chat_external_spaces_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies()
check = chat_external_spaces_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not explicitly configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_external_spaces_restricted.chat_external_spaces_restricted import (
chat_external_spaces_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = ChatPolicies()
check = chat_external_spaces_restricted()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,119 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.chat.chat_service import ChatPolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestChatIncomingWebhooksDisabled:
def test_pass(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled import (
chat_incoming_webhooks_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(enable_webhooks=False)
check = chat_incoming_webhooks_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Chat Policies"
assert findings[0].resource_id == "chatPolicies"
assert findings[0].customer_id == CUSTOMER_ID
assert findings[0].resource == ChatPolicies(enable_webhooks=False).dict()
def test_fail_enabled(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled import (
chat_incoming_webhooks_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(enable_webhooks=True)
check = chat_incoming_webhooks_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enabled" in findings[0].status_extended
def test_pass_no_policy_set(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled import (
chat_incoming_webhooks_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(enable_webhooks=None)
check = chat_incoming_webhooks_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "secure default" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_incoming_webhooks_disabled.chat_incoming_webhooks_disabled import (
chat_incoming_webhooks_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = ChatPolicies()
check = chat_incoming_webhooks_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,122 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.chat.chat_service import ChatPolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestChatInternalFileSharingDisabled:
def test_pass(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled import (
chat_internal_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(internal_file_sharing="NO_FILES")
check = chat_internal_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Chat Policies"
assert findings[0].resource_id == "chatPolicies"
assert findings[0].customer_id == CUSTOMER_ID
assert (
findings[0].resource
== ChatPolicies(internal_file_sharing="NO_FILES").dict()
)
def test_fail_all_files(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled import (
chat_internal_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(internal_file_sharing="ALL_FILES")
check = chat_internal_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "ALL_FILES" in findings[0].status_extended
def test_fail_no_policy_set(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled import (
chat_internal_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = ChatPolicies(internal_file_sharing=None)
check = chat_internal_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not explicitly configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled.chat_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.chat.chat_internal_file_sharing_disabled.chat_internal_file_sharing_disabled import (
chat_internal_file_sharing_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = ChatPolicies()
check = chat_internal_file_sharing_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,440 @@
from unittest.mock import MagicMock, patch
from googleapiclient.errors import HttpError
from httplib2 import Response as HttpResponse
from tests.providers.googleworkspace.googleworkspace_fixtures import (
ROOT_ORG_UNIT_ID,
set_mocked_googleworkspace_provider,
)
class TestChatService:
def test_chat_fetch_policies_all_settings(self):
"""Test fetching all 4 Chat policy settings from Cloud Identity API"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_credentials = MagicMock()
mock_session = MagicMock()
mock_session.credentials = mock_credentials
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/chat.chat_file_sharing",
"value": {
"externalFileSharing": "NO_FILES",
"internalFileSharing": "IMAGES_ONLY",
},
}
},
{
"setting": {
"type": "settings/chat.external_chat_restriction",
"value": {
"allowExternalChat": True,
"externalChatRestriction": "TRUSTED_DOMAINS",
},
}
},
{
"setting": {
"type": "settings/chat.chat_external_spaces",
"value": {
"enabled": True,
"domainAllowlistMode": "TRUSTED_DOMAINS",
},
}
},
{
"setting": {
"type": "settings/chat.chat_apps_access",
"value": {
"enableApps": False,
"enableWebhooks": False,
},
}
},
]
}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
assert chat.policies_fetched is True
assert chat.policies.external_file_sharing == "NO_FILES"
assert chat.policies.internal_file_sharing == "IMAGES_ONLY"
assert chat.policies.allow_external_chat is True
assert chat.policies.external_chat_restriction == "TRUSTED_DOMAINS"
assert chat.policies.external_spaces_enabled is True
assert (
chat.policies.external_spaces_domain_allowlist_mode == "TRUSTED_DOMAINS"
)
assert chat.policies.enable_apps is False
assert chat.policies.enable_webhooks is False
def test_chat_fetch_policies_empty_response(self):
"""Test handling empty policies response"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {"policies": []}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
assert chat.policies_fetched is True
assert chat.policies.external_file_sharing is None
assert chat.policies.allow_external_chat is None
assert chat.policies.enable_apps is None
assert chat.policies.enable_webhooks is None
def test_chat_fetch_policies_api_error(self):
"""Test handling of API errors during policy fetch"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_service.policies().list.side_effect = Exception("API Error")
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
assert chat.policies_fetched is False
assert chat.policies.external_file_sharing is None
def test_chat_fetch_policies_build_service_returns_none(self):
"""Test early return when _build_service fails to construct the client"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=None,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
assert chat.policies_fetched is False
assert chat.policies.external_file_sharing is None
def test_chat_fetch_policies_execute_raises(self):
"""Test inner except handler when request.execute() raises during pagination"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_request = MagicMock()
mock_request.execute.side_effect = Exception("Execute failed")
mock_service.policies().list.return_value = mock_request
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
assert chat.policies_fetched is False
assert chat.policies.external_file_sharing is None
def test_chat_fetch_policies_ignores_ou_and_group_level(self):
"""Test that OU-level and group-level policies are skipped, only customer-level used"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {
"policies": [
{
# Customer-level: no policyQuery → should be used
"setting": {
"type": "settings/chat.chat_apps_access",
"value": {"enableApps": False, "enableWebhooks": False},
}
},
{
# OU-level: has policyQuery.orgUnit → should be skipped
"policyQuery": {"orgUnit": "orgUnits/sales_team"},
"setting": {
"type": "settings/chat.chat_apps_access",
"value": {"enableApps": True, "enableWebhooks": True},
},
},
{
# Group-level: has policyQuery.group → should be skipped
"policyQuery": {"group": "groups/contractors"},
"setting": {
"type": "settings/chat.chat_file_sharing",
"value": {
"externalFileSharing": "ALL_FILES",
"internalFileSharing": "ALL_FILES",
},
},
},
{
# Customer-level: no policyQuery → should be used
"setting": {
"type": "settings/chat.chat_file_sharing",
"value": {
"externalFileSharing": "NO_FILES",
"internalFileSharing": "NO_FILES",
},
}
},
]
}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
assert chat.policies_fetched is True
assert chat.policies.enable_apps is False
assert chat.policies.external_file_sharing == "NO_FILES"
def test_chat_fetch_policies_accepts_root_ou(self):
"""Test that root-OU-scoped policies are accepted as customer-level"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {
"policies": [
{
# Root OU: matches provider's root_org_unit_id → should be accepted
"policyQuery": {"orgUnit": f"orgUnits/{ROOT_ORG_UNIT_ID}"},
"setting": {
"type": "settings/chat.chat_apps_access",
"value": {"enableApps": False, "enableWebhooks": True},
},
},
{
# Sub-OU: different orgUnit → should be skipped
"policyQuery": {"orgUnit": "orgUnits/sub_ou_sales"},
"setting": {
"type": "settings/chat.chat_file_sharing",
"value": {
"externalFileSharing": "ALL_FILES",
"internalFileSharing": "ALL_FILES",
},
},
},
]
}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
assert chat.policies_fetched is True
# Root OU policy accepted
assert chat.policies.enable_apps is False
assert chat.policies.enable_webhooks is True
# Sub-OU policy skipped
assert chat.policies.external_file_sharing is None
def test_chat_partial_fetch_marks_policies_fetched_false(self):
"""Regression: if page 1 returns valid data but page 2 raises an error,
policies_fetched must be False even though some policy values were stored."""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
# Page 1: returns valid Chat data
page1_response = {
"policies": [
{
"setting": {
"type": "settings/chat.chat_apps_access",
"value": {"enableApps": False, "enableWebhooks": False},
}
},
]
}
# Page 2 request raises HttpError 429
page1_request = MagicMock()
page1_request.execute.return_value = page1_response
page2_request = MagicMock()
page2_request.execute.side_effect = HttpError(
HttpResponse({"status": "429"}), b"Rate limit exceeded"
)
mock_service.policies().list.return_value = page1_request
mock_service.policies().list_next.return_value = page2_request
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.chat.chat_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.chat.chat_service import (
Chat,
)
chat = Chat(mock_provider)
# Page 1 data was stored
assert chat.policies.enable_apps is False
# But policies_fetched must be False because page 2 failed
assert chat.policies_fetched is False
def test_chat_policies_model(self):
"""Test ChatPolicies Pydantic model"""
from prowler.providers.googleworkspace.services.chat.chat_service import (
ChatPolicies,
)
policies = ChatPolicies(
external_file_sharing="NO_FILES",
internal_file_sharing="IMAGES_ONLY",
allow_external_chat=True,
external_chat_restriction="TRUSTED_DOMAINS",
external_spaces_enabled=True,
external_spaces_domain_allowlist_mode="TRUSTED_DOMAINS",
enable_apps=False,
enable_webhooks=False,
)
assert policies.external_file_sharing == "NO_FILES"
assert policies.internal_file_sharing == "IMAGES_ONLY"
assert policies.allow_external_chat is True
assert policies.external_chat_restriction == "TRUSTED_DOMAINS"
assert policies.external_spaces_enabled is True
assert policies.external_spaces_domain_allowlist_mode == "TRUSTED_DOMAINS"
assert policies.enable_apps is False
assert policies.enable_webhooks is False