mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-12 23:40:03 +08:00
bugfix:修复notice事件扩散问题 (#2132)
* bugfix:修复notice事件扩散问题 * 优化并发调度 * bugfix:修复签到样式 * bugfix:功能调用统计修复 * bugfix:修复私聊时功能调用统计显示已退群问题 * 提高插件适配兼容性 * 优化发送队列 * 修改权限检查设计 * 继续修改权限检查设计 * 完善权限检查设计 * 优化sqlite配置 * 优化数据库初始化 * 代码整理,无用代码清理 * bugfix:修复启动时数据库校验问题 * bugfix:修复预算裁剪过于激进问题
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Literal
|
||||
|
||||
from zhenxun.services.cache.runtime_cache import _parse_block_modules
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
|
||||
from .auth.exception import IsSuperuserException, SkipPluginException
|
||||
from .auth_profile import PluginAuthProfile
|
||||
from .auth_snapshot import AuthSnapshot
|
||||
|
||||
PolicyEffect = Literal["allow", "deny", "skip", "defer"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PolicyDecision:
|
||||
effect: PolicyEffect
|
||||
reason: str = ""
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def allowed(self) -> bool:
|
||||
return self.effect == "allow"
|
||||
|
||||
@property
|
||||
def denied(self) -> bool:
|
||||
return self.effect == "deny"
|
||||
|
||||
@property
|
||||
def skipped(self) -> bool:
|
||||
return self.effect == "skip"
|
||||
|
||||
@property
|
||||
def deferred(self) -> bool:
|
||||
return self.effect == "defer"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PolicyPrincipal:
|
||||
user_id: str
|
||||
group_id: str | None = None
|
||||
channel_id: str | None = None
|
||||
is_superuser: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PolicyAction:
|
||||
name: str
|
||||
module: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PolicyResource:
|
||||
plugin: object
|
||||
profile: PluginAuthProfile
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PolicyContext:
|
||||
snapshot: AuthSnapshot
|
||||
route_skip_checks: bool = False
|
||||
allow_sleep_bypass: bool = False
|
||||
allow_group_sleep_bypass: bool = False
|
||||
|
||||
|
||||
class PolicyDecisionPoint:
|
||||
"""Structured permission decision helpers.
|
||||
|
||||
This layer mirrors existing auth semantics and deliberately does not add a
|
||||
new policy table. Side-effecting checks such as limit counters remain
|
||||
deferred to the old hooks.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _missing(snapshot: AuthSnapshot, name: str) -> bool:
|
||||
return name in snapshot.cache_misses
|
||||
|
||||
@staticmethod
|
||||
def _private_disabled(profile: PluginAuthProfile) -> bool:
|
||||
return profile.block_type == BlockType.PRIVATE
|
||||
|
||||
@staticmethod
|
||||
def _group_disabled(profile: PluginAuthProfile) -> bool:
|
||||
return profile.block_type == BlockType.GROUP
|
||||
|
||||
@staticmethod
|
||||
def _globally_disabled(profile: PluginAuthProfile) -> bool:
|
||||
return profile.block_type == BlockType.ALL and not profile.status
|
||||
|
||||
def decide(
|
||||
self,
|
||||
principal: PolicyPrincipal,
|
||||
action: PolicyAction,
|
||||
resource: PolicyResource,
|
||||
context: PolicyContext,
|
||||
) -> PolicyDecision:
|
||||
del action
|
||||
snapshot = context.snapshot
|
||||
profile = resource.profile
|
||||
if profile.hidden:
|
||||
return PolicyDecision("allow", "hidden_plugin_skip_auth")
|
||||
if context.route_skip_checks:
|
||||
return PolicyDecision("allow", "route_miss_skip_checks")
|
||||
if snapshot.ban_state is True and not principal.is_superuser:
|
||||
return PolicyDecision("deny", "user_or_group_banned")
|
||||
if profile.superuser_only and not principal.is_superuser:
|
||||
return PolicyDecision("deny", "superuser_required")
|
||||
return PolicyDecision("defer", "needs_legacy_hooks")
|
||||
|
||||
def decide_bot(self, context: PolicyContext) -> PolicyDecision:
|
||||
snapshot = context.snapshot
|
||||
bot_data = snapshot.bot_data
|
||||
if bot_data is None:
|
||||
if self._missing(snapshot, "bot"):
|
||||
return PolicyDecision("defer", "bot_cache_unavailable")
|
||||
return PolicyDecision("deny", "bot_not_found")
|
||||
if not bot_data.status and not context.allow_sleep_bypass:
|
||||
return PolicyDecision("deny", "bot_sleeping")
|
||||
module = snapshot.profile.module
|
||||
if module and self._module_in_block_string(module, bot_data.block_plugins):
|
||||
return PolicyDecision("deny", "bot_plugin_blocked")
|
||||
return PolicyDecision("allow", "bot_allowed")
|
||||
|
||||
def decide_group(self, context: PolicyContext) -> PolicyDecision:
|
||||
snapshot = context.snapshot
|
||||
if not snapshot.group_id:
|
||||
return PolicyDecision("skip", "not_group_event")
|
||||
group = snapshot.group
|
||||
profile = snapshot.profile
|
||||
if group is None:
|
||||
if self._missing(snapshot, "group"):
|
||||
return PolicyDecision("defer", "group_cache_unavailable")
|
||||
return PolicyDecision("deny", "group_not_found")
|
||||
if group.level < 0:
|
||||
return PolicyDecision("deny", "group_blacklisted")
|
||||
if (
|
||||
not group.status
|
||||
and not context.allow_group_sleep_bypass
|
||||
and not snapshot.is_superuser
|
||||
):
|
||||
return PolicyDecision("deny", "group_sleeping")
|
||||
if profile.level > group.level:
|
||||
return PolicyDecision("deny", "group_level_low")
|
||||
return PolicyDecision("allow", "group_allowed")
|
||||
|
||||
def decide_admin(self, context: PolicyContext) -> PolicyDecision:
|
||||
snapshot = context.snapshot
|
||||
profile = snapshot.profile
|
||||
if not profile.need_admin:
|
||||
return PolicyDecision("skip", "admin_not_required")
|
||||
if profile.plugin_type in {PluginType.SUPERUSER, PluginType.SUPER_AND_ADMIN}:
|
||||
if snapshot.is_superuser:
|
||||
return PolicyDecision("allow", "superuser")
|
||||
if profile.plugin_type == PluginType.SUPERUSER:
|
||||
return PolicyDecision("deny", "superuser_required")
|
||||
if not profile.admin_level:
|
||||
return PolicyDecision("skip", "admin_level_empty")
|
||||
if snapshot.admin_levels is None:
|
||||
return PolicyDecision("defer", "admin_levels_unavailable")
|
||||
global_user, group_user = snapshot.admin_levels
|
||||
user_level = global_user.user_level if global_user else 0
|
||||
if snapshot.group_id and group_user:
|
||||
user_level = max(user_level, group_user.user_level)
|
||||
if user_level < profile.admin_level:
|
||||
return PolicyDecision("deny", "admin_level_low")
|
||||
return PolicyDecision("allow", "admin_allowed")
|
||||
|
||||
def decide_plugin(self, context: PolicyContext) -> PolicyDecision:
|
||||
snapshot = context.snapshot
|
||||
profile = snapshot.profile
|
||||
group = snapshot.group
|
||||
if snapshot.is_superuser:
|
||||
return PolicyDecision("allow", "superuser")
|
||||
if snapshot.group_id:
|
||||
if group is None:
|
||||
if self._missing(snapshot, "group"):
|
||||
return PolicyDecision("defer", "group_cache_unavailable")
|
||||
return PolicyDecision("deny", "group_not_found")
|
||||
if profile.status and not self._group_disabled(profile):
|
||||
block_set, super_block_set = self._group_block_sets(group)
|
||||
if not block_set and not super_block_set:
|
||||
return PolicyDecision("allow", "plugin_group_fast_allow")
|
||||
block_set, super_block_set = self._group_block_sets(group)
|
||||
if profile.module in super_block_set:
|
||||
return PolicyDecision("deny", "plugin_superuser_blocked_in_group")
|
||||
if profile.module in block_set:
|
||||
return PolicyDecision("deny", "plugin_blocked_in_group")
|
||||
if self._group_disabled(profile):
|
||||
return PolicyDecision("deny", "plugin_disabled_in_group")
|
||||
elif self._private_disabled(profile):
|
||||
return PolicyDecision("deny", "plugin_disabled_in_private")
|
||||
if self._globally_disabled(profile):
|
||||
if group is not None and getattr(group, "is_super", False):
|
||||
return PolicyDecision("allow", "super_group_bypass")
|
||||
return PolicyDecision("deny", "plugin_global_disabled")
|
||||
return PolicyDecision("allow", "plugin_allowed")
|
||||
|
||||
@staticmethod
|
||||
def _group_block_sets(group: object) -> tuple[frozenset[str], frozenset[str]]:
|
||||
block_set = getattr(group, "block_plugin_set", None)
|
||||
super_block_set = getattr(group, "superuser_block_plugin_set", None)
|
||||
if block_set is None:
|
||||
block_set = _parse_block_modules(getattr(group, "block_plugin", "") or "")
|
||||
setattr(group, "block_plugin_set", block_set)
|
||||
if super_block_set is None:
|
||||
super_block_set = _parse_block_modules(
|
||||
getattr(group, "superuser_block_plugin", "") or ""
|
||||
)
|
||||
setattr(group, "superuser_block_plugin_set", super_block_set)
|
||||
return block_set, super_block_set
|
||||
|
||||
@staticmethod
|
||||
def _module_in_block_string(module: str, value: str | None) -> bool:
|
||||
if not value:
|
||||
return False
|
||||
return CommonUtils.format(module) in value or module in _parse_block_modules(
|
||||
value
|
||||
)
|
||||
|
||||
|
||||
def principal_from_snapshot(snapshot: AuthSnapshot) -> PolicyPrincipal:
|
||||
return PolicyPrincipal(
|
||||
user_id=snapshot.user_id,
|
||||
group_id=snapshot.group_id,
|
||||
channel_id=snapshot.channel_id,
|
||||
is_superuser=snapshot.is_superuser,
|
||||
)
|
||||
|
||||
|
||||
def action_from_snapshot(snapshot: AuthSnapshot) -> PolicyAction:
|
||||
return PolicyAction(name="invoke_plugin", module=snapshot.module)
|
||||
|
||||
|
||||
def resource_from_snapshot(snapshot: AuthSnapshot) -> PolicyResource:
|
||||
return PolicyResource(plugin=snapshot.plugin, profile=snapshot.profile)
|
||||
|
||||
|
||||
def raise_for_policy(decision: PolicyDecision, message: str | None = None) -> None:
|
||||
if decision.denied:
|
||||
raise SkipPluginException(message or decision.reason)
|
||||
if decision.allowed and decision.reason == "super_group_bypass":
|
||||
raise IsSuperuserException()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PolicyAction",
|
||||
"PolicyContext",
|
||||
"PolicyDecision",
|
||||
"PolicyDecisionPoint",
|
||||
"PolicyPrincipal",
|
||||
"PolicyResource",
|
||||
"action_from_snapshot",
|
||||
"principal_from_snapshot",
|
||||
"raise_for_policy",
|
||||
"resource_from_snapshot",
|
||||
]
|
||||
Reference in New Issue
Block a user