Skip to content
Open
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
10 changes: 7 additions & 3 deletions internal/dms/biz/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,13 @@ func (m *MemberUsecase) CheckRoleAndOpRanges(ctx context.Context, roleAndOpRange
} else if !exist {
return fmt.Errorf("db service not exist")
}
// 角色目前与成员绑定,只支持配置数据源范围的权限
case OpRangeTypeProject, OpRangeTypeGlobal:
return fmt.Errorf("role currently only support the db service op range type, but got type: %v", op.RangeType)
case OpRangeTypeProject:
// 纯项目范围角色(如单独「脱敏审核」)允许以 project 挂载;RangeUIDs 为项目 UID
if len(r.RangeUIDs) == 0 {
return fmt.Errorf("project op range requires project uid in range_uids")
}
case OpRangeTypeGlobal:
return fmt.Errorf("role currently only support the db service or project op range type, but got type: %v", op.RangeType)
default:
return fmt.Errorf("unsupported range type: %v", op.RangeType)
}
Expand Down
17 changes: 15 additions & 2 deletions internal/dms/biz/op_permission_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,20 @@ 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):
// 项目范围权限视为本项目全部数据源命中(RangeUIDs 为项目 UID,已由授权写入保证同项目)
return true
}
}
}
Expand Down Expand Up @@ -489,12 +496,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
13 changes: 12 additions & 1 deletion internal/dms/storage/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,18 @@ AND wr.status IN (:filter_status)
{{- end }}

{{- if .filter_current_step_assignee_user_id }}
AND ((wr.status != 'rejected' AND curr_ws.assignees REGEXP :filter_current_step_assignee_user_id) OR (wr.status = 'rejected' AND w.create_user_uid = :filter_current_step_assignee_user_id))
AND (
((wr.status != 'rejected' AND curr_ws.assignees REGEXP :filter_current_step_assignee_user_id) OR (wr.status = 'rejected' AND w.create_user_uid = :filter_current_step_assignee_user_id))
OR EXISTS (
SELECT 1 FROM unmasking_workflows uw
WHERE uw.deleted_at IS NULL
AND uw.source_type = 'data_export'
AND uw.source_uid = w.uid
AND uw.approval_status = 'pending'
AND JSON_VALID(uw.current_assignee_uids)
AND JSON_CONTAINS(uw.current_assignee_uids, JSON_QUOTE(:filter_current_step_assignee_user_id), '$')
)
)
{{- end }}

{{- if .filter_db_service_uid }}
Expand Down