From 6f11557eea7496df713ec04f2a43db9daff3fe00 Mon Sep 17 00:00:00 2001 From: ranzhigg Date: Fri, 30 Oct 2026 16:26:44 +0800 Subject: [PATCH] fix: satisfy authorization PR checks --- app/api/endpoints/storage.py | 8 +++++--- app/locales/en-US.json | 4 ++++ skills/moviepilot-api/api/subscription.md | 16 ++++++++-------- skills/moviepilot-api/api/transfer.md | 4 ++-- tests/test_agent_api_identity.py | 1 - 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/api/endpoints/storage.py b/app/api/endpoints/storage.py index 284599953..67a9faf45 100644 --- a/app/api/endpoints/storage.py +++ b/app/api/endpoints/storage.py @@ -304,12 +304,14 @@ def _is_agent_storage_path_allowed(fileitem: _SchemaFileItem) -> bool: return True return False - requested_path = posixpath.normpath(f"/{requested_raw_path.lstrip('/')}") + requested_remote_path = posixpath.normpath(f"/{requested_raw_path.lstrip('/')}") for root_storage, root_raw_path in configured_roots: if root_storage != requested_storage: continue - root_path = posixpath.normpath(f"/{root_raw_path.lstrip('/')}") - if requested_path == root_path or requested_path.startswith(f"{root_path.rstrip('/')}/"): + root_remote_path = posixpath.normpath(f"/{root_raw_path.lstrip('/')}") + if requested_remote_path == root_remote_path or requested_remote_path.startswith( + f"{root_remote_path.rstrip('/')}/" + ): return True return False except (OSError, RuntimeError, TypeError, ValueError): diff --git a/app/locales/en-US.json b/app/locales/en-US.json index 00e465ba5..96b7e45d4 100644 --- a/app/locales/en-US.json +++ b/app/locales/en-US.json @@ -591,6 +591,10 @@ "订阅活动暂时不可用": "Subscription activity is temporarily unavailable", "活动不存在或无权查看": "Activity does not exist or access is denied", "文件路径为空": "File path is empty", + "目录名不能包含路径分隔符": "Directory names cannot contain path separators", + "名称不能包含路径分隔符": "Names cannot contain path separators", + "管理用户只能操作已配置的存储目录": "Management users can only operate within configured storage directories", + "Agent 只能浏览已配置的存储目录": "Agents can only browse configured storage directories", "源文件路径为空": "Source file path is empty", "未识别到媒体类型": "Media type could not be identified", "站点域名未配置": "Site domain is not configured", diff --git a/skills/moviepilot-api/api/subscription.md b/skills/moviepilot-api/api/subscription.md index a7d6aaea8..a311f52a6 100644 --- a/skills/moviepilot-api/api/subscription.md +++ b/skills/moviepilot-api/api/subscription.md @@ -71,24 +71,24 @@ Identity scope: `bound_user`; required role: `user`. ### `subscription.follow.add` `POST /api/v1/subscribe/follow`; policy effect: `reversible_write`. -Purpose: 按精确分享用户标识关注一个订阅分享用户。 -Identity scope: `bound_user`; required role: `user`. +Purpose: 修改实例级关注的订阅分享用户列表。 +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `share_uid` (string|null): 需要关注或取消关注的精确 MoviePilot Server 分享用户标识。 - `body`: none ### `subscription.follow.delete` `DELETE /api/v1/subscribe/follow`; policy effect: `reversible_write`. -Purpose: 按精确分享用户标识取消关注一个订阅分享用户。 -Identity scope: `bound_user`; required role: `user`. +Purpose: 从实例级关注列表移除一个订阅分享用户。 +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `share_uid` (string|null): 需要关注或取消关注的精确 MoviePilot Server 分享用户标识。 - `body`: none ### `subscription.follow.list` `GET /api/v1/subscribe/follow`; policy effect: `safe_read`. -Purpose: 列出当前用户关注的订阅分享用户标识。 -Identity scope: `bound_user`; required role: `user`. +Purpose: 读取实例级关注的订阅分享用户列表。 +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): 旧版完整列表接口的可选每页条数;提供 page 或 count 即启用分页,此时省略 count 使用 50 条。; `page` (integer|null): 旧版完整列表接口的可选页码,从 1 开始;同时省略 page 和 count 时保留原有完整结果。 @@ -198,8 +198,8 @@ Identity scope: `bound_user`; required role: `user`. ### `subscription.share.delete` `DELETE /api/v1/subscribe/share/{share_id}`; policy effect: `external_side_effect`. -Purpose: 按分享标识删除一条共享订阅发布记录。 -Identity scope: `bound_user`; required role: `user`. +Purpose: 按分享标识删除一条实例级共享订阅发布记录。 +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `share_id*` (integer): 分享列表操作返回的持久化 MoviePilot Server 分享标识。 - `query`: none - `body`: none diff --git a/skills/moviepilot-api/api/transfer.md b/skills/moviepilot-api/api/transfer.md index c8c774117..bf03ceda2 100644 --- a/skills/moviepilot-api/api/transfer.md +++ b/skills/moviepilot-api/api/transfer.md @@ -160,8 +160,8 @@ Identity scope: `bound_user`; required role: `user`. ### `transfer.queue.delete` `DELETE /api/v1/transfer/queue`; policy effect: `destructive_write`. -Purpose: 从整理队列移除一个精确存储项目,并停止其整理。 -Identity scope: `bound_user`; required role: `user`. +Purpose: 从实例整理队列移除一个精确存储项目,并停止其整理。 +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `basename` (string|null): 不含扩展名的文件基本名称; `children` (array|null): 子节点; `drive_id` (string|null): 云盘驱动器标识; `extension` (string|null): 文件后缀; `fileid` (string|null): 存储服务分配的文件标识; `modify_time` (number|null): 文件修改时间的 Unix 时间戳; `name` (string|null): 文件名; `parent_fileid` (string|null): 父ID; `path` (string|null; default `/`): 文件路径; `pickcode` (string|null): 115 文件下载提取码; `size` (integer|null): 文件大小; `storage` (string|null; default `local`): 存储类型,默认 local 表示本地存储; `thumbnail` (string|null): 缩略图; `type` (string|null): 条目类型,dir 表示目录,file 表示文件; `url` (string|null): 访问地址 diff --git a/tests/test_agent_api_identity.py b/tests/test_agent_api_identity.py index df42b831d..0fd7ad7e0 100644 --- a/tests/test_agent_api_identity.py +++ b/tests/test_agent_api_identity.py @@ -99,7 +99,6 @@ def test_public_queries_do_not_require_channel_binding(operation_id, arguments, ("storage.list", {"body": {"path": "/", "storage": "local", "type": "dir"}}), ("config.user.get", {}), ("config.public.get", {"path_params": {"key": "Storages"}}), - ("subscription.follow.list", {}), ]) @pytest.mark.parametrize("user_state", ["unbound", "inactive"]) def test_instance_and_user_operations_still_require_binding(operation_id, arguments, user_state):