Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions internal/dms/biz/op_permission_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,16 @@ func (o *OpPermissionVerifyUsecase) GetUserProject(ctx context.Context, userUid
return projects, nil
}

// UserCanOpDB 判断用户在 dbServiceUid 上是否具备 needOpPermissionTypes 中的任一权限。
//
// 两种授权范围的判定语义不同:
// - db_service:授予到具体数据源,需 RangeUIDs 命中 dbServiceUid;
// - project:授予到整个项目,视为命中项目内全部数据源(如「脱敏审核」700038)。
//
// project 分支不会放大数据源级权限:范围为 project 的记录只可能来自成员/成员组的
// 「项目管理权限」槽,而该槽的可选项由 ListProjectOpPermissions 限定为注册范围是
// project 的权限;数据源级授权走角色,其可选项由 ListMemberOpPermissions 明确排除
// project 范围。两个入口互斥,db_service 权限不会被标记成 project。
func (o *OpPermissionVerifyUsecase) UserCanOpDB(userOpPermissions []OpPermissionWithOpRange, needOpPermissionTypes []string, dbServiceUid string) bool {
for _, userOpPermission := range userOpPermissions {
// 项目管理员可以查看所有数据源
Expand All @@ -410,13 +420,19 @@ func (o *OpPermissionVerifyUsecase) UserCanOpDB(userOpPermissions []OpPermission

// 动作权限(创建、审核、上线工单等)
for _, needOpPermission := range needOpPermissionTypes {
if needOpPermission == userOpPermission.OpPermissionUID && userOpPermission.OpRangeType == OpRangeType(dmsV1.OpRangeTypeDBService) {
if needOpPermission != userOpPermission.OpPermissionUID {
continue
}
switch userOpPermission.OpRangeType {
case OpRangeType(dmsV1.OpRangeTypeDBService):
// 对象权限(指定数据源)
for _, id := range userOpPermission.RangeUIDs {
if id == dbServiceUid {
return true
}
}
case OpRangeType(dmsV1.OpRangeTypeProject):
return true
}
}
}
Expand Down Expand Up @@ -481,6 +497,8 @@ func (o *OpPermissionVerifyUsecase) GetCanOpDBUsers(ctx context.Context, project

// userCanOpDBWithoutAdminPrivilege checks DB operation permission without
// considering ProjectAdmin privilege (used for BWP-disabled system administrators).
// 范围判定与 UserCanOpDB 保持一致:project 范围视为命中项目内全部数据源,
// 否则 BWP 关闭的管理员会因项目级授权被漏判。
func (o *OpPermissionVerifyUsecase) userCanOpDBWithoutAdminPrivilege(userOpPermissions []OpPermissionWithOpRange, needOpPermissionTypes []string, dbServiceUid string) bool {
for _, userOpPermission := range userOpPermissions {
// Skip ProjectAdmin check - that's the admin privilege we're excluding
Expand All @@ -489,12 +507,18 @@ func (o *OpPermissionVerifyUsecase) userCanOpDBWithoutAdminPrivilege(userOpPermi
}

for _, needOpPermission := range needOpPermissionTypes {
if needOpPermission == userOpPermission.OpPermissionUID && userOpPermission.OpRangeType == OpRangeType(dmsV1.OpRangeTypeDBService) {
if needOpPermission != userOpPermission.OpPermissionUID {
continue
}
switch userOpPermission.OpRangeType {
case OpRangeType(dmsV1.OpRangeTypeDBService):
for _, id := range userOpPermission.RangeUIDs {
if id == dbServiceUid {
return true
}
}
case OpRangeType(dmsV1.OpRangeTypeProject):
return true
}
}
}
Expand Down
126 changes: 125 additions & 1 deletion internal/dms/biz/op_permission_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ func TestGetCanOpDBUsers(t *testing.T) {
members []ListMembersOpPermissionItem
users map[string]*User
isBusinessWrite bool
needPermUID string
wantUserUIDs []string
}{
{
Expand All @@ -340,6 +341,7 @@ func TestGetCanOpDBUsers(t *testing.T) {
pkgConst.UIDOfUserAdmin: {UID: pkgConst.UIDOfUserAdmin, BusinessWritePermission: true},
},
isBusinessWrite: true,
needPermUID: opPermExportApproval,
wantUserUIDs: []string{pkgConst.UIDOfUserAdmin},
},
{
Expand All @@ -357,6 +359,7 @@ func TestGetCanOpDBUsers(t *testing.T) {
pkgConst.UIDOfUserAdmin: {UID: pkgConst.UIDOfUserAdmin, BusinessWritePermission: false},
},
isBusinessWrite: true,
needPermUID: opPermExportApproval,
// Admin has ProjectAdmin but BWP=false skips admin privilege;
// userCanOpDBWithoutAdminPrivilege skips ProjectAdmin -> not included
wantUserUIDs: []string{},
Expand All @@ -381,6 +384,7 @@ func TestGetCanOpDBUsers(t *testing.T) {
pkgConst.UIDOfUserAdmin: {UID: pkgConst.UIDOfUserAdmin, BusinessWritePermission: false},
},
isBusinessWrite: true,
needPermUID: opPermExportApproval,
// Admin has BWP=false but also has explicit DB permission -> included via project auth
wantUserUIDs: []string{pkgConst.UIDOfUserAdmin},
},
Expand Down Expand Up @@ -411,6 +415,7 @@ func TestGetCanOpDBUsers(t *testing.T) {
"normal_user_1": {UID: "normal_user_1", BusinessWritePermission: true},
},
isBusinessWrite: true,
needPermUID: opPermExportApproval,
// Only normal user has explicit DB permission; admin BWP=false without explicit auth
wantUserUIDs: []string{"normal_user_1"},
},
Expand All @@ -429,6 +434,7 @@ func TestGetCanOpDBUsers(t *testing.T) {
pkgConst.UIDOfUserAdmin: {UID: pkgConst.UIDOfUserAdmin, BusinessWritePermission: false},
},
isBusinessWrite: false,
needPermUID: opPermExportApproval,
// Resource config: BWP doesn't affect, admin privilege applies
wantUserUIDs: []string{pkgConst.UIDOfUserAdmin},
},
Expand All @@ -451,6 +457,7 @@ func TestGetCanOpDBUsers(t *testing.T) {
"normal_user_1": {UID: "normal_user_1", BusinessWritePermission: true},
},
isBusinessWrite: true,
needPermUID: opPermExportApproval,
wantUserUIDs: []string{"normal_user_1"},
},
{
Expand All @@ -466,8 +473,89 @@ func TestGetCanOpDBUsers(t *testing.T) {
"normal_user_1": {UID: "normal_user_1", BusinessWritePermission: true},
},
isBusinessWrite: true,
needPermUID: opPermExportApproval,
wantUserUIDs: []string{},
},
{
name: "project_range_masking_audit_hits_all_db_in_project",
members: []ListMembersOpPermissionItem{
{
UserUid: "masking_approver_b",
UserName: "approver_b",
OpPermissions: []OpPermissionWithOpRange{
{
OpPermissionUID: pkgConst.UIdOfOpPermissionMaskingAudit,
OpRangeType: OpRangeType(dmsV1.OpRangeTypeProject),
RangeUIDs: []string{testProjectUID},
},
},
},
},
users: map[string]*User{
"masking_approver_b": {UID: "masking_approver_b", BusinessWritePermission: true},
},
isBusinessWrite: true,
needPermUID: pkgConst.UIdOfOpPermissionMaskingAudit,
wantUserUIDs: []string{"masking_approver_b"},
},
{
name: "project_range_masking_audit_not_selected_for_export_approval",
members: []ListMembersOpPermissionItem{
{
UserUid: "masking_only_user",
UserName: "masking_only",
OpPermissions: []OpPermissionWithOpRange{
{
OpPermissionUID: pkgConst.UIdOfOpPermissionMaskingAudit,
OpRangeType: OpRangeType(dmsV1.OpRangeTypeProject),
RangeUIDs: []string{testProjectUID},
},
},
},
{
UserUid: "export_approver",
UserName: "export_approver",
OpPermissions: []OpPermissionWithOpRange{
{
OpPermissionUID: opPermExportApproval,
OpRangeType: OpRangeType(dmsV1.OpRangeTypeDBService),
RangeUIDs: []string{testDBServiceUID},
},
},
},
},
users: map[string]*User{
"masking_only_user": {UID: "masking_only_user", BusinessWritePermission: true},
"export_approver": {UID: "export_approver", BusinessWritePermission: true},
},
isBusinessWrite: true,
needPermUID: opPermExportApproval,
// need=ExportApproval:仅有 project·700038 者不得入选
wantUserUIDs: []string{"export_approver"},
},
{
name: "admin_bwp_off_with_project_range_masking_audit",
members: []ListMembersOpPermissionItem{
{
UserUid: pkgConst.UIDOfUserAdmin,
UserName: "admin",
OpPermissions: []OpPermissionWithOpRange{
{OpPermissionUID: pkgConst.UIDOfOpPermissionProjectAdmin},
{
OpPermissionUID: pkgConst.UIdOfOpPermissionMaskingAudit,
OpRangeType: OpRangeType(dmsV1.OpRangeTypeProject),
RangeUIDs: []string{testProjectUID},
},
},
},
},
users: map[string]*User{
pkgConst.UIDOfUserAdmin: {UID: pkgConst.UIDOfUserAdmin, BusinessWritePermission: false},
},
isBusinessWrite: true,
needPermUID: pkgConst.UIdOfOpPermissionMaskingAudit,
wantUserUIDs: []string{pkgConst.UIDOfUserAdmin},
},
}

for _, tc := range cases {
Expand All @@ -485,11 +573,47 @@ func TestGetCanOpDBUsers(t *testing.T) {
context.Background(),
testProjectUID,
testDBServiceUID,
[]string{opPermExportApproval},
[]string{tc.needPermUID},
tc.isBusinessWrite,
)
assert.NoError(t, err)
assert.ElementsMatch(t, tc.wantUserUIDs, got, "case: %s", tc.name)
})
}
}

