fix: tighten rag permissions and area scope
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
# 当前系统权限模型收口方案
|
||||||
|
|
||||||
|
## 1. 当前角色与地区范围
|
||||||
|
|
||||||
|
### 1.1 角色定义
|
||||||
|
|
||||||
|
- `common`: 普通用户,仅用于查看和使用。
|
||||||
|
- `admin`: 市级管理员,只应管理本地区资源。
|
||||||
|
- `provincial_admin`: 省级管理员,可管理全省资源。
|
||||||
|
- `super_admin`: 超级管理员,可管理全部资源。
|
||||||
|
|
||||||
|
### 1.2 知识库查看范围
|
||||||
|
|
||||||
|
当前后端 `RAG` 知识库查看逻辑:
|
||||||
|
|
||||||
|
- `provincial_admin` / `super_admin`
|
||||||
|
- 可查看全部启用中的知识库。
|
||||||
|
- 其他角色
|
||||||
|
- 可查看 `自己地区 + 省级 + 空地区 + 公共知识库`。
|
||||||
|
|
||||||
|
对应业务含义:
|
||||||
|
|
||||||
|
- 梅州用户只能看到梅州、`省级`、空地区和公共知识库。
|
||||||
|
- 潮州用户只能看到潮州、`省级`、空地区和公共知识库。
|
||||||
|
- 省级管理员可看到全部地区知识库。
|
||||||
|
|
||||||
|
## 2. 当前真实生效矩阵
|
||||||
|
|
||||||
|
| 角色 | 查看知识库 | 创建知识库 | 编辑知识库 | 删除知识库 | 上传/重处理文档 | 编辑分段 | 聊天 | 消息反馈 |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| `common` | 仅限可见范围 | 否 | 否 | 否 | 否 | 否 | 取决于 `rag:chat:use` | 取决于 `rag:message:feedback` |
|
||||||
|
| `admin` | 本地区 + 省级 + 空地区 + 公共 | 是,但仅本地区 | 是,但仅本地区 | 是,但仅本地区且非默认库 | 是,但仅本地区 | 是,但仅本地区 | 取决于权限键 | 取决于权限键 |
|
||||||
|
| `provincial_admin` | 全部 | 是 | 是 | 是,但默认库不可直接删 | 是 | 是 | 取决于权限键 | 取决于权限键 |
|
||||||
|
| `super_admin` | 全部 | 是 | 是 | 是,但默认库不可直接删 | 是 | 是 | 取决于权限键 | 取决于权限键 |
|
||||||
|
|
||||||
|
## 3. 现状问题
|
||||||
|
|
||||||
|
### 3.1 前后端权限键不统一
|
||||||
|
|
||||||
|
历史上前端混用了 `dify:*` 和 `rag:*`,后端主要检查 `rag:*`,导致:
|
||||||
|
|
||||||
|
- 前端按钮可见性与后端接口放行口径不一致。
|
||||||
|
- 管理页可能“看得到但提交失败”,或者“前端隐藏了但后端其实能改”。
|
||||||
|
|
||||||
|
### 3.2 前端存在硬编码放行
|
||||||
|
|
||||||
|
旧逻辑存在两类兜底:
|
||||||
|
|
||||||
|
- `admin / provincial_admin / super_admin` 角色直接视为全部有权限。
|
||||||
|
- 所有 `:read` 权限默认放行。
|
||||||
|
|
||||||
|
这会绕过数据库中的精细化 `GRANT / DENY` 配置。
|
||||||
|
|
||||||
|
### 3.3 `admin` 只在前端限制地区,后端未强校验
|
||||||
|
|
||||||
|
旧逻辑里市级管理员在界面上只能点自己地区,但后端没有统一限制,存在跨地区管理风险。
|
||||||
|
|
||||||
|
## 4. 本次收口目标
|
||||||
|
|
||||||
|
### 4.1 知识库统一使用 RAG 权限键
|
||||||
|
|
||||||
|
知识库管理统一采用以下权限键:
|
||||||
|
|
||||||
|
- `rag:dataset:read`: 查看知识库与文档
|
||||||
|
- `rag:dataset:manage`: 查看知识库配置管理页
|
||||||
|
- `rag:dataset:create`: 创建知识库
|
||||||
|
- `rag:dataset:update`: 更新知识库、设置、文档、分段
|
||||||
|
- `rag:dataset:delete`: 删除知识库、文档、分段
|
||||||
|
|
||||||
|
### 4.2 前端权限判断原则
|
||||||
|
|
||||||
|
前端只允许以下来源决定权限:
|
||||||
|
|
||||||
|
- 当前路由的 `permissionMap`
|
||||||
|
- 登录态携带的 `globalPermissions`
|
||||||
|
|
||||||
|
明确移除:
|
||||||
|
|
||||||
|
- 角色直接等于全权限
|
||||||
|
- `:read` 默认放行
|
||||||
|
|
||||||
|
### 4.3 后端地区校验原则
|
||||||
|
|
||||||
|
后端 service 层强制执行:
|
||||||
|
|
||||||
|
- `admin` 只能管理自己地区知识库
|
||||||
|
- `provincial_admin` / `super_admin` 可跨地区管理
|
||||||
|
- `common` 不能执行知识库管理操作
|
||||||
|
|
||||||
|
## 5. 前后端分工
|
||||||
|
|
||||||
|
### 5.1 前端负责
|
||||||
|
|
||||||
|
- 按真实权限控制菜单、页签、按钮显隐
|
||||||
|
- 对无权限操作给出提示
|
||||||
|
- 不再使用角色硬编码做兜底
|
||||||
|
|
||||||
|
### 5.2 后端负责
|
||||||
|
|
||||||
|
- 接口最终鉴权
|
||||||
|
- 地区范围最终鉴权
|
||||||
|
- 默认知识库不可直接删除等业务规则校验
|
||||||
|
|
||||||
|
## 6. 迁移建议
|
||||||
|
|
||||||
|
### 6.1 角色授权建议
|
||||||
|
|
||||||
|
建议角色权限配置如下:
|
||||||
|
|
||||||
|
- `common`
|
||||||
|
- `rag:dataset:read`
|
||||||
|
- `rag:app:read`
|
||||||
|
- `rag:chat:use`
|
||||||
|
- `rag:conversation:read`
|
||||||
|
- `rag:message:feedback`
|
||||||
|
- `admin`
|
||||||
|
- 在 `common` 基础上增加:
|
||||||
|
- `rag:dataset:manage`
|
||||||
|
- `rag:dataset:create`
|
||||||
|
- `rag:dataset:update`
|
||||||
|
- `rag:dataset:delete`
|
||||||
|
- `provincial_admin`
|
||||||
|
- 同 `admin`,但地区范围为全省
|
||||||
|
- `super_admin`
|
||||||
|
- 同 `provincial_admin`
|
||||||
|
|
||||||
|
### 6.2 数据配置建议
|
||||||
|
|
||||||
|
除代码收口外,还需要同步确认:
|
||||||
|
|
||||||
|
- 角色是否已经实际分配 `rag:dataset:manage/create/update/delete`
|
||||||
|
- `/chat-with-llm` 路由是否已配置给需要访问知识库管理的人群
|
||||||
|
- 旧 `dify:*` 权限是否仍在数据库中使用,若有则需制定清理计划
|
||||||
|
|
||||||
|
## 7. 文档落点
|
||||||
|
|
||||||
|
本方案文档固定存放于:
|
||||||
|
|
||||||
|
- `docs/RAG/当前系统权限模型收口方案.md`
|
||||||
|
|
||||||
@@ -53,6 +53,10 @@ class RagChatController(BaseController):
|
|||||||
"message_feedback": "rag:message:feedback",
|
"message_feedback": "rag:message:feedback",
|
||||||
"app_read": "rag:app:read",
|
"app_read": "rag:app:read",
|
||||||
"dataset_read": "rag:dataset:read",
|
"dataset_read": "rag:dataset:read",
|
||||||
|
"dataset_manage": "rag:dataset:manage",
|
||||||
|
"dataset_create": "rag:dataset:create",
|
||||||
|
"dataset_update": "rag:dataset:update",
|
||||||
|
"dataset_delete": "rag:dataset:delete",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -102,7 +106,7 @@ class RagChatController(BaseController):
|
|||||||
pageSize: int = Query(20, ge=1, le=200),
|
pageSize: int = Query(20, ge=1, le=200),
|
||||||
payload: dict[str, Any] = Depends(verify_access_token),
|
payload: dict[str, Any] = Depends(verify_access_token),
|
||||||
):
|
):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_manage"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有管理知识库权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有管理知识库权限", "data": None})
|
||||||
data = await self.RagDatasetService.GetAdminDatasets(
|
data = await self.RagDatasetService.GetAdminDatasets(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
@@ -117,7 +121,7 @@ class RagChatController(BaseController):
|
|||||||
|
|
||||||
@self.router.post("/datasets/admin", response_model=Result[RagDatasetDetailVO])
|
@self.router.post("/datasets/admin", response_model=Result[RagDatasetDetailVO])
|
||||||
async def CreateAdminDataset(Body: dict[str, Any], payload: dict[str, Any] = Depends(verify_access_token)):
|
async def CreateAdminDataset(Body: dict[str, Any], payload: dict[str, Any] = Depends(verify_access_token)):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_create"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有创建知识库权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有创建知识库权限", "data": None})
|
||||||
data = await self.RagDatasetService.CreateAdminDataset(
|
data = await self.RagDatasetService.CreateAdminDataset(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
@@ -129,7 +133,7 @@ class RagChatController(BaseController):
|
|||||||
|
|
||||||
@self.router.put("/datasets/admin/{DatasetId}", response_model=Result[RagDatasetDetailVO | None])
|
@self.router.put("/datasets/admin/{DatasetId}", response_model=Result[RagDatasetDetailVO | None])
|
||||||
async def UpdateAdminDataset(DatasetId: int, Body: dict[str, Any], payload: dict[str, Any] = Depends(verify_access_token)):
|
async def UpdateAdminDataset(DatasetId: int, Body: dict[str, Any], payload: dict[str, Any] = Depends(verify_access_token)):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_update"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有更新知识库权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有更新知识库权限", "data": None})
|
||||||
data = await self.RagDatasetService.UpdateAdminDataset(
|
data = await self.RagDatasetService.UpdateAdminDataset(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
@@ -142,7 +146,7 @@ class RagChatController(BaseController):
|
|||||||
|
|
||||||
@self.router.delete("/datasets/admin/{DatasetId}", response_model=Result[RagOperationResultVO])
|
@self.router.delete("/datasets/admin/{DatasetId}", response_model=Result[RagOperationResultVO])
|
||||||
async def DeleteAdminDataset(DatasetId: int, payload: dict[str, Any] = Depends(verify_access_token)):
|
async def DeleteAdminDataset(DatasetId: int, payload: dict[str, Any] = Depends(verify_access_token)):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_delete"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有删除知识库权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有删除知识库权限", "data": None})
|
||||||
data = await self.RagDatasetService.DeleteAdminDataset(
|
data = await self.RagDatasetService.DeleteAdminDataset(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
@@ -166,7 +170,7 @@ class RagChatController(BaseController):
|
|||||||
|
|
||||||
@self.router.patch("/datasets/{DatasetId}", response_model=Result[RagDatasetDetailVO | None])
|
@self.router.patch("/datasets/{DatasetId}", response_model=Result[RagDatasetDetailVO | None])
|
||||||
async def UpdateDataset(DatasetId: int, Body: RagDatasetUpdateDTO, payload: dict[str, Any] = Depends(verify_access_token)):
|
async def UpdateDataset(DatasetId: int, Body: RagDatasetUpdateDTO, payload: dict[str, Any] = Depends(verify_access_token)):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_update"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有修改知识库权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有修改知识库权限", "data": None})
|
||||||
data = await self.RagDatasetService.UpdateDataset(
|
data = await self.RagDatasetService.UpdateDataset(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
@@ -222,7 +226,7 @@ class RagChatController(BaseController):
|
|||||||
data: str | None = Form(None),
|
data: str | None = Form(None),
|
||||||
payload: dict[str, Any] = Depends(verify_access_token),
|
payload: dict[str, Any] = Depends(verify_access_token),
|
||||||
):
|
):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_update"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有上传知识库文档权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有上传知识库文档权限", "data": None})
|
||||||
process_config = json.loads(data) if data else None
|
process_config = json.loads(data) if data else None
|
||||||
file_bytes = await file.read()
|
file_bytes = await file.read()
|
||||||
@@ -246,7 +250,7 @@ class RagChatController(BaseController):
|
|||||||
data: str | None = Form(None),
|
data: str | None = Form(None),
|
||||||
payload: dict[str, Any] = Depends(verify_access_token),
|
payload: dict[str, Any] = Depends(verify_access_token),
|
||||||
):
|
):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_update"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有重处理知识库文档权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有重处理知识库文档权限", "data": None})
|
||||||
process_config = json.loads(data) if data else None
|
process_config = json.loads(data) if data else None
|
||||||
file_bytes = await file.read()
|
file_bytes = await file.read()
|
||||||
@@ -287,7 +291,7 @@ class RagChatController(BaseController):
|
|||||||
Body: dict[str, Any],
|
Body: dict[str, Any],
|
||||||
payload: dict[str, Any] = Depends(verify_access_token),
|
payload: dict[str, Any] = Depends(verify_access_token),
|
||||||
):
|
):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_update"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有修改知识库文档状态权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有修改知识库文档状态权限", "data": None})
|
||||||
enabled = Action == "enable"
|
enabled = Action == "enable"
|
||||||
if Action not in {"enable", "disable"}:
|
if Action not in {"enable", "disable"}:
|
||||||
@@ -332,7 +336,7 @@ class RagChatController(BaseController):
|
|||||||
DocumentId: int,
|
DocumentId: int,
|
||||||
payload: dict[str, Any] = Depends(verify_access_token),
|
payload: dict[str, Any] = Depends(verify_access_token),
|
||||||
):
|
):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_delete"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有删除知识库文档权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有删除知识库文档权限", "data": None})
|
||||||
result = await self.RagDatasetService.DeleteDatasetDocument(
|
result = await self.RagDatasetService.DeleteDatasetDocument(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
@@ -388,7 +392,7 @@ class RagChatController(BaseController):
|
|||||||
Body: dict[str, Any],
|
Body: dict[str, Any],
|
||||||
payload: dict[str, Any] = Depends(verify_access_token),
|
payload: dict[str, Any] = Depends(verify_access_token),
|
||||||
):
|
):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_update"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有修改知识库分段权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有修改知识库分段权限", "data": None})
|
||||||
result = await self.RagDatasetService.UpdateDatasetDocumentSegment(
|
result = await self.RagDatasetService.UpdateDatasetDocumentSegment(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
@@ -408,7 +412,7 @@ class RagChatController(BaseController):
|
|||||||
SegmentId: str,
|
SegmentId: str,
|
||||||
payload: dict[str, Any] = Depends(verify_access_token),
|
payload: dict[str, Any] = Depends(verify_access_token),
|
||||||
):
|
):
|
||||||
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_read"]]):
|
if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["dataset_delete"]]):
|
||||||
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有删除知识库分段权限", "data": None})
|
return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有删除知识库分段权限", "data": None})
|
||||||
result = await self.RagDatasetService.DeleteDatasetDocumentSegment(
|
result = await self.RagDatasetService.DeleteDatasetDocumentSegment(
|
||||||
CurrentUserId=int(payload["user_id"]),
|
CurrentUserId=int(payload["user_id"]),
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class RagDatasetServiceImpl(IRagDatasetService):
|
|||||||
) -> RagDatasetPageVO:
|
) -> RagDatasetPageVO:
|
||||||
if UserRole not in ("provincial_admin", "admin", "super_admin"):
|
if UserRole not in ("provincial_admin", "admin", "super_admin"):
|
||||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前用户没有管理知识库权限")
|
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前用户没有管理知识库权限")
|
||||||
|
managed_area = self._resolve_managed_area(UserRole=UserRole, UserArea=UserArea)
|
||||||
|
|
||||||
filters = ["d.deleted_at IS NULL"]
|
filters = ["d.deleted_at IS NULL"]
|
||||||
params: dict = {
|
params: dict = {
|
||||||
@@ -72,7 +73,12 @@ class RagDatasetServiceImpl(IRagDatasetService):
|
|||||||
"limit": PageSize,
|
"limit": PageSize,
|
||||||
}
|
}
|
||||||
areas = [item.strip() for item in str(Area or "").split(",") if item.strip()]
|
areas = [item.strip() for item in str(Area or "").split(",") if item.strip()]
|
||||||
if len(areas) == 1:
|
if managed_area:
|
||||||
|
if areas and any(item != managed_area for item in areas):
|
||||||
|
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前用户只能查看本地区知识库配置")
|
||||||
|
filters.append("d.area = :managed_area")
|
||||||
|
params["managed_area"] = managed_area
|
||||||
|
elif len(areas) == 1:
|
||||||
filters.append("d.area = :area")
|
filters.append("d.area = :area")
|
||||||
params["area"] = areas[0]
|
params["area"] = areas[0]
|
||||||
elif len(areas) > 1:
|
elif len(areas) > 1:
|
||||||
@@ -127,6 +133,7 @@ class RagDatasetServiceImpl(IRagDatasetService):
|
|||||||
description = str(Body.get("dataset_description") or Body.get("description") or "").strip()
|
description = str(Body.get("dataset_description") or Body.get("description") or "").strip()
|
||||||
if not area or not name:
|
if not area or not name:
|
||||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "地区和知识库名称不能为空")
|
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "地区和知识库名称不能为空")
|
||||||
|
self._assert_manage_area_scope(UserRole=UserRole, UserArea=UserArea, DatasetArea=area)
|
||||||
|
|
||||||
collection_name = self._slugify_collection_name(area, name)
|
collection_name = self._slugify_collection_name(area, name)
|
||||||
retrieval_model = {}
|
retrieval_model = {}
|
||||||
@@ -208,8 +215,10 @@ class RagDatasetServiceImpl(IRagDatasetService):
|
|||||||
existing = await self._get_dataset_row(DatasetId)
|
existing = await self._get_dataset_row(DatasetId)
|
||||||
if not existing:
|
if not existing:
|
||||||
return None
|
return None
|
||||||
|
self._assert_manage_area_scope(UserRole=UserRole, UserArea=UserArea, DatasetArea=str(existing.get("area") or ""))
|
||||||
|
|
||||||
area = str(Body.get("area") or existing.get("area") or "").strip()
|
area = str(Body.get("area") or existing.get("area") or "").strip()
|
||||||
|
self._assert_manage_area_scope(UserRole=UserRole, UserArea=UserArea, DatasetArea=area)
|
||||||
|
|
||||||
async with GetAsyncSession() as session:
|
async with GetAsyncSession() as session:
|
||||||
target_is_default = bool(Body.get("is_default", existing.get("is_default")))
|
target_is_default = bool(Body.get("is_default", existing.get("is_default")))
|
||||||
@@ -268,6 +277,7 @@ class RagDatasetServiceImpl(IRagDatasetService):
|
|||||||
existing = await self._get_dataset_row(DatasetId)
|
existing = await self._get_dataset_row(DatasetId)
|
||||||
if not existing:
|
if not existing:
|
||||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "知识库不存在")
|
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "知识库不存在")
|
||||||
|
self._assert_manage_area_scope(UserRole=UserRole, UserArea=UserArea, DatasetArea=str(existing.get("area") or ""))
|
||||||
if bool(existing.get("is_default")):
|
if bool(existing.get("is_default")):
|
||||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "默认知识库不允许删除,请先切换默认知识库")
|
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "默认知识库不允许删除,请先切换默认知识库")
|
||||||
async with GetAsyncSession() as session:
|
async with GetAsyncSession() as session:
|
||||||
@@ -691,6 +701,24 @@ class RagDatasetServiceImpl(IRagDatasetService):
|
|||||||
return f"legal_kb_{normalized}"[:96]
|
return f"legal_kb_{normalized}"[:96]
|
||||||
return f"legal_kb_{uuid.uuid4().hex[:12]}"
|
return f"legal_kb_{uuid.uuid4().hex[:12]}"
|
||||||
|
|
||||||
|
def _resolve_managed_area(self, UserRole: str | None, UserArea: str | None) -> str | None:
|
||||||
|
if UserRole == "admin":
|
||||||
|
area = str(UserArea or "").strip()
|
||||||
|
if not area:
|
||||||
|
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前市级管理员未配置地区,无法管理知识库")
|
||||||
|
return area
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _assert_manage_area_scope(self, UserRole: str | None, UserArea: str | None, DatasetArea: str) -> None:
|
||||||
|
if UserRole in ("provincial_admin", "super_admin"):
|
||||||
|
return
|
||||||
|
if UserRole != "admin":
|
||||||
|
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前用户没有管理知识库权限")
|
||||||
|
|
||||||
|
managed_area = self._resolve_managed_area(UserRole=UserRole, UserArea=UserArea)
|
||||||
|
if DatasetArea != managed_area:
|
||||||
|
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前用户只能管理本地区知识库")
|
||||||
|
|
||||||
async def UploadDatasetDocument(
|
async def UploadDatasetDocument(
|
||||||
self,
|
self,
|
||||||
CurrentUserId: int,
|
CurrentUserId: int,
|
||||||
|
|||||||
@@ -252,6 +252,10 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
|||||||
{"permission_key": "rag:conversation:delete", "display_name": "删除 RAG 会话", "module": "rag", "resource": "conversation", "action": "delete", "api_method": "DELETE", "api_path": "/api/v3/rag/chat/conversations/{ConversationId}", "route_path": "/chat-with-llm"},
|
{"permission_key": "rag:conversation:delete", "display_name": "删除 RAG 会话", "module": "rag", "resource": "conversation", "action": "delete", "api_method": "DELETE", "api_path": "/api/v3/rag/chat/conversations/{ConversationId}", "route_path": "/chat-with-llm"},
|
||||||
{"permission_key": "rag:message:feedback", "display_name": "反馈 RAG 消息", "module": "rag", "resource": "message", "action": "feedback", "api_method": "POST", "api_path": "/api/v3/rag/chat/messages/{MessageId}/feedback", "route_path": "/chat-with-llm"},
|
{"permission_key": "rag:message:feedback", "display_name": "反馈 RAG 消息", "module": "rag", "resource": "message", "action": "feedback", "api_method": "POST", "api_path": "/api/v3/rag/chat/messages/{MessageId}/feedback", "route_path": "/chat-with-llm"},
|
||||||
{"permission_key": "rag:dataset:read", "display_name": "查看 RAG 知识库", "module": "rag", "resource": "dataset", "action": "read", "api_method": "GET", "api_path": "/api/v3/rag/datasets/my", "route_path": "/chat-with-llm"},
|
{"permission_key": "rag:dataset:read", "display_name": "查看 RAG 知识库", "module": "rag", "resource": "dataset", "action": "read", "api_method": "GET", "api_path": "/api/v3/rag/datasets/my", "route_path": "/chat-with-llm"},
|
||||||
|
{"permission_key": "rag:dataset:manage", "display_name": "查看知识库配置管理", "module": "rag", "resource": "dataset", "action": "manage", "api_method": "GET", "api_path": "/api/v3/rag/datasets/admin", "route_path": "/chat-with-llm"},
|
||||||
|
{"permission_key": "rag:dataset:create", "display_name": "创建知识库", "module": "rag", "resource": "dataset", "action": "create", "api_method": "POST", "api_path": "/api/v3/rag/datasets/admin", "route_path": "/chat-with-llm"},
|
||||||
|
{"permission_key": "rag:dataset:update", "display_name": "更新知识库与文档", "module": "rag", "resource": "dataset", "action": "update", "api_method": "PATCH", "api_path": "/api/v3/rag/datasets/{DatasetId}", "route_path": "/chat-with-llm"},
|
||||||
|
{"permission_key": "rag:dataset:delete", "display_name": "删除知识库与文档", "module": "rag", "resource": "dataset", "action": "delete", "api_method": "DELETE", "api_path": "/api/v3/rag/datasets/admin/{DatasetId}", "route_path": "/chat-with-llm"},
|
||||||
]
|
]
|
||||||
|
|
||||||
async def ListRoles(self, CurrentUserId: int, Page: int, PageSize: int, RoleKey: str | None, RoleName: str | None, IncludeSystem: bool) -> RoleListVO:
|
async def ListRoles(self, CurrentUserId: int, Page: int, PageSize: int, RoleKey: str | None, RoleName: str | None, IncludeSystem: bool) -> RoleListVO:
|
||||||
|
|||||||
@@ -571,6 +571,7 @@ class RbacServiceImpl(IRbacService):
|
|||||||
}
|
}
|
||||||
|
|
||||||
_PERMISSION_PREFIXES_BY_PATH: dict[str, list[str]] = {
|
_PERMISSION_PREFIXES_BY_PATH: dict[str, list[str]] = {
|
||||||
|
"/chat-with-llm": ["rag:"],
|
||||||
"/files": ["documents:"],
|
"/files": ["documents:"],
|
||||||
"/files/upload": ["documents:upload:"],
|
"/files/upload": ["documents:upload:"],
|
||||||
"/documents": ["documents:"],
|
"/documents": ["documents:"],
|
||||||
|
|||||||
Reference in New Issue
Block a user