完成权限系统
This commit is contained in:
10
.drone.yml
10
.drone.yml
@ -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
5
bot.py
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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")]
|
||||
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
112
konabot/plugins/perm_manage/__init__.py
Normal file
112
konabot/plugins/perm_manage/__init__.py
Normal 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)
|
||||
Reference in New Issue
Block a user