// TestUserCanOpDB_ProjectRange covers S2: project-scoped permission hits all DB services in project.
func TestUserCanOpDB_ProjectRange(t *testing.T) {
uc := newTestOpPermissionVerifyUsecase(&mockUserRepo{users: map[string]*User{}}, &mockOpPermissionVerifyRepo{})
maskingAudit := pkgConst.UIdOfOpPermissionMaskingAudit
exportApproval := pkgConst.UIDOfOpPermissionExportApprovalReject

t.Run("project_range_matching_uid_hits", func(t *testing.T) {
perms := []OpPermissionWithOpRange{{
OpPermissionUID: maskingAudit,
OpRangeType: OpRangeType(dmsV1.OpRangeTypeProject),
RangeUIDs: []string{"project_1"},
}}
assert.True(t, uc.UserCanOpDB(perms, []string{maskingAudit}, "db_any"))
assert.True(t, uc.userCanOpDBWithoutAdminPrivilege(perms, []string{maskingAudit}, "db_any"))
})

t.Run("project_range_wrong_uid_misses", func(t *testing.T) {
perms := []OpPermissionWithOpRange{{
OpPermissionUID: maskingAudit,
OpRangeType: OpRangeType(dmsV1.OpRangeTypeProject),
RangeUIDs: []string{"project_1"},
}}
assert.False(t, uc.UserCanOpDB(perms, []string{exportApproval}, "db_1"))
})

t.Run("db_service_range_still_requires_uid_match", func(t *testing.T) {
perms := []OpPermissionWithOpRange{{
OpPermissionUID: exportApproval,
OpRangeType: OpRangeType(dmsV1.OpRangeTypeDBService),
RangeUIDs: []string{"db_1"},
}}
assert.True(t, uc.UserCanOpDB(perms, []string{exportApproval}, "db_1"))
assert.False(t, uc.UserCanOpDB(perms, []string{exportApproval}, "db_2"))
})
}
16 changes: 15 additions & 1 deletion internal/dms/storage/op_permission_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ func (o *OpPermissionVerifyRepo) ListUsersOpPermissionInProject(ctx context.Cont
}

