security: 权限门禁收紧为owner组织角色
- _require_admin→_require_owner: 持有任一orgtypeid='owner'角色(含通配)放行 - 不再仅限superuser/admin; 其他组织(含ownerx前缀伪造)一律拒绝 - 工具描述/文案同步更新
This commit is contained in:
parent
b947d3456e
commit
e95e618372
Binary file not shown.
@ -5,8 +5,8 @@ pipeline-service: platform_ability — 平台内部 agent 能力包(pipeline_i
|
|||||||
核心能力是「通读模型 API 文档 → 自动生成模型治理配置(供应商/适配模板/模型/定价)」。
|
核心能力是「通读模型 API 文档 → 自动生成模型治理配置(供应商/适配模板/模型/定价)」。
|
||||||
|
|
||||||
权限模型(代码层硬门禁,不依赖 prompt):
|
权限模型(代码层硬门禁,不依赖 prompt):
|
||||||
所有工具 handler 入口校验调用者的 RBAC 角色 ∈ {owner.superuser, owner.admin}。
|
所有工具 handler 入口校验调用者持有 owner 组织的角色(orgtypeid='owner',含通配 'owner.*')。
|
||||||
内部 agent 只服务管理员——普通用户即使打开页面也调不动任何工具。
|
内部 agent 只服务 owner 组织的角色——其他组织的用户即使打开页面也调不动任何工具。
|
||||||
|
|
||||||
配置生成链路(用户给定 API 文档 → 完成配置):
|
配置生成链路(用户给定 API 文档 → 完成配置):
|
||||||
① fetch_model_doc:抓取文档页面(SSRF 防护:仅 http(s) 公网域名)
|
① fetch_model_doc:抓取文档页面(SSRF 防护:仅 http(s) 公网域名)
|
||||||
@ -29,8 +29,8 @@ logger = logging.getLogger("pipeline.platform_ability")
|
|||||||
|
|
||||||
PLATFORM_PIPELINE_ID = "platform_general"
|
PLATFORM_PIPELINE_ID = "platform_general"
|
||||||
|
|
||||||
# 管理角色:内部 agent 仅管理员可用
|
# 组织门禁:内部 agent 仅 owner 组织的角色可操作(任一 owner.* 角色,含通配)
|
||||||
ADMIN_ROLES = ("owner.superuser", "owner.admin")
|
OWNER_ORG = "owner"
|
||||||
|
|
||||||
# 当前运行时调用链支持的协议(llm_bridge 固定 OpenAI 兼容路径)。
|
# 当前运行时调用链支持的协议(llm_bridge 固定 OpenAI 兼容路径)。
|
||||||
# 其他协议的适配模板会存入 llm_api_profile 备查,但运行时暂不渲染。
|
# 其他协议的适配模板会存入 llm_api_profile 备查,但运行时暂不渲染。
|
||||||
@ -39,25 +39,28 @@ RUNTIME_PROTOCOLS = ("openai_compat",)
|
|||||||
|
|
||||||
# ────────────────────── 权限门禁(代码层) ──────────────────────
|
# ────────────────────── 权限门禁(代码层) ──────────────────────
|
||||||
|
|
||||||
async def _require_admin(sor, ctx) -> str:
|
async def _require_owner(sor, ctx) -> str:
|
||||||
"""校验当前用户是管理员。返回 ''=通过,否则错误信息。"""
|
"""校验当前用户持有 owner 组织的角色。返回 ''=通过,否则错误信息。
|
||||||
|
|
||||||
|
判定:userrole→role JOIN 后,存在任一 orgtypeid='owner' 的角色(含通配 'owner.*')。
|
||||||
|
"""
|
||||||
uid = ctx.get("user_id", "") or ""
|
uid = ctx.get("user_id", "") or ""
|
||||||
if not uid:
|
if not uid:
|
||||||
return "无法识别当前用户身份(未登录),内部 agent 仅管理员可用"
|
return "无法识别当前用户身份(未登录),内部 agent 仅 owner 组织角色可用"
|
||||||
recs = await sor.sqlExe(
|
recs = await sor.sqlExe(
|
||||||
"SELECT r.orgtypeid, r.name FROM userrole ur JOIN role r ON ur.roleid=r.id "
|
"SELECT r.orgtypeid, r.name FROM userrole ur JOIN role r ON ur.roleid=r.id "
|
||||||
"WHERE ur.userid=${u}$", {"u": uid})
|
"WHERE ur.userid=${u}$", {"u": uid})
|
||||||
await sor.sqlExe("COMMIT", {})
|
await sor.sqlExe("COMMIT", {})
|
||||||
roles = set()
|
roles = []
|
||||||
for r in (recs or []):
|
for r in (recs or []):
|
||||||
o = getattr(r, "orgtypeid", "") or ""
|
o = getattr(r, "orgtypeid", "") or ""
|
||||||
n = getattr(r, "name", "") or ""
|
n = getattr(r, "name", "") or ""
|
||||||
if o and n:
|
if o and n:
|
||||||
roles.add("%s.%s" % (o, n))
|
roles.append("%s.%s" % (o, n))
|
||||||
if roles & set(ADMIN_ROLES):
|
if any(fn.split(".", 1)[0] == OWNER_ORG for fn in roles):
|
||||||
return ""
|
return ""
|
||||||
return "权限不足:内部 agent 工具仅管理员(%s)可用,当前角色 %s" % (
|
return "权限不足:内部 agent 工具仅 %s 组织的角色可操作,当前角色 %s" % (
|
||||||
"/".join(ADMIN_ROLES), sorted(roles) or "无")
|
OWNER_ORG, sorted(set(roles)) or "无")
|
||||||
|
|
||||||
|
|
||||||
def _row(r):
|
def _row(r):
|
||||||
@ -70,8 +73,8 @@ def _row(r):
|
|||||||
# ────────────────────── 工具 1:模型治理状态 ──────────────────────
|
# ────────────────────── 工具 1:模型治理状态 ──────────────────────
|
||||||
|
|
||||||
async def _h_platform_llm_status(sor, params, ctx):
|
async def _h_platform_llm_status(sor, params, ctx):
|
||||||
"""供应商/账号/模型/用量概览(管理员诊断配置用)。"""
|
"""供应商/账号/模型/用量概览(owner 组织诊断配置用)。"""
|
||||||
err = await _require_admin(sor, ctx)
|
err = await _require_owner(sor, ctx)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
out = {}
|
out = {}
|
||||||
@ -132,7 +135,7 @@ def _html_to_text(html: str) -> str:
|
|||||||
|
|
||||||
async def _h_fetch_model_doc(sor, params, ctx):
|
async def _h_fetch_model_doc(sor, params, ctx):
|
||||||
"""抓取模型 API 文档页面 → 纯文本(供 LLM 提取配置规格)。"""
|
"""抓取模型 API 文档页面 → 纯文本(供 LLM 提取配置规格)。"""
|
||||||
err = await _require_admin(sor, ctx)
|
err = await _require_owner(sor, ctx)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
url = (params.get("url") or "").strip()
|
url = (params.get("url") or "").strip()
|
||||||
@ -192,7 +195,7 @@ _EXTRACT_PROMPT = """你是大模型 API 配置专家。通读下面这份模型
|
|||||||
|
|
||||||
async def _h_extract_llm_api_spec(sor, params, ctx):
|
async def _h_extract_llm_api_spec(sor, params, ctx):
|
||||||
"""LLM 通读文档文本 → 结构化配置规格(JSON)。"""
|
"""LLM 通读文档文本 → 结构化配置规格(JSON)。"""
|
||||||
err = await _require_admin(sor, ctx)
|
err = await _require_owner(sor, ctx)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
doc_text = (params.get("doc_text") or "").strip()
|
doc_text = (params.get("doc_text") or "").strip()
|
||||||
@ -238,7 +241,7 @@ async def _h_apply_llm_config(sor, params, ctx):
|
|||||||
幂等:供应商按名称复用;模型按 (vendor, vendor_model_id) 复用——
|
幂等:供应商按名称复用;模型按 (vendor, vendor_model_id) 复用——
|
||||||
已存在则更新定价,不重复创建。
|
已存在则更新定价,不重复创建。
|
||||||
"""
|
"""
|
||||||
err = await _require_admin(sor, ctx)
|
err = await _require_owner(sor, ctx)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
try:
|
try:
|
||||||
@ -410,7 +413,7 @@ def _default_templates(protocol: str, capability: str, spec: dict) -> tuple:
|
|||||||
|
|
||||||
async def _h_platform_modules(sor, params, ctx):
|
async def _h_platform_modules(sor, params, ctx):
|
||||||
"""列出平台已装载的业务模块(内部 agent 了解平台构成用)。"""
|
"""列出平台已装载的业务模块(内部 agent 了解平台构成用)。"""
|
||||||
err = await _require_admin(sor, ctx)
|
err = await _require_owner(sor, ctx)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
mods = []
|
mods = []
|
||||||
@ -439,39 +442,39 @@ async def _h_platform_modules(sor, params, ctx):
|
|||||||
PLATFORM_TOOLS = [
|
PLATFORM_TOOLS = [
|
||||||
ToolDefinition(
|
ToolDefinition(
|
||||||
name="platform_llm_status",
|
name="platform_llm_status",
|
||||||
description="查看模型治理状态:供应商/账号余额/模型定价/各状态分布。用户问「模型配置状态/账号余额」时调用。仅管理员可用。",
|
description="查看模型治理状态:供应商/账号余额/模型定价/各状态分布。用户问「模型配置状态/账号余额」时调用。仅owner组织角色可用。",
|
||||||
parameters={},
|
parameters={},
|
||||||
category="platform",
|
category="platform",
|
||||||
),
|
),
|
||||||
ToolDefinition(
|
ToolDefinition(
|
||||||
name="fetch_model_doc",
|
name="fetch_model_doc",
|
||||||
description="抓取大模型供应商的 API 文档页面(返回纯文本)。配置新模型前先用此抓取官方文档。仅管理员可用。",
|
description="抓取大模型供应商的 API 文档页面(返回纯文本)。配置新模型前先用此抓取官方文档。仅owner组织角色可用。",
|
||||||
parameters={"url": "文档页面 URL(必须是公网 http/https)"},
|
parameters={"url": "文档页面 URL(必须是公网 http/https)"},
|
||||||
category="platform",
|
category="platform",
|
||||||
),
|
),
|
||||||
ToolDefinition(
|
ToolDefinition(
|
||||||
name="extract_llm_api_spec",
|
name="extract_llm_api_spec",
|
||||||
description="通读文档文本,LLM 提取 API 配置规格(端点/协议/请求响应格式/定价)。配合 fetch_model_doc 使用。仅管理员可用。",
|
description="通读文档文本,LLM 提取 API 配置规格(端点/协议/请求响应格式/定价)。配合 fetch_model_doc 使用。仅owner组织角色可用。",
|
||||||
parameters={"doc_text": "fetch_model_doc 返回的文档文本"},
|
parameters={"doc_text": "fetch_model_doc 返回的文档文本"},
|
||||||
category="platform",
|
category="platform",
|
||||||
),
|
),
|
||||||
ToolDefinition(
|
ToolDefinition(
|
||||||
name="apply_llm_config",
|
name="apply_llm_config",
|
||||||
description="按提取的规格写入模型治理配置:供应商/端点/适配模板/模型(含定价)。幂等——已有模型只更新定价。仅管理员可用。",
|
description="按提取的规格写入模型治理配置:供应商/端点/适配模板/模型(含定价)。幂等——已有模型只更新定价。仅owner组织角色可用。",
|
||||||
parameters={"spec": "extract_llm_api_spec 返回的 JSON 规格"},
|
parameters={"spec": "extract_llm_api_spec 返回的 JSON 规格"},
|
||||||
category="platform",
|
category="platform",
|
||||||
requires_confirmation=True,
|
requires_confirmation=True,
|
||||||
),
|
),
|
||||||
ToolDefinition(
|
ToolDefinition(
|
||||||
name="platform_modules",
|
name="platform_modules",
|
||||||
description="列出平台已装载的业务模块清单。用户问「平台有哪些模块」时调用。仅管理员可用。",
|
description="列出平台已装载的业务模块清单。用户问「平台有哪些模块」时调用。仅owner组织角色可用。",
|
||||||
parameters={},
|
parameters={},
|
||||||
category="platform",
|
category="platform",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
PLATFORM_PROMPT = """
|
PLATFORM_PROMPT = """
|
||||||
你是产线平台的内部运维 agent,服务对象是平台管理员。
|
你是产线平台的内部运维 agent,服务对象是 owner 组织的角色。
|
||||||
|
|
||||||
## 模型自动配置工作流(用户给你文档 URL 要求配置模型时)
|
## 模型自动配置工作流(用户给你文档 URL 要求配置模型时)
|
||||||
1. `fetch_model_doc` 抓取官方文档页面
|
1. `fetch_model_doc` 抓取官方文档页面
|
||||||
@ -484,7 +487,7 @@ PLATFORM_PROMPT = """
|
|||||||
- cost_*(供应商成本)未知时与 price_* 相同
|
- cost_*(供应商成本)未知时与 price_* 相同
|
||||||
- 非 openai_compat 协议的模型:模板会存档但运行时暂不可调用,必须如实告知
|
- 非 openai_compat 协议的模型:模板会存档但运行时暂不可调用,必须如实告知
|
||||||
- 文档抓取失败/内容不足时如实说明,不要凭记忆编造 API 格式
|
- 文档抓取失败/内容不足时如实说明,不要凭记忆编造 API 格式
|
||||||
- 所有工具仅管理员可用,权限报错时如实转告用户
|
- 所有工具仅 owner 组织角色可用,权限报错时如实转告用户
|
||||||
|
|
||||||
## 平台知识
|
## 平台知识
|
||||||
平台模块清单用 `platform_modules` 查询;技能库中「平台」相关技能(skills_library/all/ 与
|
平台模块清单用 `platform_modules` 查询;技能库中「平台」相关技能(skills_library/all/ 与
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
"options": {"width": "100%", "alignItems": "center", "padding": "16px 24px 8px 24px", "cheight": 6, "gap": "12px"},
|
"options": {"width": "100%", "alignItems": "center", "padding": "16px 24px 8px 24px", "cheight": 6, "gap": "12px"},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{"widgettype": "Title2", "options": {"text": "平台内部助手"}},
|
{"widgettype": "Title2", "options": {"text": "平台内部助手"}},
|
||||||
{"widgettype": "Text", "options": {"text": "平台运维/管理 agent · 模型自动配置 · 仅管理员可用", "cfontsize": 0.9, "color": "#94a3b8"}},
|
{"widgettype": "Text", "options": {"text": "平台运维/管理 agent · 模型自动配置 · 仅owner组织角色可用", "cfontsize": 0.9, "color": "#94a3b8"}},
|
||||||
{"widgettype": "Filler"},
|
{"widgettype": "Filler"},
|
||||||
{
|
{
|
||||||
"widgettype": "Button",
|
"widgettype": "Button",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user