ForcePilot/backend/package/yuxi/channels/adapters/zalo_oa/security.py

128 lines
4.1 KiB
Python
Raw Normal View History

from __future__ import annotations
import enum
from typing import Any
ALLOW_ENTRY_PREFIXES = ("zoa:", "zalo_oa:")
class DMPolicy(enum.StrEnum):
OPEN = "open"
PAIRING = "pairing"
ALLOWLIST = "allowlist"
DISABLED = "disabled"
def normalize_allow_entry(entry: str) -> str:
for prefix in ALLOW_ENTRY_PREFIXES:
if entry.lower().startswith(prefix):
return entry[len(prefix) :]
return entry
def resolve_dm_policy(config: dict[str, Any]) -> DMPolicy:
policy_str = config.get("dm_policy", config.get("dmPolicy", ""))
if policy_str == "open":
return DMPolicy.OPEN
if policy_str == "pairing":
return DMPolicy.PAIRING
if policy_str == "allowlist":
return DMPolicy.ALLOWLIST
if policy_str == "disabled":
return DMPolicy.DISABLED
return DMPolicy.OPEN
def load_allowlist(config: dict[str, Any]) -> set[str]:
raw = config.get("allowFrom", [])
if not isinstance(raw, list):
raw = []
if "*" in raw:
return {"*"}
return {normalize_allow_entry(str(entry)) for entry in raw}
def check_dm_allowed(user_id: str, policy: DMPolicy, allowlist: set[str]) -> bool:
if policy == DMPolicy.OPEN:
return True
if policy == DMPolicy.DISABLED:
return False
if policy == DMPolicy.ALLOWLIST:
if "*" in allowlist:
return True
return user_id in allowlist
if policy == DMPolicy.PAIRING:
if "*" in allowlist:
return True
return user_id in allowlist
return False
def collect_security_warnings(config: dict[str, Any]) -> list[dict[str, Any]]:
warnings: list[dict[str, Any]] = []
policy = resolve_dm_policy(config)
allowlist = load_allowlist(config)
webhook = config.get("webhook", {})
if policy == DMPolicy.OPEN:
warnings.append(
{
"severity": "warning",
"type": "dm_policy_open",
"message": "DM policy is 'open' — any follower can send messages without restriction",
"recommendation": "Consider switching to 'allowlist' or 'pairing' for production",
}
)
if policy == DMPolicy.ALLOWLIST and not allowlist:
warnings.append(
{
"severity": "error",
"type": "empty_allowlist",
"message": "DM policy is 'allowlist' but allowFrom is empty — no users can message the OA",
"recommendation": "Add user IDs to allowFrom or change policy to 'open'",
}
)
if policy == DMPolicy.PAIRING and not allowlist and "*" not in allowlist:
warnings.append(
{
"severity": "info",
"type": "pairing_no_preset",
"message": "DM policy is 'pairing' with empty allowFrom — all pairing requests will be processed",
"recommendation": "Add pre-approved user IDs to allowFrom for faster onboarding",
}
)
if policy == DMPolicy.ALLOWLIST and "*" in allowlist:
warnings.append(
{
"severity": "info",
"type": "allowlist_wildcard",
"message": "allowFrom contains '*' wildcard — effectively same as 'open' policy",
"recommendation": "Use '*' only in development or use 'open' policy explicitly",
}
)
if not webhook.get("url"):
warnings.append(
{
"severity": "info",
"type": "webhook_not_configured",
"message": "Webhook URL not configured — messages will only be received via polling",
"recommendation": "Configure a webhook URL for real-time message delivery",
}
)
if not webhook.get("mac_key"):
warnings.append(
{
"severity": "warning",
"type": "webhook_mac_key_missing",
"message": "Webhook MAC key not configured — webhook signature verification is disabled",
"recommendation": "Set ZALO_WEBHOOK_MAC_KEY or webhook.mac_key to enable signature verification",
}
)
return warnings