{
// 角色挂载权限 UNION 成员/成员组项目权限槽(与 GetUserOpPermissionInProject 合并语义一致)
if err = tx.WithContext(ctx).Raw(`
SELECT
m.user_uid, p.op_permission_uid, r.op_range_type, r.range_uids
Expand All @@ -455,7 +456,20 @@ func (o *OpPermissionVerifyRepo) ListUsersOpPermissionInProject(ctx context.Cont
JOIN member_group_users mgu ON mg.uid = mgu.member_group_uid
JOIN member_group_role_op_ranges mgror ON mgu.member_group_uid = mgror.member_group_uid
JOIN role_op_permissions rop ON mgror.role_uid = rop.role_uid
WHERE mg.project_uid = ? and mgu.user_uid in (?)`, userIds, projectUid, projectUid, userIds).Scan(&permissionResults).Error; err != nil {
WHERE mg.project_uid = ? and mgu.user_uid in (?)
UNION
SELECT
m.user_uid, mop.op_permission_uid, 'project' AS op_range_type, m.project_uid AS range_uids
FROM members AS m
JOIN member_op_permissions AS mop ON m.uid = mop.member_uid AND m.user_uid IN (?) AND m.project_uid = ?
UNION
SELECT
DISTINCT mgu.user_uid, mgop.op_permission_uid, 'project' AS op_range_type, mg.project_uid AS range_uids
FROM member_groups mg
JOIN member_group_users mgu ON mg.uid = mgu.member_group_uid
JOIN member_group_op_permissions AS mgop ON mg.uid = mgop.member_group_uid
WHERE mg.project_uid = ? AND mgu.user_uid IN (?)`,
userIds, projectUid, projectUid, userIds, userIds, projectUid, projectUid, userIds).Scan(&permissionResults).Error; err != nil {
return fmt.Errorf("failed to get user op permission in project: %v", err)
}
}
Expand Down