No project description provided
Project description
<!-- markdownlint-disable MD033 MD036 MD041 -->
<p align="center">
<a href="https://v2.nonebot.dev/"><img src="https://v2.nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
</p>
<div align="center">
nonebot-plugin-access-control
============
_✨ Nonebot 权限控制插件 ✨_
</div>
<p align="center">
<a href="https://raw.githubusercontent.com/ssttkkl/nonebot-plugin-access-control/master/LICENSE">
<img src="https://img.shields.io/github/license/ssttkkl/nonebot-plugin-access-control.svg" alt="license">
</a>
<a href="https://pypi.python.org/pypi/nonebot-plugin-access-control">
<img src="https://img.shields.io/pypi/v/nonebot-plugin-access-control.svg" alt="pypi">
</a>
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
</p>
## 特点
- 功能
- [x] 支持针对用户/群组的权限开关
- [x] 支持限制同一用户在一定时间内的指令调用次数
- [x] 对未适配插件提供**插件级别**的控制支持
- 插件适配
- [x] 支持**功能级别**的细粒度控制
- [x] 支持对权限开关等事件进行订阅
- 拓展
- [x] 支持开发者拓展定义新的主体
- CLI插件
- [x] 支持通过控制台中执行`nb accctrl`进入权限控制台
## 使用
### 主体
#### 概念
当我们对用户进行权限控制时,我们想要的是针对拥有某种同样身份的所有用户进行配置,而不是对每一个具体用户分别配置。因此,我们引入主体的概念。
**主体(Subject)代表了拥有某种同样身份的用户的集合,也是设置权限的基本单位。**
当我们说用户具有某个主体,也就是说用户在该主体所代表的集合内。换句话说,主体就是一个用户所具有的身份。
一个用户通常拥有多个主体。举个例子:QQ上群组G的用户U发送了一条消息,该用户同时具有“用户U”、“群组G成员”、“QQ用户”、“Bot用户”这几个主体。同时QQ上群组G的用户V也发送了一条消息,该用户该用户同时具有“用户V”、“群组G成员”、“QQ用户”、“Bot用户”这几个主体。
当设置权限时,我们直接针对一个主体进行设置。当鉴权时,我们对用户的所有主体**按优先级从高到低的顺序**
,逐一检查是否设置了权限。一旦检查到某个主体设置了权限,就以该主体设置的权限作为该用户的权限。
回到上面的例子,假设我们对主体”群组G“禁用服务,但是对主体”用户V“启用服务。则用户U在群组G内将无法使用服务,但是用户V在群组G内可以使用。
在插件使用中,我们用一个字符串表示主体,例如`qq:12345678`表示QQ用户12345678、`qq:g87654321`表示QQ群组87654321。
我们约定:`all`表示所有用户、`superuser`表示超级用户、`<平台名>`表示所有此平台的用户、`<协议名>`
表示所有此协议的用户、所有与平台相关的主体均以`<平台名>:`开头、所有与协议相关的主体均以`<协议名>:`开头。
插件依赖[nonebot-plugin-session](https://github.com/noneplugin/nonebot-plugin-session)提取事件的主体,各适配器的主体定义如下:
- [OneBot V11](docs/onebot_v11.md)
- [OneBot V12](docs/onebot_v12.md)
- [Kaiheila](docs/kaiheila.md)
- [QQ Guild](docs/qqguild.md)
- Telegram:未测试,参考OneBot V12的定义
### 服务
**服务(Service)为一组能够进行权限控制的功能的集合。** 服务可以拥有子服务,通过树形结构组织服务,统一管理权限。
整个NoneBot是一个名为nonebot的服务,为树形结构的根节点,其直接子节点为所有插件。
一个插件是一个服务(PluginService),其父节点为nonebot。当插件未进行适配时,该插件只具有这一个PluginService。对插件进行适配,则需要从插件的PluginService创建SubService,为插件的Matcher等功能入口应用SubService。(参考下文插件适配章节)
(为防止意外发生,nonebot_plugin_access_control本身不可以进行权限开关)
当某主体需要检查某服务是否可用时,将从该服务开始检查是否为该主体配置了权限,若未配置则检查其父服务,以此类推直到检查到根节点nonebot。
换句话说,当鉴权时,我们逐一对用户的所有主体**按优先级从高到低的顺序**,**按服务的节点深度从深到浅的顺序**
检查该服务及其所有祖先节点配置的权限。因此,在节点深度较浅的服务配置的高优先级的主体配置,也会覆盖在节点深度较深的服务配置的低优先级的主体配置。
通过指令可查看所有服务及子服务层级:
- `/ac service ls`:列出所有服务与子服务层级
- `/ac service ls --srv <服务>`:列出服务的子服务层级
其中`<服务>`的格式如下:
- `nonebot`:对整个NoneBot进行开关
- `<插件名>`:对整个插件进行开关
- `<插件名>.<子服务名>.<子服务名>.....<子服务名>`:对插件内的某个子服务进行开关(需参照下文对插件进行配置)
### 鉴权
通过指令可实现权限开关:
- `/ac permission allow --sbj <主体> --srv <服务>`:为主体启用服务
- `/ac permission deny --sbj <主体> --srv <服务>`:为主体禁用服务
- `/ac permission rm --sbj <主体> --srv <服务>`:为主体删除服务权限配置
- `/ac permission ls`:列出所有已配置的权限
- `/ac permission ls --sbj <主体>`:列出主体已配置的服务权限
- `/ac permission ls --srv <服务>`:列出服务已配置的主体权限
- `/ac permission ls --sbj <主体> --srv <服务>`:列出主体与服务已配置的权限
其中`<服务>`的格式同上
### 限流
插件还提供了限流的功能(限制同一用户在一定时间内的指令调用次数)。
限流规则同样是针对服务及主体的。与鉴权不同的是,限流规则是串联的。应用在一个用户拥有的所有主体的限流规则,与应用在一个服务及其所有祖先服务的限流规则,会同时对用户产生限制。
举个例子,如果我们设置了”主体`all`每天最多调用100次`nonebot`服务“,”主体`qq:g87654321`
每分钟最多调用5次`nonebot_plugin_pixivbot`服务“。一位拥有`qq:12345678`、`qq:g87654321`、`qq`、`all`
这四个主体的用户尝试调用`nonebot_plugin_pixivbot.xxx`服务时,将同时受到以上两条规则的限制。
如果我不希望这样的串联,该怎么办?通过为规则设置”覆写“属性,可以覆盖掉所有优先级低于该规则的规则。规则的优先级比较规则如下:
- 若主体优先级不一致,按照主体优先级
- 否则,按照服务的父子关系,父服务优先级低于子服务
通过指令可对限流规则进行操作:
- `/ac limit add --sbj <主体> --srv <服务> --limit <次数> --span <时间间隔> [--overwrite]`
:为主体与服务添加限流规则(`--overwrite`:为规则设置”覆写“属性)
- `/ac limit rm <规则ID>`:删除限流规则
- `/ac limit ls`:列出所有已配置的限流规则
- `/ac limit ls --sbj <主体>`:列出主体已配置的限流规则
- `/ac limit ls --srv <服务>`:列出服务已配置的限流规则
- `/ac limit ls --sbj <主体> --srv <服务>`:列出主体与服务已配置的限流规则
- `/ac limit reset`:重置限流计数
其中`<服务>`的格式同上
### 指令一览
进行控制的指令为`/ac`,仅超级用户可用。(通过在配置文件中设置`SUPERUSERS`变量可设置超级用户)
(注意:0.3.0版本对指令进行了一次大的更改)
- 帮助
- `/ac help`:显示此帮助
- 权限控制
- `/ac permission allow --sbj <主体> --srv <服务>`:为主体启用服务
- `/ac permission deny --sbj <主体> --srv <服务>`:为主体禁用服务
- `/ac permission rm --sbj <主体> --srv <服务>`:为主体删除服务权限配置
- `/ac permission ls`:列出所有已配置的权限
- `/ac permission ls --sbj <主体>`:列出主体已配置的服务权限
- `/ac permission ls --srv <服务>`:列出服务已配置的主体权限
- `/ac permission ls --sbj <主体> --srv <服务>`:列出主体与服务已配置的权限
- 流量限制
- `/ac limit add --sbj <主体> --srv <服务> --limit <次数> --span <时间间隔> [--overwrite]`:为主体与服务添加限流规则
- `/ac limit rm <规则ID>`:删除限流规则
- `/ac limit ls`:列出所有已配置的限流规则
- `/ac limit ls --sbj <主体>`:列出主体已配置的限流规则
- `/ac limit ls --srv <服务>`:列出服务已配置的限流规则
- `/ac limit ls --sbj <主体> --srv <服务>`:列出主体与服务已配置的限流规则
- `/ac limit reset`:重置限流计数
- 服务查看
- `/ac service ls`:列出所有服务与子服务层级
- `/ac service ls --srv <服务>`:列出服务的子服务层级
- 主体测试
- `/ac subject`:列出消息发送者的所有主体
其中`<服务>`的格式如下:
- `nonebot`:对整个NoneBot进行开关
- `<插件名>`:对整个插件进行开关
- `<插件名>.<子服务名>.<子服务名>.....<子服务名>`:对插件内的某个子服务进行开关(需参照下文对插件进行配置)
### 示例
首先编辑配置文件,打开对未适配插件的支持:
```
ACCESS_CONTROL_AUTO_PATCH_ENABLED=true
```
假设bot加载了内置插件echo
```python
nonebot.load_builtin_plugins("echo")
```
#### 鉴权
执行下面的指令后,QQ用户12345678将无法调用指令`/echo`
```
/ac permission deny --sbj qq:12345678 --srv echo
```
执行下面的指令后,QQ用户12345678将无法调用所有指令
```
/ac permission deny --sbj all --srv nonebot
```
执行下面的指令后,QQ群组87654321的所有用户(QQ用户12345678除外)将无法调用指令`/echo`
```
/ac permission allow --sbj qq:12345678 --srv echo
/ac permission deny --sbj qq:g87654321 --srv echo
```
执行下面的指令后,所有用户将无法调用指令`/echo`
```
/ac permission deny --sbj all --srv echo
```
#### 限流
执行下面的指令后,所有用户每天只能调用100次任意指令,且每分钟只能调用三次指令`/echo`
```
/ac limit add --sbj all --srv nonebot --span 1d --limit 100
/ac limit add --sbj all --srv echo --span 1m --limit 3
```
执行下面的指令后,QQ群组87654321的所有用户(QQ用户12345678除外)每分钟只能调用三次指令`/echo`
(QQ用户12345678每分钟能够调用114514次指令`/echo`)
```
/ac limit add --sbj qq:g87654321 --srv echo --span 1m --limit 3
/ac limit add --sbj qq:12345678 --srv echo --span 1m --limit 114514 --overwrite
```
## 插件适配
完整代码:[src/nonebot_plugin_ac_demo/matcher_demo.py](src/nonebot_plugin_ac_demo/matcher_demo.py)
1. 创建一个名为nonebot_plugin_ac_demo的插件
2. 通过create_plugin_service函数创建一个PluginService实例(注意参数必须为插件包名)
```python
from nonebot import require
require("nonebot_plugin_access_control")
from nonebot_plugin_access_control.service import create_plugin_service
plugin_service = create_plugin_service("nonebot_plugin_ac_demo")
```
3. 通过PluginService.create_subservice创建SubService实例。调用`Service.patch_matcher()`
应用至Matcher,或在事件处理函数上应用装饰器`Service.patch_handle()`。(二选一)
```python
group1 = plugin_service.create_subservice("group1")
a_matcher = on_command('a')
a_service = group1.create_subservice('a')
a_service.patch_matcher(a_matcher)
@a_matcher.handle()
async def _(matcher: Matcher):
await matcher.send("a")
b_matcher = on_command('b')
b_service = group1.create_subservice('b')
b_service.patch_matcher(b_matcher)
@b_matcher.handle()
async def _(matcher: Matcher):
await matcher.send("b")
c_matcher = on_command('c')
c_service = plugin_service.create_subservice('c')
@c_matcher.handle()
@c_service.patch_handler() # 必须在 @c_matcher.handle() 之下
async def _(matcher: Matcher):
await matcher.send("c")
```
插件服务的结构如下所示:
![](docs/img/2.svg)
4. 通过指令配置服务权限
执行下面的指令后,所有用户将无法调用指令`/a`与`/b`
```
/ac permission deny --sbj all --srv nonebot_plugin_ac_demo.group1
```
执行下面的指令后,QQ用户12345678将无法调用指令`/a`
```
/ac permission deny --sbj qq:12345678 --srv nonebot_plugin_ac_demo.group1.a
```
执行下面的指令后,QQ群组87654321的所有用户将无法调用除`/c`以外的任何指令
```
/ac permission deny --sbj qq:g87654321 --srv nonebot_plugin_ac_demo
/ac permission allow --sbj qq:g87654321 --srv nonebot_plugin_ac_demo.c
```
5. 手动鉴权
对于非Matcher的功能入口(如APScheduler的定时任务等),需要开发者手动进行鉴权。
- 方法一:调用`service.check(Bot, Event)`方法,传入Bot及Event实例,返回bool值表示该用户是否具有权限
- 方法二:调用`service.check_by_subject(*str)`方法,传入主体字符串,返回bool值表示该用户是否具有权限
APScheduler示例:[src/nonebot_plugin_ac_demo/apscheduler_demo.py](src/nonebot_plugin_ac_demo/apscheduler_demo.py)
对于一些功能,如果希望在执行失败时不消耗限流次数,则需要开发者手动进行鉴权与限流。
```python
d_matcher = on_command('d')
d_service = plugin_service.create_subservice('d')
@d_matcher.handle()
async def _(bot: Bot, event: Event, matcher: Matcher):
if not await d_service.check(bot, event, acquire_rate_limit_token=False):
# 没有权限
await matcher.finish()
token = await d_service.acquire_token_for_rate_limit(bot, event)
if token is None:
# 已达到限流次数上线
await matcher.finish()
ok = do_your_logic()
if not ok:
# 功能执行失败时,收回限流消耗的次数
await token.retire()
await matcher.send("c")
```
6. 事件订阅
通过`service.on_set_permission`、`service.on_remove_permission`、`service.on_change_permission`方法可以订阅事件,具体如下表:
| 装饰器 | 事件类型 | 事件接收函数的参数 |
|-------------------------------------|---------------------------------------|-----------------------------|
| `service.on_set_permission` | 服务设置主体权限 | service:服务<br>permission:权限 |
| `service.on_remove_permission` | 服务删除主体权限 | service:服务<br>subject:主体 |
| `service.on_change_permission` | 服务变更主体权限(包括该服务及其所有祖先服务设置、删除权限导致的权限变更) | service:服务<br>permission:权限 |
| `service.on_add_rate_limit_rule` | 服务添加限流规则(该服务及其所有祖先服务添加限流规则时都会触发) | service:服务<br>rule:限流规则 |
| `service.on_remove_rate_limit_rule` | 服务删除限流规则(该服务及其所有祖先服务删除限流规则时都会触发) | service:服务<br>rule:限流规则 |
**调用事件接收函数时通过具名参数传参,因此事件接收函数的参数必须严格遵循参数名。**
事件订阅示例:[src/nonebot_plugin_ac_demo/event_demo.py](src/nonebot_plugin_ac_demo/event_demo.py)
## 拓展定义新的主体
主体提取器(Subject Extractor)用于提取事件发出用户所具有的主体。开发者可以在自己的插件中注册自定义的主体提取器,从而实现拓展定义新的主体。
主体提取器为一个函数(或可调用对象),其应该具有如下的函数签名,并通过`add_subject_extractor`装饰器注入到插件中:
```python
@add_subject_extractor
def your_custom_subject_extractor(bot: Bot, event: Event, current: Sequence[str]) -> Sequence[str]:
...
```
传入参数`bot`与`event`其义自明,而`current`参数表明当前阶段已经被提取出的主体,返回值表示经过该轮处理后提取出的主体。
在主体提取流程,插件会**按顺序依次**调用已注册的主体提取器,每一轮调用时,将上一轮的返回值作为这一轮传入的`current`
参数,并取最后一轮的返回值作为事件的主体。请注意维护主体提取器注册的顺序。(tips:如果你的主体提取器依赖其他插件中实现的主体提取器,可以依赖`nonebot`
提供的`require`机制控制主体提取器注册的顺序)
例如,下面这个主体提取器用于为OneBot V11协议中群管理/群主提取相应的主体:
```python
def extract_onebot_v11_group_role(bot: Bot, event: Event, current: Sequence[str]) -> Sequence[str]:
if bot.type != "OneBot V11":
return current
li = [*current]
group_id = getattr(event, "group_id", None)
sender: Optional['Sender'] = getattr(event, "sender", None)
if group_id is not None and sender is not None:
# 添加群管理/群主的subject(在"qq:g{group_id}"之前)
idx = li.index(f"qq:g{group_id}")
if sender.role == 'owner':
li.insert(idx, f"qq:group_owner")
li.insert(idx, f"qq:g{group_id}.group_owner")
if sender.role == 'owner' or sender.role == 'admin':
li.insert(idx, f"qq:group_admin")
li.insert(idx, f"qq:g{group_id}.group_admin")
return li
```
## CLI支持
可以通过控制台中执行`nb accctrl`进入权限控制台,使用方式与`/ac`指令类似。
## 配置项
### datastore_database_url
(来自[nonebot-plugin-datastore](https://github.com/he0119/nonebot-plugin-datastore))
数据库连接字符串,默认使用 SQLite 数据库
默认值:`sqlite+aiosqlite:///data_dir/data.db`
### access_control_default_permission
未设置权限时的默认行为
可选值:`allow`, `deny`
默认值:`allow`
### access_control_auto_patch_enabled
是否启用对未适配插件的权限控制
类型:`bool`
默认值:`False`
### access_control_auto_patch_ignore
对指定的未适配插件将不启用权限控制
类型:`List[str]`
默认值:`[]`
### access_control_reply_on_permission_denied_enabled
因无权限而拒绝执行时,是否向用户回复消息
类型:`bool`
默认值:`False`
### access_control_reply_on_permission_denied
因无权限而拒绝执行时向用户回复的消息
类型:`str`
默认值:`"你没有权限执行该指令"`
### access_control_reply_on_rate_limited_enabled
因到达最大次数而拒绝执行时,是否向用户回复消息
类型:`bool`
默认值:`False`
### access_control_reply_on_rate_limited
因到达最大次数而拒绝执行时向用户回复的消息
类型:`str`
### access_control_rate_limit_token_storage
限流计数使用的存储方式,支持内存存储(inmemory)与数据库存储(datastore)。
内存存储会在重启后重置限流计数,数据库存储则不会。同时数据库存储还能够实现多个NoneBot实例共享限流计数,适用于分布式Bot应用。
在性能上,内存存储优于数据库存储。默认使用的是内存存储。
可选值:`inmemory`, `datastore`
默认值:`inmemory`
## Q&A
### **本插件与[nonebot_plugin_rauthman](https://github.com/Lancercmd/nonebot_plugin_rauthman)
和[nonebot-plugin-manager](https://github.com/nonepkg/nonebot-plugin-manager)等其他权限管理插件有什么差别?**
[nonebot_plugin_rauthman](https://github.com/Lancercmd/nonebot_plugin_rauthman)
支持功能级别的细粒度权限控制,但是需要插件进行适配,对于未适配插件不起作用。[nonebot-plugin-manager](https://github.com/nonepkg/nonebot-plugin-manager)
则实现了非侵入式的权限控制。
本插件主要受这两个插件的启发,结合了这两个插件的优点。既支持非侵入式应用到原有插件,也支持对插件进行适配以获得更多feature。同时提供事件订阅机制,以便插件开发者更灵活处理权限。
同时,上述两款插件均只支持OneBot V11协议,而本插件设计之初就考虑到了除OneBot V11以外的协议,具有更强的可扩展性。
## 在线乞讨
<details><summary>点击请我打两把maimai</summary>
![](https://github.com/ssttkkl/ssttkkl/blob/main/afdian-ssttkkl.jfif)
</details>
## LICENSE
> MIT License
>
> Copyright (c) 2022 ssttkkl
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
>
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Close
Hashes for nonebot_plugin_access_control-0.7.0.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8d8489e8f0b67237573f95140b317bc5fa2736c3050374bc262fb2199f32e375 |
|
MD5 | 225456bcb71521e9b60dcd96721c41c8 |
|
BLAKE2b-256 | 26900c8f8dca3668b23c57dfc8f35a60c9480290784f67648e4c487ccbdde147 |
Close
Hashes for nonebot_plugin_access_control-0.7.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ef6b345add6ea2cc999dbdadd0b4974dfc00d4fcf53c4495b9a25b734eb581d2 |
|
MD5 | c6d6b579ffdef1950ee4fdfd00287a07 |
|
BLAKE2b-256 | e89a32f3e09ff0209bd4414ae80acb784a17e5f92998c7623653c1c582d5ecba |