| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- package user
- import (
- "context"
- "errors"
- "math"
- "testing"
- "perms-system-server/internal/consts"
- "perms-system-server/internal/loaders"
- "perms-system-server/internal/middleware"
- "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"
- )
- // ---------------------------------------------------------------------------
- // 覆盖目标:审计 H-4 修复 —— "把用户移出部门树(deptId=0)" 的破坏组织结构操作,
- // 仅能由 SuperAdmin 或产品 ADMIN 执行。DEVELOPER / MEMBER 执行必须 403,并且 DB 不得发生变更。
- // 修复前这是横向越权点:下级把上级拉出部门树后,后续 checkDeptHierarchy 对该目标彻底失效。
- // ---------------------------------------------------------------------------
- // TC-0814: H-4 —— DEVELOPER 调用者执行 deptId=0 的 UpdateUser 必须 403,且 target.DeptId 不动。
- func TestUpdateUser_DeveloperCannotMoveTargetOutOfDept(t *testing.T) {
- bootstrap := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- callerDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "h4_caller_dev", "/700/")
- targetDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "h4_target_dev", "/700/1/")
- targetId := insertTestUserWithDept(t, bootstrap, "h4_dev", targetDeptId)
- mId := insertTestMember(t, svcCtx, "test_product", targetId)
- t.Cleanup(func() {
- testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
- testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
- testutil.CleanTable(bootstrap, conn, "`sys_dept`", callerDeptId, targetDeptId)
- })
- devCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
- UserId: 88881,
- Username: "h4_dev",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeDeveloper,
- Status: consts.StatusEnabled,
- ProductCode: "test_product",
- DeptId: callerDeptId,
- DeptPath: "/700/",
- MinPermsLevel: math.MaxInt64,
- })
- zero := int64(0)
- err := NewUpdateUserLogic(devCtx, svcCtx).UpdateUser(&types.UpdateUserReq{
- Id: targetId,
- DeptId: &zero,
- })
- require.Error(t, err, "H-4:DEVELOPER 不得把目标移出部门树")
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- assert.Contains(t, ce.Error(), "仅超级管理员或产品管理员可将用户移出部门")
- u, err := svcCtx.SysUserModel.FindOne(bootstrap, targetId)
- require.NoError(t, err)
- assert.Equal(t, targetDeptId, u.DeptId, "被拒绝的请求对 DB 零副作用")
- }
- // TC-0815: H-4 —— MEMBER 调用者同理被拒(即便是修改自身的其他字段也不能顺手把自己移出部门)。
- // 用户修改自身时,路由层 if caller.UserId == req.Id 分支只拦 DeptId != nil/Status != 0;
- // 但修改他人为 deptId=0 的分支仍必须 403,以防任何下级调用者漂白组织结构。
- func TestUpdateUser_MemberCannotMoveOtherOutOfDept(t *testing.T) {
- bootstrap := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- callerDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "h4_member_caller", "/800/")
- targetDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "h4_member_target", "/800/1/")
- targetId := insertTestUserWithDept(t, bootstrap, "h4_mem", targetDeptId)
- mId := insertTestMember(t, svcCtx, "test_product", targetId)
- t.Cleanup(func() {
- testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
- testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
- testutil.CleanTable(bootstrap, conn, "`sys_dept`", callerDeptId, targetDeptId)
- })
- memberCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
- UserId: 88882,
- Username: "h4_mem",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeMember,
- Status: consts.StatusEnabled,
- ProductCode: "test_product",
- DeptId: callerDeptId,
- DeptPath: "/800/",
- MinPermsLevel: 10,
- })
- zero := int64(0)
- err := NewUpdateUserLogic(memberCtx, svcCtx).UpdateUser(&types.UpdateUserReq{
- Id: targetId,
- DeptId: &zero,
- })
- require.Error(t, err, "H-4:MEMBER 更不得移出他人")
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- u, err := svcCtx.SysUserModel.FindOne(bootstrap, targetId)
- require.NoError(t, err)
- assert.Equal(t, targetDeptId, u.DeptId)
- }
- // TC-0816: H-4 —— 产品 ADMIN 有权将他人移出部门(功能不应被修复路径误伤)。
- func TestUpdateUser_ProductAdminCanMoveTargetOutOfDept(t *testing.T) {
- bootstrap := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- adminDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "h4_admin", "/900/")
- targetDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "h4_admin_target", "/900/1/")
- targetId := insertTestUserWithDept(t, bootstrap, "h4_admin_tgt", targetDeptId)
- mId := insertTestMember(t, svcCtx, "test_product", targetId)
- t.Cleanup(func() {
- testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
- testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
- testutil.CleanTable(bootstrap, conn, "`sys_dept`", adminDeptId, targetDeptId)
- })
- adminCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
- UserId: 88883, Username: "h4_admin",
- IsSuperAdmin: false, MemberType: consts.MemberTypeAdmin,
- Status: consts.StatusEnabled, ProductCode: "test_product",
- DeptId: adminDeptId, DeptPath: "/900/", MinPermsLevel: math.MaxInt64,
- })
- zero := int64(0)
- require.NoError(t,
- NewUpdateUserLogic(adminCtx, svcCtx).UpdateUser(&types.UpdateUserReq{
- Id: targetId, DeptId: &zero,
- }),
- "产品 ADMIN 必须仍能执行 deptId=0 的合法运维操作")
- u, err := svcCtx.SysUserModel.FindOne(bootstrap, targetId)
- require.NoError(t, err)
- assert.Equal(t, int64(0), u.DeptId, "ADMIN 的合法 deptId=0 操作必须落盘")
- }
- // TC-0817: H-4 —— SuperAdmin 有权将他人移出部门(豁免路径)。
- func TestUpdateUser_SuperAdminCanMoveTargetOutOfDept(t *testing.T) {
- bootstrap := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- targetDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "h4_sa_target", "/950/")
- targetId := insertTestUserWithDept(t, bootstrap, "h4_sa_tgt", targetDeptId)
- mId := insertTestMember(t, svcCtx, "test_product", targetId)
- t.Cleanup(func() {
- testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
- testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
- testutil.CleanTable(bootstrap, conn, "`sys_dept`", targetDeptId)
- })
- zero := int64(0)
- require.NoError(t,
- NewUpdateUserLogic(ctxhelper.SuperAdminCtx(), svcCtx).UpdateUser(&types.UpdateUserReq{
- Id: targetId, DeptId: &zero,
- }),
- "SuperAdmin 的 deptId=0 操作是合法的顶层运维")
- u, err := svcCtx.SysUserModel.FindOne(bootstrap, targetId)
- require.NoError(t, err)
- assert.Equal(t, int64(0), u.DeptId)
- }
|