2026-05-12 00:46:12 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
from yuxi.channels.auth.ssrf_guard import (
|
|
|
|
|
is_private_url as _is_private_url,
|
|
|
|
|
)
|
|
|
|
|
from yuxi.channels.auth.ssrf_guard import (
|
|
|
|
|
validate_url_with_whitelist,
|
|
|
|
|
)
|
2026-05-12 00:46:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_private_url(url: str) -> bool:
|
2026-05-13 16:12:02 +08:00
|
|
|
return _is_private_url(url)
|
2026-05-12 00:46:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_url_safety(url: str, dangerously_allow_private: bool = False) -> tuple[bool, str]:
|
|
|
|
|
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"
|
|
|
|
|
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
def validate_mattermost_server_url(url: str, dangerously_allow_private: bool = False) -> str:
|
|
|
|
|
return validate_url_with_whitelist(
|
|
|
|
|
url,
|
|
|
|
|
allowed_hosts=None,
|
|
|
|
|
enforce_https=True,
|
|
|
|
|
allow_private=dangerously_allow_private,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-05-12 00:46:12 +08:00
|
|
|
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)
|