现象
对象 actions[] 里声明 locations:['record_header'] 的 type:'api' 动作,若 target URL 带 {id} 占位符,console 原样把 {id} 三个字符发出去,不做替换:
PATCH /api/v1/data/os_tianshun_ehr_production_plan/%7Bid%7D → 400 Bad Request
%7Bid%7D 即 URL 编码后的 {id}。后端收到一个名为 {id} 的记录 id,必然打不中记录。
同一份动作声明挂到 locations:['list_item'] 上则插值正常 —— 两个执行器行为不一致。
复现
平台 17.0.0-rc.1(生产)与 17.0.0-rc.3(本地)均复现。
对象上声明:
{
name: 'copy_plan_row',
label: '复制',
locations: ['record_header'],
type: 'api',
method: 'PATCH',
target: '/api/v1/data/os_tianshun_ehr_production_plan/{id}',
params: [{ field: 'copy_ovr_start', required: true }],
bodyExtra: { copy_now: true },
confirmText: '…',
}
浏览器打开任一条记录的详情页 → 点该按钮 → 填参数 → 确认 → DevTools Network 里即可看到上面那条 %7Bid%7D 的 400。
根因
RecordDetailView 的动作执行器里,插值被 params._rowRecord 守卫:
let r = t.params && !Array.isArray(t.params) ? t.params._rowRecord : void 0;
…
if (a.startsWith(`/`) || /^https?:\/\//i.test(a)) {
let e = a;
r && /\{[a-z_][a-z0-9_]*\}/i.test(e) && (e = e.replace(/\{([a-z_][a-z0-9_]*)\}/gi, (e, t) => {
let n = r[t];
return n == null ? `` : encodeURIComponent(String(n));
}));
替换动作依赖 r(即 params._rowRecord)。但 record_header 执行器从不把当前记录放进 params._rowRecord —— r 恒为 undefined,r && … 短路,整个替换被跳过,a 原样进入请求。
对照组:framework 的列表执行器显式保留该键,故 list_item 上插值正常:
let n = Array.isArray(e.params) ? {} : e.params || {}, i = n._rowRecord;
e.params = { ...n, ...t };
i !== void 0 && (e.params._rowRecord = …)
版本比对
rc.1 与 rc.3 的这段守卫逐字相同,未修:
| 版本 |
bundle |
守卫 |
| 17.0.0-rc.1 |
RecordDetailView-BYYpDJSj.js |
r&&/\{[a-z_][a-z0-9_]*\}/i.test(e)&&(e=e.replace(…)) |
| 17.0.0-rc.3 |
RecordDetailView-CP1b5rdr.js |
同上,一字不差 |
影响
详情页头的 type:'api' 动作无法通过 URL 路径定位当前记录。平台侧现有的 recordIdParam 只能把 id 注入 body,而 /api/v1/data/:object/:id 这类 REST 端点要求 id 在路径上,body 里的 id 不被采纳 —— 于是详情页头动作无法直接调用平台自己的 data 端点。
应用侧当前只能自建一个中转端点(从 body 取 id 再转发),这既是额外的攻击面,也把「服务端调用自身 HTTP」这种反模式引进了应用层。
期望
record_header 执行器与 list_item 执行器行为对齐:执行 type:'api' 动作前,把当前记录放进 params._rowRecord(或以其它方式让 target URL 的 {field} 占位符得到插值),使详情页头动作可以直接使用 /api/v1/data/:object/{id} 一类的原生端点。
环境
@objectstack/console 17.0.0-rc.1 / 17.0.0-rc.3
- Chrome,PC Console 记录详情页
现象
对象
actions[]里声明locations:['record_header']的type:'api'动作,若 target URL 带{id}占位符,console 原样把{id}三个字符发出去,不做替换:%7Bid%7D即 URL 编码后的{id}。后端收到一个名为{id}的记录 id,必然打不中记录。同一份动作声明挂到
locations:['list_item']上则插值正常 —— 两个执行器行为不一致。复现
平台
17.0.0-rc.1(生产)与17.0.0-rc.3(本地)均复现。对象上声明:
浏览器打开任一条记录的详情页 → 点该按钮 → 填参数 → 确认 → DevTools Network 里即可看到上面那条
%7Bid%7D的 400。根因
RecordDetailView的动作执行器里,插值被params._rowRecord守卫:替换动作依赖
r(即params._rowRecord)。但record_header执行器从不把当前记录放进params._rowRecord——r恒为undefined,r && …短路,整个替换被跳过,a原样进入请求。对照组:
framework的列表执行器显式保留该键,故list_item上插值正常:版本比对
rc.1与rc.3的这段守卫逐字相同,未修:RecordDetailView-BYYpDJSj.jsr&&/\{[a-z_][a-z0-9_]*\}/i.test(e)&&(e=e.replace(…))RecordDetailView-CP1b5rdr.js影响
详情页头的
type:'api'动作无法通过 URL 路径定位当前记录。平台侧现有的recordIdParam只能把 id 注入 body,而/api/v1/data/:object/:id这类 REST 端点要求 id 在路径上,body 里的 id 不被采纳 —— 于是详情页头动作无法直接调用平台自己的 data 端点。应用侧当前只能自建一个中转端点(从 body 取 id 再转发),这既是额外的攻击面,也把「服务端调用自身 HTTP」这种反模式引进了应用层。
期望
record_header执行器与list_item执行器行为对齐:执行type:'api'动作前,把当前记录放进params._rowRecord(或以其它方式让 target URL 的{field}占位符得到插值),使详情页头动作可以直接使用/api/v1/data/:object/{id}一类的原生端点。环境
@objectstack/console17.0.0-rc.1 / 17.0.0-rc.3