|
@@ -80,8 +80,9 @@ func TestBindRoles_Success(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{r1, r2},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{r1, r2},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
@@ -129,14 +130,16 @@ func TestBindRoles_EmptyRoleIds_ClearsAll(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{r1},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{r1},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
err = logic.BindRoles(&types.BindRolesReq{
|
|
err = logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
@@ -168,14 +171,16 @@ func TestBindRoles_Rebind(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{r1, r2},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{r1, r2},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
err = logic.BindRoles(&types.BindRolesReq{
|
|
err = logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{r2, r3},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{r2, r3},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
@@ -205,8 +210,9 @@ func TestBindRoles_RoleBelongsToOtherProduct(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{otherRole},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{otherRole},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.Error(t, err)
|
|
require.Error(t, err)
|
|
|
|
|
|
|
@@ -237,8 +243,9 @@ func TestBindRoles_RoleDisabled(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{disabledRole},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{disabledRole},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.Error(t, err)
|
|
require.Error(t, err)
|
|
|
|
|
|
|
@@ -283,7 +290,7 @@ func TestBindRoles_L_R14_2_InvalidIdsUnifiedMessage(t *testing.T) {
|
|
|
var codes []int
|
|
var codes []int
|
|
|
var msgs []string
|
|
var msgs []string
|
|
|
for _, c := range cases {
|
|
for _, c := range cases {
|
|
|
- err := logic.BindRoles(&types.BindRolesReq{UserId: userId, RoleIds: c.roleIds})
|
|
|
|
|
|
|
+ err := logic.BindRoles(&types.BindRolesReq{UserId: userId, RoleIds: c.roleIds, ProductCode: "test_product"})
|
|
|
require.Error(t, err, c.name)
|
|
require.Error(t, err, c.name)
|
|
|
var ce *response.CodeError
|
|
var ce *response.CodeError
|
|
|
require.True(t, errors.As(err, &ce), c.name)
|
|
require.True(t, errors.As(err, &ce), c.name)
|
|
@@ -317,8 +324,9 @@ func TestBindRoles_RoleNotExists(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{999999999},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{999999999},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.Error(t, err)
|
|
require.Error(t, err)
|
|
|
|
|
|
|
@@ -426,8 +434,9 @@ func TestBindRoles_PermsLevelEscalation_Rejected(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: targetUserId,
|
|
|
|
|
- RoleIds: []int64{highLevelRole},
|
|
|
|
|
|
|
+ UserId: targetUserId,
|
|
|
|
|
+ RoleIds: []int64{highLevelRole},
|
|
|
|
|
+ ProductCode: productCode,
|
|
|
})
|
|
})
|
|
|
require.Error(t, err)
|
|
require.Error(t, err)
|
|
|
var ce *response.CodeError
|
|
var ce *response.CodeError
|
|
@@ -476,8 +485,9 @@ func TestBindRoles_AdminBypassesPermsLevelCheck(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{lowLevelRole},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{lowLevelRole},
|
|
|
|
|
+ ProductCode: productCode,
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err, "ADMIN 调用者应当能绑定任意级别的角色")
|
|
require.NoError(t, err, "ADMIN 调用者应当能绑定任意级别的角色")
|
|
|
|
|
|
|
@@ -527,8 +537,9 @@ func TestBindRoles_DeveloperBypassesPermsLevelCheck(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{lowLevelRole},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{lowLevelRole},
|
|
|
|
|
+ ProductCode: productCode,
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err, "DEVELOPER 调用者应当能绑定任意级别的角色")
|
|
require.NoError(t, err, "DEVELOPER 调用者应当能绑定任意级别的角色")
|
|
|
}
|
|
}
|
|
@@ -575,8 +586,9 @@ func TestBindRoles_MemberWithSentinelMinLevel_NotBlocked(t *testing.T) {
|
|
|
// bindRoles 内部的 permsLevel 分支。实际发生于 ADMIN 通过上层校验但 MemberType 上下文异常时的防御。
|
|
// bindRoles 内部的 permsLevel 分支。实际发生于 ADMIN 通过上层校验但 MemberType 上下文异常时的防御。
|
|
|
// 这里只断言:"sentinel 路径不应报 403 '不能分配权限级别高于自身的角色'"。
|
|
// 这里只断言:"sentinel 路径不应报 403 '不能分配权限级别高于自身的角色'"。
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{role},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{role},
|
|
|
|
|
+ ProductCode: productCode,
|
|
|
})
|
|
})
|
|
|
// 调用者非 ADMIN,且是 MEMBER,上游会拦 403 "仅ADMIN/超管可绑定角色";
|
|
// 调用者非 ADMIN,且是 MEMBER,上游会拦 403 "仅ADMIN/超管可绑定角色";
|
|
|
// 此处我们只校验"即使走到 permsLevel 分支,sentinel MinPermsLevel 不应命中"
|
|
// 此处我们只校验"即使走到 permsLevel 分支,sentinel MinPermsLevel 不应命中"
|
|
@@ -610,8 +622,9 @@ func TestBindRoles_SuperAdminCanAssignAnyLevel(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{highLevelRole},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{highLevelRole},
|
|
|
|
|
+ ProductCode: productCode,
|
|
|
})
|
|
})
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
@@ -632,8 +645,9 @@ func TestBindRoles_NonMemberRejected(t *testing.T) {
|
|
|
|
|
|
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
logic := NewBindRolesLogic(ctx, svcCtx)
|
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
err := logic.BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{},
|
|
|
|
|
+ ProductCode: "test_product",
|
|
|
})
|
|
})
|
|
|
require.Error(t, err)
|
|
require.Error(t, err)
|
|
|
|
|
|
|
@@ -711,8 +725,9 @@ func TestBindRoles_Vs_DeleteRole_NoOrphanRows(t *testing.T) {
|
|
|
defer wg.Done()
|
|
defer wg.Done()
|
|
|
<-start
|
|
<-start
|
|
|
err := NewBindRolesLogic(superCtx, svcCtx).BindRoles(&types.BindRolesReq{
|
|
err := NewBindRolesLogic(superCtx, svcCtx).BindRoles(&types.BindRolesReq{
|
|
|
- UserId: userId,
|
|
|
|
|
- RoleIds: []int64{roleId},
|
|
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{roleId},
|
|
|
|
|
+ ProductCode: productCode,
|
|
|
})
|
|
})
|
|
|
if err == nil {
|
|
if err == nil {
|
|
|
bindOK.Store(true)
|
|
bindOK.Store(true)
|
|
@@ -862,8 +877,9 @@ func TestBindRoles_EqualPermsLevel_Rejected(t *testing.T) {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
err := NewBindRolesLogic(ctx, svcCtx).BindRoles(&types.BindRolesReq{
|
|
err := NewBindRolesLogic(ctx, svcCtx).BindRoles(&types.BindRolesReq{
|
|
|
- UserId: targetUserId,
|
|
|
|
|
- RoleIds: []int64{sameLevelRole},
|
|
|
|
|
|
|
+ UserId: targetUserId,
|
|
|
|
|
+ RoleIds: []int64{sameLevelRole},
|
|
|
|
|
+ ProductCode: productCode,
|
|
|
})
|
|
})
|
|
|
require.Error(t, err, "同级角色分配必须被拒绝(含同级)")
|
|
require.Error(t, err, "同级角色分配必须被拒绝(含同级)")
|
|
|
var ce *response.CodeError
|
|
var ce *response.CodeError
|
|
@@ -933,3 +949,71 @@ func TestBindRoles_L_R13_1_SuperAdminWithEmptyMemberTypeStillProceeds(t *testing
|
|
|
"超管不应被 L-R13-1 闸误伤,应穿透到 SysUserModel.FindOne 并返 404")
|
|
"超管不应被 L-R13-1 闸误伤,应穿透到 SysUserModel.FindOne 并返 404")
|
|
|
assert.Equal(t, "用户不存在", ce.Error())
|
|
assert.Equal(t, "用户不存在", ce.Error())
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// TC-1265: 非超管(ADMIN)传入 req.ProductCode 指向其他产品时,该字段必须被忽略,
|
|
|
|
|
+// 始终使用 JWT context 中的 productCode,不允许跨产品操作。
|
|
|
|
|
+// 安全约束:若非超管能通过 req.ProductCode 切换产品,则可绕过 CheckManageAccess 的产品隔离。
|
|
|
|
|
+func TestBindRoles_NonSuperAdmin_ReqProductCodeIgnored(t *testing.T) {
|
|
|
|
|
+ svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
|
|
|
|
|
+ conn := testutil.GetTestSqlConn()
|
|
|
|
|
+ superCtx := ctxhelper.SuperAdminCtx()
|
|
|
|
|
+
|
|
|
|
|
+ productCode := "test_product"
|
|
|
|
|
+ username := testutil.UniqueId()
|
|
|
|
|
+ userId := insertTestUser(t, superCtx, username, testutil.HashPassword("pass"))
|
|
|
|
|
+ mId := insertTestMember(t, svcCtx, productCode, userId)
|
|
|
|
|
+ role := insertTestRole(t, svcCtx, productCode, consts.StatusEnabled)
|
|
|
|
|
+
|
|
|
|
|
+ t.Cleanup(func() {
|
|
|
|
|
+ testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", userId)
|
|
|
|
|
+ testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId)
|
|
|
|
|
+ testutil.CleanTable(superCtx, conn, "`sys_user`", userId)
|
|
|
|
|
+ testutil.CleanTable(superCtx, conn, "`sys_role`", role)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // ADMIN caller 的 JWT context productCode = "test_product",
|
|
|
|
|
+ // 请求体传入 "other_product"——非超管时该字段必须被忽略。
|
|
|
|
|
+ ctx := ctxhelper.AdminCtx(productCode)
|
|
|
|
|
+ err := NewBindRolesLogic(ctx, svcCtx).BindRoles(&types.BindRolesReq{
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{role},
|
|
|
|
|
+ ProductCode: "other_product", // 非超管时此字段应被忽略
|
|
|
|
|
+ })
|
|
|
|
|
+ require.NoError(t, err, "非超管传入其他产品的 productCode 应被忽略,仍按 JWT context 产品操作")
|
|
|
|
|
+
|
|
|
|
|
+ roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, userId)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ assert.Contains(t, roleIds, role, "角色应成功绑定到 JWT context 对应的产品下")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TC-1266: 非超管(ADMIN)不传 req.ProductCode,使用 JWT context 中的 productCode 正常绑定。
|
|
|
|
|
+func TestBindRoles_NonSuperAdmin_UsesCtxProductCode(t *testing.T) {
|
|
|
|
|
+ svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
|
|
|
|
|
+ conn := testutil.GetTestSqlConn()
|
|
|
|
|
+ superCtx := ctxhelper.SuperAdminCtx()
|
|
|
|
|
+
|
|
|
|
|
+ productCode := "test_product"
|
|
|
|
|
+ username := testutil.UniqueId()
|
|
|
|
|
+ userId := insertTestUser(t, superCtx, username, testutil.HashPassword("pass"))
|
|
|
|
|
+ mId := insertTestMember(t, svcCtx, productCode, userId)
|
|
|
|
|
+ role := insertTestRole(t, svcCtx, productCode, consts.StatusEnabled)
|
|
|
|
|
+
|
|
|
|
|
+ t.Cleanup(func() {
|
|
|
|
|
+ testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", userId)
|
|
|
|
|
+ testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId)
|
|
|
|
|
+ testutil.CleanTable(superCtx, conn, "`sys_user`", userId)
|
|
|
|
|
+ testutil.CleanTable(superCtx, conn, "`sys_role`", role)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ ctx := ctxhelper.AdminCtx(productCode)
|
|
|
|
|
+ err := NewBindRolesLogic(ctx, svcCtx).BindRoles(&types.BindRolesReq{
|
|
|
|
|
+ UserId: userId,
|
|
|
|
|
+ RoleIds: []int64{role},
|
|
|
|
|
+ // ProductCode 不传,应自动使用 JWT context 中的 "test_product"
|
|
|
|
|
+ })
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, userId)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ assert.Contains(t, roleIds, role)
|
|
|
|
|
+}
|