完成权限系统

This commit is contained in:
2026-03-07 17:35:59 +08:00
parent e605527900
commit 230705f689
6 changed files with 232 additions and 25 deletions

View File

@ -30,7 +30,7 @@ steps:
volumes:
- name: docker-socket
path: /var/run/docker.sock
- name: 在容器中测试插件加载
- name: 在容器中进行若干测试
image: docker:dind
privileged: true
volumes:
@ -38,14 +38,8 @@ steps:
path: /var/run/docker.sock
commands:
- docker run --rm gitea.service.jazzwhom.top/mttu-developers/konabot:nightly-${DRONE_COMMIT_SHA} python scripts/test_plugin_load.py
- name: 在容器中测试 Playwright 工作正常
image: docker:dind
privileged: true
volumes:
- name: docker-socket
path: /var/run/docker.sock
commands:
- docker run --rm gitea.service.jazzwhom.top/mttu-developers/konabot:nightly-${DRONE_COMMIT_SHA} python scripts/test_playwright.py
- docker run --rm gitea.service.jazzwhom.top/mttu-developers/konabot:nightly-${DRONE_COMMIT_SHA} python -m pytest --cov-report term-missing:skip-covered
- name: 发送构建结果到 ntfy
image: parrazam/drone-ntfy
when:

5
bot.py
View File

@ -7,7 +7,6 @@ from nonebot.adapters.discord import Adapter as DiscordAdapter
from nonebot.adapters.minecraft import Adapter as MinecraftAdapter
from nonebot.adapters.onebot.v11 import Adapter as OnebotAdapter
from konabot.common import permsys
from konabot.common.log import init_logger
from konabot.common.nb.exc import BotExceptionMessage
from konabot.common.path import LOG_PATH
@ -57,11 +56,13 @@ def main():
nonebot.load_plugins("konabot/plugins")
nonebot.load_plugin("nonebot_plugin_analysis_bilibili")
from konabot.common import permsys
permsys.create_startup()
# 注册关闭钩子
@driver.on_shutdown
async def shutdown_handler():
async def _():
# 关闭全局数据库管理器
db_manager = get_global_db_manager()
await db_manager.close_all_connections()

View File

