ForcePilot/backend/package/yuxi/channels/adapters/mattermost/client.py
Kris 002d601d1b feat(mattermost): 实现完整的 Mattermost 适配器模块
新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
2026-05-12 00:46:12 +08:00

82 lines
2.3 KiB
Python

from __future__ import annotations
import ipaddress
import socket
from urllib.parse import urlparse
_PRIVATE_NETWORKS = [
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("169.254.0.0/16"),
ipaddress.ip_network("fc00::/7"),
ipaddress.ip_network("::1/128"),
]
def is_private_url(url: str) -> bool:
"""检查 URL 是否指向内网地址 — SSRF 防护。"""
if not url:
return False
try:
parsed = urlparse(url)
hostname = parsed.hostname
if not hostname:
return False
if hostname in ("localhost", "localhost.localdomain"):
return True
addr = ipaddress.ip_address(hostname)
for network in _PRIVATE_NETWORKS:
if addr in network:
return True
return False
except ValueError:
try:
resolved = socket.getaddrinfo(hostname, None)
for family, _, _, _, sockaddr in resolved:
ip = sockaddr[0]
addr = ipaddress.ip_address(ip)
for network in _PRIVATE_NETWORKS:
if addr in network:
return True
except socket.gaierror:
pass
return False
return False
def validate_url_safety(url: str, dangerously_allow_private: bool = False) -> tuple[bool, str]:
"""验证 URL 安全性。返回 (is_safe, reason)。"""
if not url:
return False, "URL is empty"
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return False, f"Unsupported scheme: {parsed.scheme}"
if not dangerously_allow_private and is_private_url(url):
return False, f"URL points to private/internal network: {parsed.hostname}"
return True, "ok"
def check_dangerously_allow_private_network(config: dict) -> bool:
return bool(config.get("network", {}).get("dangerouslyAllowPrivateNetwork", False))
def normalize_mattermost_base_url(url: str) -> str:
url = url.strip()
url = url.rstrip("/")
if url.endswith("/api/v4"):
url = url[: -len("/api/v4")]
return url.rstrip("/")
def safe_url_for_driver(url: str) -> str:
return normalize_mattermost_base_url(url)