setUserPermsLogic_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. package user
  2. import (
  3. "context"
  4. "errors"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. "math"
  8. "perms-system-server/internal/consts"
  9. "perms-system-server/internal/loaders"
  10. "perms-system-server/internal/middleware"
  11. permModel "perms-system-server/internal/model/perm"
  12. productModel "perms-system-server/internal/model/product"
  13. "perms-system-server/internal/response"
  14. "perms-system-server/internal/svc"
  15. "perms-system-server/internal/testutil"
  16. "perms-system-server/internal/testutil/ctxhelper"
  17. "perms-system-server/internal/types"
  18. "testing"
  19. "time"
  20. )
  21. type userPermRow struct {
  22. Id int64 `db:"id"`
  23. UserId int64 `db:"userId"`
  24. PermId int64 `db:"permId"`
  25. Effect string `db:"effect"`
  26. }
  27. func findUserPerms(t *testing.T, ctx context.Context, userId int64) []userPermRow {
  28. t.Helper()
  29. conn := testutil.GetTestSqlConn()
  30. var rows []userPermRow
  31. require.NoError(t, conn.QueryRowsCtx(ctx, &rows,
  32. "SELECT `id`,`userId`,`permId`,`effect` FROM `sys_user_perm` WHERE `userId`=?", userId))
  33. return rows
  34. }
  35. func insertTestPerm(t *testing.T, svcCtx *svc.ServiceContext, productCode string) int64 {
  36. t.Helper()
  37. now := time.Now().Unix()
  38. res, err := svcCtx.SysPermModel.Insert(ctxhelper.SuperAdminCtx(), &permModel.SysPerm{
  39. ProductCode: productCode,
  40. Name: "perm_" + testutil.UniqueId(),
  41. Code: "code_" + testutil.UniqueId(),
  42. Status: 1,
  43. CreateTime: now,
  44. UpdateTime: now,
  45. })
  46. require.NoError(t, err)
  47. id, _ := res.LastInsertId()
  48. return id
  49. }
  50. // TC-0192: 正常ALLOW
  51. func TestSetUserPerms_Allow(t *testing.T) {
  52. ctx := ctxhelper.SuperAdminCtx()
  53. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  54. conn := testutil.GetTestSqlConn()
  55. username := testutil.UniqueId()
  56. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  57. mId := insertTestMember(t, svcCtx, "test_product", userId)
  58. p1 := insertTestPerm(t, svcCtx, "test_product")
  59. p2 := insertTestPerm(t, svcCtx, "test_product")
  60. t.Cleanup(func() {
  61. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  62. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  63. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  64. testutil.CleanTable(ctx, conn, "`sys_perm`", p1, p2)
  65. })
  66. logic := NewSetUserPermsLogic(ctx, svcCtx)
  67. err := logic.SetUserPerms(&types.SetPermsReq{
  68. UserId: userId,
  69. Perms: []types.UserPermItem{
  70. {PermId: p1, Effect: "ALLOW"},
  71. {PermId: p2, Effect: "ALLOW"},
  72. },
  73. })
  74. require.NoError(t, err)
  75. perms := findUserPerms(t, ctx, userId)
  76. assert.Len(t, perms, 2)
  77. for _, p := range perms {
  78. assert.Equal(t, "ALLOW", p.Effect)
  79. }
  80. }
  81. // TC-0194: DENY权限
  82. func TestSetUserPerms_Deny(t *testing.T) {
  83. ctx := ctxhelper.SuperAdminCtx()
  84. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  85. conn := testutil.GetTestSqlConn()
  86. username := testutil.UniqueId()
  87. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  88. mId := insertTestMember(t, svcCtx, "test_product", userId)
  89. p1 := insertTestPerm(t, svcCtx, "test_product")
  90. t.Cleanup(func() {
  91. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  92. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  93. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  94. testutil.CleanTable(ctx, conn, "`sys_perm`", p1)
  95. })
  96. logic := NewSetUserPermsLogic(ctx, svcCtx)
  97. err := logic.SetUserPerms(&types.SetPermsReq{
  98. UserId: userId,
  99. Perms: []types.UserPermItem{
  100. {PermId: p1, Effect: "DENY"},
  101. },
  102. })
  103. require.NoError(t, err)
  104. perms := findUserPerms(t, ctx, userId)
  105. require.Len(t, perms, 1)
  106. assert.Equal(t, "DENY", perms[0].Effect)
  107. assert.Equal(t, p1, perms[0].PermId)
  108. }
  109. // TC-0193: 用户不存在
  110. func TestSetUserPerms_UserNotFound(t *testing.T) {
  111. ctx := ctxhelper.SuperAdminCtx()
  112. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  113. logic := NewSetUserPermsLogic(ctx, svcCtx)
  114. err := logic.SetUserPerms(&types.SetPermsReq{
  115. UserId: 999999999,
  116. Perms: []types.UserPermItem{
  117. {PermId: 1, Effect: "ALLOW"},
  118. },
  119. })
  120. require.Error(t, err)
  121. var codeErr *response.CodeError
  122. require.True(t, errors.As(err, &codeErr))
  123. assert.Equal(t, 404, codeErr.Code())
  124. assert.Equal(t, "用户不存在", codeErr.Error())
  125. }
  126. // TC-0195: 清空权限
  127. func TestSetUserPerms_EmptyPerms_ClearsAll(t *testing.T) {
  128. ctx := ctxhelper.SuperAdminCtx()
  129. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  130. conn := testutil.GetTestSqlConn()
  131. username := testutil.UniqueId()
  132. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  133. mId := insertTestMember(t, svcCtx, "test_product", userId)
  134. p1 := insertTestPerm(t, svcCtx, "test_product")
  135. t.Cleanup(func() {
  136. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  137. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  138. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  139. testutil.CleanTable(ctx, conn, "`sys_perm`", p1)
  140. })
  141. logic := NewSetUserPermsLogic(ctx, svcCtx)
  142. err := logic.SetUserPerms(&types.SetPermsReq{
  143. UserId: userId,
  144. Perms: []types.UserPermItem{
  145. {PermId: p1, Effect: "ALLOW"},
  146. },
  147. })
  148. require.NoError(t, err)
  149. err = logic.SetUserPerms(&types.SetPermsReq{
  150. UserId: userId,
  151. Perms: []types.UserPermItem{},
  152. })
  153. require.NoError(t, err)
  154. perms := findUserPerms(t, ctx, userId)
  155. assert.Empty(t, perms)
  156. }
  157. // TC-0196: 无效Effect值
  158. func TestSetUserPerms_InvalidEffect(t *testing.T) {
  159. ctx := ctxhelper.SuperAdminCtx()
  160. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  161. conn := testutil.GetTestSqlConn()
  162. username := testutil.UniqueId()
  163. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  164. mId := insertTestMember(t, svcCtx, "test_product", userId)
  165. t.Cleanup(func() {
  166. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  167. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  168. })
  169. logic := NewSetUserPermsLogic(ctx, svcCtx)
  170. err := logic.SetUserPerms(&types.SetPermsReq{
  171. UserId: userId,
  172. Perms: []types.UserPermItem{
  173. {PermId: 1, Effect: "INVALID"},
  174. },
  175. })
  176. require.Error(t, err)
  177. var codeErr *response.CodeError
  178. require.True(t, errors.As(err, &codeErr))
  179. assert.Equal(t, 400, codeErr.Code())
  180. assert.Contains(t, codeErr.Error(), "effect值无效")
  181. }
  182. // TC-0197: PermId不存在
  183. func TestSetUserPerms_PermNotExists(t *testing.T) {
  184. ctx := ctxhelper.SuperAdminCtx()
  185. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  186. conn := testutil.GetTestSqlConn()
  187. username := testutil.UniqueId()
  188. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  189. mId := insertTestMember(t, svcCtx, "test_product", userId)
  190. t.Cleanup(func() {
  191. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  192. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  193. })
  194. logic := NewSetUserPermsLogic(ctx, svcCtx)
  195. err := logic.SetUserPerms(&types.SetPermsReq{
  196. UserId: userId,
  197. Perms: []types.UserPermItem{
  198. {PermId: 999999999, Effect: "ALLOW"},
  199. },
  200. })
  201. require.Error(t, err)
  202. var codeErr *response.CodeError
  203. require.True(t, errors.As(err, &codeErr))
  204. assert.Equal(t, 400, codeErr.Code())
  205. assert.Contains(t, codeErr.Error(), "无效的权限ID")
  206. }
  207. // TC-0198: 权限不属于当前产品
  208. func TestSetUserPerms_PermBelongsToOtherProduct(t *testing.T) {
  209. ctx := ctxhelper.SuperAdminCtx()
  210. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  211. conn := testutil.GetTestSqlConn()
  212. username := testutil.UniqueId()
  213. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  214. mId := insertTestMember(t, svcCtx, "test_product", userId)
  215. otherPerm := insertTestPerm(t, svcCtx, "other_product")
  216. t.Cleanup(func() {
  217. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  218. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  219. testutil.CleanTable(ctx, conn, "`sys_perm`", otherPerm)
  220. })
  221. logic := NewSetUserPermsLogic(ctx, svcCtx)
  222. err := logic.SetUserPerms(&types.SetPermsReq{
  223. UserId: userId,
  224. Perms: []types.UserPermItem{
  225. {PermId: otherPerm, Effect: "ALLOW"},
  226. },
  227. })
  228. require.Error(t, err)
  229. var codeErr *response.CodeError
  230. require.True(t, errors.As(err, &codeErr))
  231. assert.Equal(t, 400, codeErr.Code())
  232. assert.Contains(t, codeErr.Error(), "其他产品的权限")
  233. }
  234. // TC-0210: 同一权限ID同时为ALLOW和DENY被拒绝
  235. func TestSetUserPerms_ConflictingEffects(t *testing.T) {
  236. ctx := ctxhelper.SuperAdminCtx()
  237. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  238. conn := testutil.GetTestSqlConn()
  239. username := testutil.UniqueId()
  240. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  241. mId := insertTestMember(t, svcCtx, "test_product", userId)
  242. p1 := insertTestPerm(t, svcCtx, "test_product")
  243. t.Cleanup(func() {
  244. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  245. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  246. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  247. testutil.CleanTable(ctx, conn, "`sys_perm`", p1)
  248. })
  249. logic := NewSetUserPermsLogic(ctx, svcCtx)
  250. err := logic.SetUserPerms(&types.SetPermsReq{
  251. UserId: userId,
  252. Perms: []types.UserPermItem{
  253. {PermId: p1, Effect: "ALLOW"},
  254. {PermId: p1, Effect: "DENY"},
  255. },
  256. })
  257. require.Error(t, err)
  258. var codeErr *response.CodeError
  259. require.True(t, errors.As(err, &codeErr))
  260. assert.Equal(t, 400, codeErr.Code())
  261. assert.Contains(t, codeErr.Error(), "同一权限ID不能同时为 ALLOW 和 DENY")
  262. }
  263. // TC-0211: 重复的权限ID相同Effect被去重
  264. func TestSetUserPerms_DuplicatePermDedup(t *testing.T) {
  265. ctx := ctxhelper.SuperAdminCtx()
  266. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  267. conn := testutil.GetTestSqlConn()
  268. username := testutil.UniqueId()
  269. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  270. mId := insertTestMember(t, svcCtx, "test_product", userId)
  271. p1 := insertTestPerm(t, svcCtx, "test_product")
  272. t.Cleanup(func() {
  273. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  274. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  275. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  276. testutil.CleanTable(ctx, conn, "`sys_perm`", p1)
  277. })
  278. logic := NewSetUserPermsLogic(ctx, svcCtx)
  279. err := logic.SetUserPerms(&types.SetPermsReq{
  280. UserId: userId,
  281. Perms: []types.UserPermItem{
  282. {PermId: p1, Effect: "ALLOW"},
  283. {PermId: p1, Effect: "ALLOW"},
  284. },
  285. })
  286. require.NoError(t, err)
  287. perms := findUserPerms(t, ctx, userId)
  288. assert.Len(t, perms, 1, "重复的权限ID应被去重,只插入一条")
  289. assert.Equal(t, "ALLOW", perms[0].Effect)
  290. }
  291. // TC-0212: 已禁用的权限不能被设置
  292. func TestSetUserPerms_DisabledPermRejected(t *testing.T) {
  293. ctx := ctxhelper.SuperAdminCtx()
  294. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  295. conn := testutil.GetTestSqlConn()
  296. username := testutil.UniqueId()
  297. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  298. mId := insertTestMember(t, svcCtx, "test_product", userId)
  299. now := time.Now().Unix()
  300. res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  301. ProductCode: "test_product",
  302. Name: "disabled_perm_" + testutil.UniqueId(),
  303. Code: "disabled_" + testutil.UniqueId(),
  304. Status: 2,
  305. CreateTime: now,
  306. UpdateTime: now,
  307. })
  308. require.NoError(t, err)
  309. disabledPermId, _ := res.LastInsertId()
  310. t.Cleanup(func() {
  311. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  312. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  313. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  314. testutil.CleanTable(ctx, conn, "`sys_perm`", disabledPermId)
  315. })
  316. logic := NewSetUserPermsLogic(ctx, svcCtx)
  317. err = logic.SetUserPerms(&types.SetPermsReq{
  318. UserId: userId,
  319. Perms: []types.UserPermItem{
  320. {PermId: disabledPermId, Effect: "ALLOW"},
  321. },
  322. })
  323. require.Error(t, err)
  324. var codeErr *response.CodeError
  325. require.True(t, errors.As(err, &codeErr))
  326. assert.Equal(t, 400, codeErr.Code())
  327. assert.Contains(t, codeErr.Error(), "已被禁用")
  328. }
  329. // TC-0199: 目标用户不是当前产品成员时拒绝设置权限(修复验证)
  330. func TestSetUserPerms_NonMemberRejected(t *testing.T) {
  331. ctx := ctxhelper.SuperAdminCtx()
  332. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  333. conn := testutil.GetTestSqlConn()
  334. username := testutil.UniqueId()
  335. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  336. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  337. logic := NewSetUserPermsLogic(ctx, svcCtx)
  338. err := logic.SetUserPerms(&types.SetPermsReq{
  339. UserId: userId,
  340. Perms: []types.UserPermItem{},
  341. })
  342. require.Error(t, err)
  343. var codeErr2 *response.CodeError
  344. require.True(t, errors.As(err, &codeErr2))
  345. assert.Equal(t, 400, codeErr2.Code())
  346. assert.Contains(t, codeErr2.Error(), "不是当前产品的成员")
  347. }
  348. type lyingSysPermModel struct {
  349. permModel.SysPermModel
  350. lyingProductCode string
  351. }
  352. func (m *lyingSysPermModel) FindByIds(ctx context.Context, ids []int64) ([]*permModel.SysPerm, error) {
  353. real, err := m.SysPermModel.FindByIds(ctx, ids)
  354. if err != nil {
  355. return nil, err
  356. }
  357. for _, p := range real {
  358. p.ProductCode = m.lyingProductCode
  359. p.Status = 1
  360. }
  361. return real, nil
  362. }
  363. // TC-0988: TOCTOU 复核 —— 前置检查通过但实际 Disabled,事务末 COUNT 必须触发 409 回滚。
  364. func TestSetUserPerms_L4_TOCTOU_CountMismatch_RollsBackWith409(t *testing.T) {
  365. ctx := ctxhelper.SuperAdminCtx()
  366. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  367. conn := testutil.GetTestSqlConn()
  368. username := testutil.UniqueId()
  369. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  370. mId := insertTestMember(t, svcCtx, "test_product", userId)
  371. // 直接在 DB 里塞一个 status=Disabled 的 perm,模拟 SyncPermissions 已经提交
  372. // 把这个 perm 落盘为 Disabled 的状态。
  373. now := time.Now().Unix()
  374. res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  375. ProductCode: "test_product",
  376. Name: "l4_disabled_" + testutil.UniqueId(),
  377. Code: "l4_dis_" + testutil.UniqueId(),
  378. Status: 2, // Disabled
  379. CreateTime: now, UpdateTime: now,
  380. })
  381. require.NoError(t, err)
  382. disabledPermId, _ := res.LastInsertId()
  383. t.Cleanup(func() {
  384. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  385. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  386. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  387. testutil.CleanTable(ctx, conn, "`sys_perm`", disabledPermId)
  388. })
  389. // 装饰 SysPermModel:让 FindByIds 撒谎(Status=1, productCode=test_product)。
  390. svcCtx.SysPermModel = &lyingSysPermModel{
  391. SysPermModel: svcCtx.SysPermModel,
  392. lyingProductCode: "test_product",
  393. }
  394. err = NewSetUserPermsLogic(ctx, svcCtx).SetUserPerms(&types.SetPermsReq{
  395. UserId: userId,
  396. Perms: []types.UserPermItem{{PermId: disabledPermId, Effect: "ALLOW"}},
  397. })
  398. require.Error(t, err, "前置通过但 DB 实际 Disabled 时,事务末 COUNT 必须触发 409")
  399. var ce *response.CodeError
  400. require.True(t, errors.As(err, &ce))
  401. assert.Equal(t, 409, ce.Code(),
  402. "TOCTOU 复核必须返回 409 Conflict;若仍是 200/4xx 说明复核 COUNT 被移除,"+
  403. "脏 user_perm 会被真实落盘")
  404. assert.Contains(t, ce.Error(), "已被禁用",
  405. "错误文案必须明示'部分权限在提交时已被禁用',供前端判定是否重试")
  406. // 最关键的断言:脏行必须不可能落盘。
  407. leftover := findUserPerms(t, ctx, userId)
  408. assert.Empty(t, leftover,
  409. "事务必须回滚;如果发现 sys_user_perm 有脏行,说明 COUNT 复核失效或"+
  410. "事务隔离性被破坏,loadPerms 的 status=1 过滤能兜底但会绕开链")
  411. }
  412. // TC-0989: 正向基线 —— 所有 perm 真实 Enabled 时,不得被 复核误杀。
  413. // 这条显式"不回滚"的断言防止未来有人把 COUNT 改成 "!=" 逻辑或把阈值改错。
  414. func TestSetUserPerms_L4_AllEnabled_CountPasses(t *testing.T) {
  415. ctx := ctxhelper.SuperAdminCtx()
  416. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  417. conn := testutil.GetTestSqlConn()
  418. username := testutil.UniqueId()
  419. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  420. mId := insertTestMember(t, svcCtx, "test_product", userId)
  421. p1 := insertTestPerm(t, svcCtx, "test_product")
  422. p2 := insertTestPerm(t, svcCtx, "test_product")
  423. t.Cleanup(func() {
  424. testutil.CleanTableByField(ctx, conn, "`sys_user_perm`", "userId", userId)
  425. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  426. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  427. testutil.CleanTable(ctx, conn, "`sys_perm`", p1, p2)
  428. })
  429. err := NewSetUserPermsLogic(ctx, svcCtx).SetUserPerms(&types.SetPermsReq{
  430. UserId: userId,
  431. Perms: []types.UserPermItem{
  432. {PermId: p1, Effect: "ALLOW"},
  433. {PermId: p2, Effect: "DENY"},
  434. },
  435. })
  436. require.NoError(t, err, "不得误杀正常写入;一旦误报会把正常管理操作变 409")
  437. rows := findUserPerms(t, ctx, userId)
  438. assert.Len(t, rows, 2, "两条 user_perm 必须落盘")
  439. }
  440. func TestSetUserPerms_MemberCannotSelfEscalate(t *testing.T) {
  441. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  442. conn := testutil.GetTestSqlConn()
  443. now := time.Now().Unix()
  444. bootstrap := context.Background()
  445. code := testutil.UniqueId()
  446. pRes, err := svcCtx.SysProductModel.Insert(bootstrap, &productModel.SysProduct{
  447. Code: code, Name: "p_" + code, AppKey: code + "_k", AppSecret: "s",
  448. Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
  449. })
  450. require.NoError(t, err)
  451. pId, _ := pRes.LastInsertId()
  452. username := testutil.UniqueId()
  453. userId := insertTestUser(t, bootstrap, username, testutil.HashPassword("pw"))
  454. mId := insertTestMember(t, svcCtx, code, userId)
  455. permRes, err := svcCtx.SysPermModel.Insert(bootstrap, &permModel.SysPerm{
  456. ProductCode: code, Name: "escalate_p", Code: "esc_" + testutil.UniqueId(),
  457. Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
  458. })
  459. require.NoError(t, err)
  460. permId, _ := permRes.LastInsertId()
  461. t.Cleanup(func() {
  462. testutil.CleanTableByField(bootstrap, conn, "`sys_user_perm`", "userId", userId)
  463. testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
  464. testutil.CleanTable(bootstrap, conn, "`sys_perm`", permId)
  465. testutil.CleanTable(bootstrap, conn, "`sys_user`", userId)
  466. testutil.CleanTable(bootstrap, conn, "`sys_product`", pId)
  467. })
  468. // caller = 目标用户本人,MemberType=MEMBER(非 ADMIN)
  469. callerCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
  470. UserId: userId, Username: username,
  471. IsSuperAdmin: false,
  472. MemberType: consts.MemberTypeMember,
  473. Status: consts.StatusEnabled,
  474. ProductCode: code,
  475. DeptId: 1,
  476. DeptPath: "/1/",
  477. MinPermsLevel: math.MaxInt64,
  478. })
  479. err = NewSetUserPermsLogic(callerCtx, svcCtx).SetUserPerms(&types.SetPermsReq{
  480. UserId: userId, // 给自己
  481. Perms: []types.UserPermItem{{PermId: permId, Effect: consts.PermEffectAllow}},
  482. })
  483. require.Error(t, err, "MEMBER 不得自我授权")
  484. var ce *response.CodeError
  485. require.True(t, errors.As(err, &ce))
  486. assert.Equal(t, 403, ce.Code())
  487. assert.Contains(t, ce.Error(), "仅超级管理员或该产品的管理员可执行此操作")
  488. // 二次确认:没有任何 user_perm 记录被写入
  489. rows := findUserPerms(t, bootstrap, userId)
  490. assert.Len(t, rows, 0, "被拒绝的 SetUserPerms 不得在 DB 残留任何个性化权限")
  491. }
  492. // TC-0744: -A 修复回归 —— DEVELOPER 调用者(非 ADMIN)同样被拦截,即便目标不是自己。
  493. func TestSetUserPerms_DeveloperCallerRejected(t *testing.T) {
  494. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  495. conn := testutil.GetTestSqlConn()
  496. bootstrap := context.Background()
  497. now := time.Now().Unix()
  498. code := testutil.UniqueId()
  499. pRes, err := svcCtx.SysProductModel.Insert(bootstrap, &productModel.SysProduct{
  500. Code: code, Name: "p_" + code, AppKey: code + "_k", AppSecret: "s",
  501. Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
  502. })
  503. require.NoError(t, err)
  504. pId, _ := pRes.LastInsertId()
  505. targetUsername := "target_" + testutil.UniqueId()
  506. targetId := insertTestUser(t, bootstrap, targetUsername, testutil.HashPassword("pw"))
  507. mId := insertTestMember(t, svcCtx, code, targetId)
  508. t.Cleanup(func() {
  509. testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
  510. testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
  511. testutil.CleanTable(bootstrap, conn, "`sys_product`", pId)
  512. })
  513. devCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
  514. UserId: 777777, Username: "dev_caller",
  515. MemberType: consts.MemberTypeDeveloper, Status: consts.StatusEnabled,
  516. ProductCode: code, DeptId: 1, DeptPath: "/1/", MinPermsLevel: math.MaxInt64,
  517. })
  518. err = NewSetUserPermsLogic(devCtx, svcCtx).SetUserPerms(&types.SetPermsReq{
  519. UserId: targetId, Perms: []types.UserPermItem{},
  520. })
  521. require.Error(t, err)
  522. var ce *response.CodeError
  523. require.True(t, errors.As(err, &ce))
  524. assert.Equal(t, 403, ce.Code())
  525. assert.Contains(t, ce.Error(), "仅超级管理员或该产品的管理员可执行此操作")
  526. }
  527. // TC-0745: -A 正向回归 —— 同产品 ADMIN 操作合法 MEMBER 目标(非自己)依旧放行。
  528. func TestSetUserPerms_ProductAdminStillWorks(t *testing.T) {
  529. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  530. conn := testutil.GetTestSqlConn()
  531. bootstrap := context.Background()
  532. now := time.Now().Unix()
  533. code := testutil.UniqueId()
  534. pRes, err := svcCtx.SysProductModel.Insert(bootstrap, &productModel.SysProduct{
  535. Code: code, Name: "p_" + code, AppKey: code + "_k", AppSecret: "s",
  536. Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
  537. })
  538. require.NoError(t, err)
  539. pId, _ := pRes.LastInsertId()
  540. targetId := insertTestUser(t, bootstrap, "tgt_"+testutil.UniqueId(), testutil.HashPassword("pw"))
  541. mId := insertTestMember(t, svcCtx, code, targetId)
  542. permRes, err := svcCtx.SysPermModel.Insert(bootstrap, &permModel.SysPerm{
  543. ProductCode: code, Name: "ok_p", Code: "ok_" + testutil.UniqueId(),
  544. Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
  545. })
  546. require.NoError(t, err)
  547. permId, _ := permRes.LastInsertId()
  548. t.Cleanup(func() {
  549. testutil.CleanTableByField(bootstrap, conn, "`sys_user_perm`", "userId", targetId)
  550. testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
  551. testutil.CleanTable(bootstrap, conn, "`sys_perm`", permId)
  552. testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
  553. testutil.CleanTable(bootstrap, conn, "`sys_product`", pId)
  554. })
  555. adminCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
  556. UserId: 999999, Username: "admin_caller",
  557. MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled,
  558. ProductCode: code, DeptId: 1, DeptPath: "/1/", MinPermsLevel: math.MaxInt64,
  559. })
  560. err = NewSetUserPermsLogic(adminCtx, svcCtx).SetUserPerms(&types.SetPermsReq{
  561. UserId: targetId,
  562. Perms: []types.UserPermItem{{PermId: permId, Effect: consts.PermEffectAllow}},
  563. })
  564. require.NoError(t, err, "产品 ADMIN 正常路径必须放行")
  565. rows := findUserPerms(t, bootstrap, targetId)
  566. assert.Len(t, rows, 1, "ADMIN 授权后 DB 应有 1 条 user_perm")
  567. }