@ -1,7 +1,12 @@
from typing import Annotated
import nonebot
from nonebot.adapters import Event
from nonebot.params import Depends
from nonebot.rule import Rule
from konabot.common.database import DatabaseManager
from konabot.common.nb.is_admin import cfg
from konabot.common.pager import PagerQuery
from konabot.common.path import DATA_PATH
from konabot.common.permsys.entity import PermEntity, get_entity_chain
from konabot.common.permsys.migrates import execute_migration
@ -11,18 +16,23 @@ from konabot.common.permsys.repo import PermRepo
db = DatabaseManager(DATA_PATH / "perm.sqlite3")
_EntityLike = Event | PermEntity | list[PermEntity]
async def _to_entity_chain(el: _EntityLike):
if isinstance(el, Event):
return await get_entity_chain(el) # pragma: no cover
if isinstance(el, PermEntity):
return [el]
return el
class PermManager:
def __init__(self, db: DatabaseManager) -> None:
self.db = db
async def check_has_permission(
self, entities: Event | PermEntity | list[PermEntity], key: str
) -> bool:
if isinstance(entities, Event):
entities = await get_entity_chain(entities) # pragma: no cover
if isinstance(entities, PermEntity):
entities = [entities]
async def check_has_permission_info(self, entities: _EntityLike, key: str):
entities = await _to_entity_chain(entities)
key = key.removesuffix("*").removesuffix(".")
key_split = key.split(".")
key_split = [s for s in key_split if len(s) > 0]
@ -32,24 +42,31 @@ class PermManager:
async with self.db.get_conn() as conn:
repo = PermRepo(conn)
# for entity in entities:
# for k in keys:
# perm = await repo.get_perm_info(entity, k)
# if perm is not None:
# return perm
data = await repo.get_perm_info_batch(entities, keys)
for entity in entities:
for k in keys:
p = data.get((entity, k))
if p is not None:
return p
return False
return (entity, k, p)
return None
async def check_has_permission(self, entities: _EntityLike, key: str) -> bool:
res = await self.check_has_permission_info(entities, key)
if res is None:
return False
return res[2]
async def update_permission(self, entity: PermEntity, key: str, perm: bool | None):
async with self.db.get_conn() as conn:
repo = PermRepo(conn)
await repo.update_perm_info(entity, key, perm)
async def list_permission(self, entities: _EntityLike, query: PagerQuery):
entities = await _to_entity_chain(entities)
async with self.db.get_conn() as conn:
repo = PermRepo(conn)
return await repo.list_perm_info_batch(entities, query)
def perm_manager(_db: DatabaseManager | None = None) -> PermManager: # pragma: no cover
if _db is None:
@ -64,7 +81,24 @@ def create_startup(): # pragma: no cover
async def _():
async with db.get_conn() as conn:
await execute_migration(conn)
pm = perm_manager(db)
for account in cfg.admin_qq_account:
# ^ 这里的是超级管理员!!用环境变量定义的。
# 咕嘿嘿嘿!!!夺取全部权限!!!
await pm.update_permission(
PermEntity("ob11", "user", str(account)), "*", True
)
@driver.on_shutdown
async def _():
await db.close_all_connections()
DepPermManager = Annotated[PermManager, Depends(perm_manager)]
def require_permission(perm: str) -> Rule: # pragma: no cover
async def check_permission(event: Event, pm: DepPermManager) -> bool:
return await pm.check_has_permission(event, perm)
return Rule(check_permission)

View File

@ -21,6 +21,14 @@ class PermEntity:
external_id: str
def get_entity_chain_of_entity(entity: PermEntity) -> list[PermEntity]:
return [
PermEntity("sys", "global", "global"),
PermEntity(entity.platform, "global", "global"),
entity,
][::-1]
async def get_entity_chain(event: Event) -> list[PermEntity]: # pragma: no cover
entities = [PermEntity("sys", "global", "global")]

View File

@ -1,8 +1,11 @@
from dataclasses import dataclass
import math
from pathlib import Path
import aiosqlite
from konabot.common.pager import PagerQuery, PagerResult
from .entity import PermEntity
@ -178,3 +181,58 @@ class PermRepo:
rows = await cursor.fetchall()
return {(entity_ids[row[0]], row[1]): bool(row[2]) for row in rows}
async def list_perm_info_batch(
self, entities: list[PermEntity], pager: PagerQuery
) -> PagerResult[tuple[PermEntity, str, bool]]:
"""批量获取某个实体的权限信息
Args:
entities: PermEntity 列表
pager: PagerQuery 对象,即分页要求
Returns:
字典,键是 PermEntity值是权限条目和布尔的元组过滤掉所有空值
"""
entity_to_id = await self.get_entity_id_batch(entities)
id_to_entity = {v: k for k, v in entity_to_id.items()}
ordered_ids = [entity_to_id[e] for e in entities if e in entity_to_id]
placeholders = ", ".join("?" * len(ordered_ids))
order_by_cases = " ".join([f"WHEN ? THEN {i}" for i in range(len(ordered_ids))])
pagecount_sql = f"SELECT COUNT(*) FROM perm_info WHERE entity_id IN ({placeholders}) AND value IS NOT NULL;"
count_cursor = await self.conn.execute(pagecount_sql, tuple(ordered_ids))
total_count = (await count_cursor.fetchone() or (0,))[0]
sql = f"""
SELECT entity_id, config_key, value
FROM perm_info
WHERE entity_id IN ({placeholders})
AND value IS NOT NULL
ORDER BY
(CASE entity_id {order_by_cases} END) ASC,
config_key ASC
LIMIT ?
OFFSET ?;
"""
params = (
tuple(ordered_ids)
+ tuple(ordered_ids)
+ (
pager.page_size,
(pager.page_index - 1) * pager.page_size,
)
)
cursor = await self.conn.execute(sql, params)
rows = await cursor.fetchall()
# return {entity_ids[row[0]]: (row[1], bool(row[2])) for row in rows}
return PagerResult(
data=[(id_to_entity[row[0]], row[1], row[2]) for row in rows],
success=True,
message="",
page_count=math.ceil(total_count / pager.page_size),
query=pager,
)

