package user import ( "errors" "testing" "time" "perms-system-server/internal/consts" "perms-system-server/internal/loaders" userModel "perms-system-server/internal/model/user" userroleModel "perms-system-server/internal/model/userrole" "perms-system-server/internal/response" "perms-system-server/internal/svc" "perms-system-server/internal/testutil" "perms-system-server/internal/testutil/ctxhelper" "perms-system-server/internal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // seedCallerWithRoleLevel 为调用者落地真实 DB 记录(user + role + user_role), // 保证审计 M-3 修复(GuardRoleLevelAssignable 的 fresh DB 读)生效后, // FindMinPermsLevelByUserIdAndProductCode 能命中调用者真实的 permsLevel。 // 修复前的测试用假 UserId 即可走通,修复后必须落地真实关系链才能触发越级拦截。 func seedCallerWithRoleLevel(t *testing.T, svcCtx *svc.ServiceContext, productCode string, callerLevel int64) (int64, func()) { t.Helper() superCtx := ctxhelper.SuperAdminCtx() conn := testutil.GetTestSqlConn() callerUserId := insertTestUserFull(t, superCtx, &userModel.SysUser{ Username: "caller_" + testutil.UniqueId(), Password: testutil.HashPassword("pass"), Nickname: "caller_seed", DeptId: 0, IsSuperAdmin: consts.IsSuperAdminNo, MustChangePassword: 2, Status: consts.StatusEnabled, }) mId := insertTestMember(t, svcCtx, productCode, callerUserId) roleId := insertTestRoleWithLevel(t, svcCtx, productCode, consts.StatusEnabled, callerLevel) now := time.Now().Unix() _, err := svcCtx.SysUserRoleModel.Insert(superCtx, &userroleModel.SysUserRole{ UserId: callerUserId, RoleId: roleId, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) cleanup := func() { testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", callerUserId) testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId) testutil.CleanTable(superCtx, conn, "`sys_user`", callerUserId) testutil.CleanTable(superCtx, conn, "`sys_role`", roleId) } return callerUserId, cleanup } // --------------------------------------------------------------------------- // 覆盖目标:审计 H-3 修复 —— "不能分配与自己同级(或更高)的角色"。 // 修复前代码仅拦 `>` 严格高于,允许 MEMBER 调用者把同级角色分配给别人,继而下一次 BindRoles 时 // 由于同级权限集相同,可用后续 upgrade 路径放大;修复后变为 `<=`(含同级)拦截。 // 本文件作为"同级也必须 403"的契约锚点。 // --------------------------------------------------------------------------- // TC-0813: H-3 —— MEMBER 调用者不能分配与自己同 permsLevel 的角色。 func TestBindRoles_EqualPermsLevel_Rejected(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() superCtx := ctxhelper.SuperAdminCtx() deptId, deptPath, cleanupDept := setupDeptForCaller(t, svcCtx) t.Cleanup(cleanupDept) productCode := "test_product" username := testutil.UniqueId() targetUserId := insertTestUserFull(t, superCtx, &userModel.SysUser{ Username: username, Password: testutil.HashPassword("pass"), Nickname: "tgt_eq", DeptId: deptId, IsSuperAdmin: consts.IsSuperAdminNo, MustChangePassword: 2, Status: consts.StatusEnabled, }) mId := insertTestMember(t, svcCtx, productCode, targetUserId) const callerLevel int64 = 50 sameLevelRole := insertTestRoleWithLevel(t, svcCtx, productCode, consts.StatusEnabled, callerLevel) // M-3 修复后 GuardRoleLevelAssignable 走 DB 强一致读取 caller 的 MinPermsLevel, // 因此需要在 DB 里为调用者落地真实的 user + role + user_role 关系链。 callerUserId, callerCleanup := seedCallerWithRoleLevel(t, svcCtx, productCode, callerLevel) t.Cleanup(callerCleanup) t.Cleanup(func() { testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", targetUserId) testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId) testutil.CleanTable(superCtx, conn, "`sys_user`", targetUserId) testutil.CleanTable(superCtx, conn, "`sys_role`", sameLevelRole) }) ctx := ctxhelper.CustomCtx(&loaders.UserDetails{ UserId: callerUserId, Username: "member_eq_level", IsSuperAdmin: false, MemberType: consts.MemberTypeMember, Status: consts.StatusEnabled, ProductCode: productCode, DeptId: deptId, DeptPath: deptPath, MinPermsLevel: callerLevel, }) err := NewBindRolesLogic(ctx, svcCtx).BindRoles(&types.BindRolesReq{ UserId: targetUserId, RoleIds: []int64{sameLevelRole}, }) require.Error(t, err, "H-3 防线:同级角色分配必须被拒绝(含同级)") var ce *response.CodeError require.True(t, errors.As(err, &ce)) assert.Equal(t, 403, ce.Code()) assert.Contains(t, ce.Error(), "不能分配权限级别高于自身的角色", "错误消息应当明确点出'含同级'的拦截语义") // 同时验证 DB 未产生任何 user-role 关系。 rids, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserIdForProduct(ctx, targetUserId, productCode) require.NoError(t, err) assert.Empty(t, rids, "被拒绝的 BindRoles 不得落地任何行") }