|
|
@@ -6,9 +6,10 @@
|
|
|
-- 在**已存在**的「收付款」目录下,新增:
|
|
|
-- · 1 行页面(type=2)「商户进件」 —— 进件页路由
|
|
|
-- · 2 行按钮(type=3)「建号」「提交入网」 —— 承载两个写权限点
|
|
|
--- 并把这三行授权给 role_id 1(管理员角色)**与** 2(租户角色)——
|
|
|
--- ★ 这与计划/lsy-menu.sql 字面的「role_id = 1」不同,依据是活库现状,见下「★ 授权给谁」。
|
|
|
--- 只想给 admin 就把授权语句里的 `(2)` 删掉;代价写在那一节。
|
|
|
+-- 并把这三行授权给 role_id = 1(管理员角色)。
|
|
|
+-- ★★ 本批次**故意不授** role_id = 2(租户角色)—— 不是「它不该有」,而是「现在给会造出一条
|
|
|
+-- **活的跨租户写入路径**」。完整因果、放开条件、以及放开用的那条语句,见下「★ 授权给谁」。
|
|
|
+-- 这一条请在执行前读完再决定。
|
|
|
--
|
|
|
-- ── ★ 与 lsy-menu.sql 的关键差异(照抄它的写法会出错的地方)──────────────
|
|
|
-- lsy-menu.sql 的 `dir` CTE 是**插入**收付款目录;本脚本的 `dir` 是**查**它。
|
|
|
@@ -95,20 +96,50 @@
|
|
|
-- route_name 会成为 vue-router 的 name(菜单高亮与 keep-alive 的 include 都按它匹配),必须唯一。
|
|
|
-- 2) route_path:本脚本用 `/payment/collection/onboarding`(模块前缀式),照同目录三个 lsy 页面。
|
|
|
--
|
|
|
--- ── ★ 授权给谁:role_id 1 **与** 2(此处偏离计划字面,依据是活库现状)────────
|
|
|
+-- ── ★★ 授权给谁:本批次只给 role_id = 1(**故意不给 role 2,理由是安全**)────────
|
|
|
+--
|
|
|
-- **活库实测(2026-09-23)**:
|
|
|
-- · `sys_role` 一共两行:**id=1「管理员角色」(code=ADMIN),只被 1 个用户持有(`admin`, tenant_id=1)**;
|
|
|
-- **id=2「租户角色」(code=tenant),被 17 个用户持有**(tenant_id = 2, 9–18, 21–26)。
|
|
|
--- · 「收付款」目录(id=268)与它下面**全部三个** lsy 兄弟页(269 支付订单 / 270 退款记录 /
|
|
|
--- 271 商户映射)**都授给了 role 1 和 role 2 两个角色**(实测 8 行 sys_role_menus,两角色各 4)。
|
|
|
--- ⇒ 若只授 role 1,**17 个租户用户一个都看不到这个页面**(菜单树按 sys_role_menus 过滤),
|
|
|
--- 而进件本来就是**租户级**业务、租户用户正是它的使用者;
|
|
|
--- 更糟的是 `admin` 是超管,`PermissionCheck` 对超管直接放行 ⇒ **本机测不出这个缺口**。
|
|
|
--- ⇒ 故本脚本授两个角色,与这棵子树**现有的每一行**保持一致。
|
|
|
--- ★ 如果你的意图是「进件页只给 admin」,把下面 INSERT 里的 `(2)` 一行删掉即可 ——
|
|
|
--- 那是一个可以做的选择,但请知道它的后果:**17 个租户用户看不到进件菜单**。
|
|
|
--- (`lsy-menu.sql` 自己写的是 role_id = 1,可活库里那几行**都**带着 role 2 ——
|
|
|
--- 说明那份脚本的授权行不足以复现现状,别把它的字面当权威。)
|
|
|
+-- · `module_payment:leshouying:merchant:list` **role 1 与 role 2 都有**(实测 sys_role_menus)。
|
|
|
+-- · 「收付款」目录(id=268)与三个 lsy 兄弟页(269/270/271)也都授给了两个角色。
|
|
|
+-- ⇒ 从**惯例**看应当给 role 2;本批次不给,是因为下面这条已核实的攻击链。
|
|
|
+--
|
|
|
+-- ★★ 不给 role 2 的理由(**每一跳都实测过,不是推测**):
|
|
|
+-- `onboarding:commit` 的那道「租户归属闸」证明的是「**本租户内存在一行 mch_no 等于该值
|
|
|
+-- 的映射**」,不是「这个商户号属于你」。而那张映射表**调用方自己就能写**:
|
|
|
+-- · `LeshouyingMerchantController.save`(`POST /payment/collection/merchant`)的权限是
|
|
|
+-- `module_payment:leshouying:merchant:list` —— **一个读权限守着写和删**;
|
|
|
+-- · 它收原始实体,**mch_no 由调用方给**,`LsyMerchantService.save` 对该值**只判非空**
|
|
|
+-- (实测 LsyMerchantService.java:457-459);
|
|
|
+-- · 没有 id ⇒ 走新增分支直接 insert(`:478` / `:486`),租户由
|
|
|
+-- `AutoFillMetaObjectHandler` 从认证上下文补 ⇒ 落进**调用方自己的租户**;
|
|
|
+-- · `enterprise_id` 给一个**没被本租户用过的非空值**即可绕开 `uk_lsy_merchant_tenant_ent`
|
|
|
+-- (复合唯一对非空值不限行数 —— 与本文档订正的那条 uuid 断言同源,见上);
|
|
|
+-- ⇒ 于是 `/commit` 的第二行 `requireByMchNoForTenant` **必然通过**,
|
|
|
+-- 上游就收到一个**我方租户并不拥有**的商户号的入网提交(商户资质 / 门店位置 / 经纬度)。
|
|
|
+-- `mch_no` 形如 `Y52211090004`,是**可枚举**的结构化编号。
|
|
|
+-- ⇒ 今天**只有超管**能调 `/commit`(超管对 `@PreAuthorize` 直通,本就不构成新风险);
|
|
|
+-- 一旦把 `:commit` 授给 role 2,这条链的**每一跳都被 17 个真实用户满足**。
|
|
|
+--
|
|
|
+-- ★ 放开条件(两条**任一**成立即可把 role 2 加回来):
|
|
|
+-- 1. 让归属**由构造保证**(例如给 `pay_lsy_merchant` 留「这一行是谁建的」的痕,
|
|
|
+-- `/commit` 只接受由本租户 `/create` 建出来的行);**或**
|
|
|
+-- 2. 把 `save` / `delete` 的权限从 `merchant:list` 换成写专用权限,且该权限**不授给 role 2**
|
|
|
+-- (即:映射表由运营维护、租户只读)。
|
|
|
+--
|
|
|
+-- ★ 放开时**不要重跑本脚本**(页面行已存在 ⇒ `page` 为空 ⇒ 授权行也不会插,见下面「幂等」)。
|
|
|
+-- 单独跑这一条即可:
|
|
|
+-- INSERT INTO sys_role_menus (role_id, menu_id)
|
|
|
+-- SELECT 2, m.id FROM sys_menu m
|
|
|
+-- WHERE m.name IN ('商户进件', '建号', '提交入网') AND m.status = '0';
|
|
|
+--
|
|
|
+-- ★ 代价(如实写):今天 17 个租户用户看不到进件页、也调不了三个端点;**admin 仍可全程走通**
|
|
|
+-- (它以 tenant_id=1 调 `/create`,建出的行就落在 tenant 1,随后 `/commit` 对它自己的行成立),
|
|
|
+-- 所以**联调不被阻塞**。这与「惯例上应当给 role 2」是一处取舍,取舍方向是「先不给、别造出
|
|
|
+-- 一条活的跨租户写入路径」,放开只要上面那一条 INSERT。
|
|
|
+-- ★ `lsy-menu.sql` 自己写的是 role_id = 1,而活库里那几行**都**带着 role 2 ⇒
|
|
|
+-- 那份脚本的授权行不足以复现现状,别把它的字面当权威。
|
|
|
--
|
|
|
-- ── 其他 ─────────────────────────────────────────────────────────────
|
|
|
-- · icon 一律留空:图标集是 src/assets/icons/*.svg,经 `i-svg:<name>` 渲染;
|
|
|
@@ -158,12 +189,10 @@ buttons AS (
|
|
|
RETURNING id
|
|
|
)
|
|
|
-- 页面行与两个按钮行都要授权:菜单树按 sys_role_menus 过滤,只授页面会让按钮权限缺席 ⇒ 403。
|
|
|
--- 两个角色都要(见上「★ 授权给谁」):`page` / `buttons` 各只被引用一次,
|
|
|
--- 免得依赖「数据修改型 CTE 能否被引用两次」这种我没实测过的语义。
|
|
|
+-- 只授 role 1(**故意不授 role 2** —— 理由与放开条件见上「★★ 授权给谁」)。
|
|
|
INSERT INTO sys_role_menus (role_id, menu_id)
|
|
|
-SELECT v.role_id, x.id
|
|
|
- FROM (SELECT id FROM page UNION ALL SELECT id FROM buttons) AS x
|
|
|
- CROSS JOIN (VALUES (1), (2)) AS v(role_id);
|
|
|
+SELECT 1, x.id
|
|
|
+ FROM (SELECT id FROM page UNION ALL SELECT id FROM buttons) AS x;
|
|
|
|
|
|
|
|
|
-- ── 只读核对(执行后手工跑一遍;前三条**执行前**也建议先跑)────────────────
|
|
|
@@ -188,13 +217,13 @@ SELECT v.role_id, x.id
|
|
|
-- 期望:**2 行**,permission 逐字为
|
|
|
-- 'module_payment:leshouying:onboarding:create' 与 'module_payment:leshouying:onboarding:commit'。
|
|
|
--
|
|
|
--- 4) 授权行(**两个角色各 3 行**,见上「★ 授权给谁」)
|
|
|
+-- 4) 授权行(**3 行,role_id 全是 1** —— 故意不授 role 2,见上「★★ 授权给谁」)
|
|
|
-- SELECT rm.role_id, m.name, m.permission
|
|
|
-- FROM sys_role_menus rm JOIN sys_menu m ON m.id = rm.menu_id
|
|
|
-- WHERE m.name IN ('商户进件', '建号', '提交入网') AND m.status = '0'
|
|
|
-- ORDER BY rm.role_id, m.name;
|
|
|
--- 期望:**6 行** —— role_id 1 与 2 **各 3 行**。
|
|
|
--- 若只有 3 行且 role_id 全是 1 ⇒ 17 个租户用户看不到进件菜单。
|
|
|
+-- 期望:**3 行**,role_id 都是 1。
|
|
|
+-- 出现 role_id = 2 ⇒ 有人放开了,那时必须先确认上面「放开条件」已成立。
|
|
|
--
|
|
|
-- 5) 端到端(**唯一能证明权限串没写错的一条**):用一个**非超管**的租户账号登录,
|
|
|
-- 确认三个`module_payment:leshouying:onboarding:*`都出现在
|