ForcePilot/backend/package/yuxi/channel/security/allowlist.py
Kris bab30f2715
Some checks failed
Deploy VitePress site to Pages / build (push) Has been cancelled
Ruff Format Check / Ruff Format & Lint (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
feat:0715
2026-07-15 12:30:58 +08:00

99 lines
3.2 KiB
Python

"""多渠道网关白名单检查。"""
from __future__ import annotations
from typing import TYPE_CHECKING
from yuxi.channel.constants import InboundRejectionReason
from yuxi.channel.security.models import DmPolicy, GroupPolicy
from .policy import SecurityCheckResult
if TYPE_CHECKING:
from .registry import SecurityContext
class AllowlistChecker:
name = "allowlist"
default_priority = 100
async def check(self, ctx: SecurityContext) -> SecurityCheckResult | None:
"""SecurityChecker 协议入口。"""
policy = self._resolve_policy(ctx)
allowed = self.is_allowed(policy, ctx.inbound, ctx.resolved_sender_id)
if allowed:
return None
reason = (
InboundRejectionReason.DM_NOT_ALLOWED
if ctx.chat_type == "private"
else InboundRejectionReason.GROUP_NOT_ALLOWED
)
return SecurityCheckResult(allowed=False, reason=reason)
def _resolve_policy(self, ctx: SecurityContext) -> DmPolicy | GroupPolicy | None:
if ctx.chat_type == "private":
resolve = getattr(ctx.plugin, "resolve_dm_policy", None)
else:
resolve = getattr(ctx.plugin, "resolve_group_policy", None)
if resolve is not None:
return resolve(ctx.config, ctx.inbound.account_id)
return None
def is_allowed(
self,
policy: DmPolicy | GroupPolicy | None,
inbound: object,
resolved_sender_id: str | None = None,
) -> bool:
"""原有 check(policy, inbound, resolved_sender_id) 逻辑,改名以避免覆盖协议方法。"""
if policy is None:
return True
if isinstance(policy, DmPolicy):
if policy.mode == "open":
return True
if policy.mode == "deny":
return False
if policy.mode == "allow_from":
sender_id = resolved_sender_id
if sender_id is None:
sender_id = getattr(inbound, "sender_id", None)
return self._match_allowlist(
policy.allow_list,
sender_id,
getattr(inbound, "channel_type", ""),
)
return True
if isinstance(policy, GroupPolicy):
chat_type = getattr(inbound, "chat_type", None)
if chat_type != "private":
session_key = getattr(inbound, "session_key", None) or ""
if policy.deny_groups and session_key in policy.deny_groups:
return False
if policy.require_mention and not getattr(inbound, "is_at_bot", False):
return False
if policy.allow_groups:
return session_key in policy.allow_groups
return True
return True
def _match_allowlist(
self,
allow_list: list[str],
sender_id: str | None,
channel_type: str,
) -> bool:
if not sender_id:
return False
for entry in allow_list:
if entry == "*":
return True
if entry == sender_id:
return True
if entry == f"{channel_type}:{sender_id}":
return True
return False