| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- 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 不得落地任何行")
- }
|