View File

@ -0,0 +1,112 @@
from typing import Annotated
from nonebot.adapters import Event
from nonebot.params import Depends
from nonebot_plugin_alconna import Alconna, Args, Subcommand, UniMessage, on_alconna
from konabot.common.pager import PagerQuery
from konabot.common.permsys import DepPermManager, require_permission
from konabot.common.permsys.entity import PermEntity, get_entity_chain_of_entity
cmd = on_alconna(
Alconna(
"konaperm",
Subcommand(
"list",
Args["platform", str],
Args["entity_type", str],
Args["external_id", str],
Args["page?", int],
),
Subcommand(
"get",
Args["platform", str],
Args["entity_type", str],
Args["external_id", str],
Args["perm", str],
),
Subcommand(
"set",
Args["platform", str],
Args["entity_type", str],
Args["external_id", str],
Args["perm", str],
Args["val", str],
),
),
rule=require_permission("admin"),
)
async def _get_perm_entity_chain(platform: str, entity_type: str, external_id: str):
return get_entity_chain_of_entity(PermEntity(platform, entity_type, external_id))
_DepEntityChain = Annotated[list[PermEntity], Depends(_get_perm_entity_chain)]
def make_formatter(parent: PermEntity):
def _formatter(d: tuple[PermEntity, str, bool]):
permmark = {True: "[✅ ALLOW] ", False: "[❌ DENY] "}[d[2]]
inheritmark = ""
if parent != d[0]:
inheritmark = (
f"[继承自 {d[0].platform}.{d[0].entity_type}.{d[0].external_id}] "
)
return f"{permmark}{inheritmark}{d[1]}"
return _formatter
@cmd.assign("list")
async def list_permisison(
pm: DepPermManager,
ec: _DepEntityChain,
event: Event,
page: int = 1,
):
pq = PagerQuery(page, 10)
data = await pm.list_permission(ec, pq)
msg = data.to_unimessage(make_formatter(ec[0]))
await msg.send(event)
@cmd.assign("get")
async def get_permisison(
pm: DepPermManager,
ec: _DepEntityChain,
perm: str,
event: Event,
):
data = await pm.check_has_permission_info(ec, perm)
obj_s = f"{ec[0].platform}.{ec[0].entity_type}.{ec[0].external_id}"
if data is None:
await UniMessage.text(f"对象 {obj_s}{perm} 权限记录").send(event)
return
pe, k, p = data
inheritmark = ""
if ec[0] != pe or k != perm:
inheritmark = (
f"继承自 {pe.platform}.{pe.entity_type}.{pe.external_id}{k} 的设置,"
)
await UniMessage.text(f"{inheritmark}对象 {obj_s}{perm} 的权限为 {p}").send(
event
)
@cmd.assign("set")
async def set_permisison(
pm: DepPermManager,
ec: _DepEntityChain,
perm: str,
val: str,
event: Event,
):
if any(i == val.lower() for i in ("y", "yes", "allow", "true", "t")):
await pm.update_permission(ec[0], perm, True)
if any(i == val.lower() for i in ("n", "no", "deny", "false", "f")):
await pm.update_permission(ec[0], perm, False)
if any(i == val.lower() for i in ("null", "none")):
await pm.update_permission(ec[0], perm, None)
await get_permisison(pm, ec, perm, event)