Skip to content

Commit 285c5e3

Browse files
committed
feat: Folder authorization can optionally include sub-resources
1 parent 674cd61 commit 285c5e3

File tree

7 files changed

+182
-19
lines changed

7 files changed

+182
-19
lines changed

apps/system_manage/serializers/user_resource_permission.py

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
from django.core.cache import cache
1313
from django.db import models
14-
from django.db.models import QuerySet, Q
14+
from django.db.models import QuerySet, Q, TextField
15+
from django.db.models.functions import Cast
1516
from django.utils.translation import gettext_lazy as _
1617
from rest_framework import serializers
1718

@@ -208,7 +209,7 @@ def auth_resource(self, resource_id: str, is_folder=False):
208209
auth_target_type=auth_target_type,
209210
permission_list=[ResourcePermission.VIEW,
210211
ResourcePermission.MANAGE] if (
211-
auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP or is_folder) else [
212+
auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP or is_folder) else [
212213
ResourcePermissionRole.ROLE],
213214
workspace_id=workspace_id,
214215
user_id=user_id,
@@ -326,6 +327,12 @@ class ResourceUserPermissionSerializer(serializers.Serializer):
326327
auth_target_type = serializers.CharField(required=True, label=_('resource'))
327328
users_permission = ResourceUserPermissionEditRequest(required=False, many=True, label=_('users_permission'))
328329

330+
RESOURCE_MODEL_MAP = {
331+
'APPLICATION': Application,
332+
'KNOWLEDGE': Knowledge,
333+
'TOOL': Tool
334+
}
335+
329336
def get_queryset(self, instance):
330337

331338
user_query_set = QuerySet(model=get_dynamics_model({
@@ -392,7 +399,28 @@ def page(self, instance, current_page: int, page_size: int, with_valid=True):
392399
))
393400
return resource_user_permission_page_list
394401

395-
def edit(self, instance, with_valid=True):
402+
def get_has_manage_permission_resource_under_folders(self, current_user_id, folder_ids):
403+
404+
workspace_id = self.data.get("workspace_id")
405+
auth_target_type = self.data.get("auth_target_type")
406+
workspace_manage = is_workspace_manage(current_user_id, workspace_id)
407+
resource_model = self.RESOURCE_MODEL_MAP[auth_target_type]
408+
409+
if workspace_manage:
410+
current_user_managed_resources_ids = QuerySet(resource_model).filter(workspace_id=workspace_id, folder__in=folder_ids).annotate(
411+
id_str=Cast('id', TextField())
412+
).values_list("id", flat=True)
413+
else:
414+
current_user_managed_resources_ids = QuerySet(WorkspaceUserResourcePermission).filter(
415+
workspace_id=workspace_id, user_id=current_user_id, auth_target_type=auth_target_type,
416+
target__in=QuerySet(resource_model).filter(workspace_id=workspace_id, folder__in=folder_ids).annotate(
417+
id_str=Cast('id', TextField())
418+
).values_list("id", flat=True),
419+
permission_list__contains=['MANAGE']).values_list('target', flat=True)
420+
421+
return current_user_managed_resources_ids
422+
423+
def edit(self, instance, with_valid=True, current_user_id=None):
396424
if with_valid:
397425
self.is_valid(raise_exception=True)
398426
ResourceUserPermissionEditRequest(data=instance, many=True).is_valid(
@@ -404,27 +432,36 @@ def edit(self, instance, with_valid=True):
404432
users_permission = instance
405433

406434
users_id = [item["user_id"] for item in users_permission]
435+
include_children = users_permission[0].get('include_children')
436+
folder_ids = users_permission[0].get('folder_ids')
407437
# 删除已存在的对应的用户在该资源下的权限
438+
439+
if include_children:
440+
managed_resource_ids = list(
441+
self.get_has_manage_permission_resource_under_folders(current_user_id, folder_ids)) + folder_ids
442+
443+
else:
444+
managed_resource_ids = [target]
408445
QuerySet(WorkspaceUserResourcePermission).filter(
409446
workspace_id=workspace_id,
410-
target=target,
447+
target__in=managed_resource_ids,
411448
auth_target_type=auth_target_type,
412449
user_id__in=users_id
413450
).delete()
414451

415-
save_list = []
416-
for item in users_permission:
417-
permission = item['permission']
418-
auth_type, permission_list = permission_map[permission]
419-
420-
save_list.append(WorkspaceUserResourcePermission(
421-
target=target,
452+
save_list = [
453+
WorkspaceUserResourcePermission(
454+
target=resource_id,
422455
auth_target_type=auth_target_type,
423456
workspace_id=workspace_id,
424-
auth_type=auth_type,
457+
auth_type=permission_map[item['permission']][0],
425458
user_id=item["user_id"],
426-
permission_list=permission_list
427-
))
459+
permission_list=permission_map[item['permission']][1]
460+
)
461+
for resource_id in managed_resource_ids
462+
for item in users_permission
463+
]
464+
428465
if save_list:
429466
QuerySet(WorkspaceUserResourcePermission).bulk_create(save_list)
430467

apps/system_manage/views/user_resource_permission.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def get(self, request: Request, workspace_id: str, target: str, resource: str):
164164
def put(self, request: Request, workspace_id: str, target: str, resource: str):
165165
return result.success(ResourceUserPermissionSerializer(
166166
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource, })
167-
.edit(instance=request.data))
167+
.edit(instance=request.data, current_user_id=request.user.id))
168168

169169
class Page(APIView):
170170
authentication_classes = [TokenAuth]

ui/src/components/folder-tree/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ const currentNode = ref<Tree | null>(null)
273273
const ResourceAuthorizationDrawerRef = ref()
274274
function openAuthorization(data: any) {
275275
currentNode.value = data
276-
ResourceAuthorizationDrawerRef.value.open(data.id)
276+
ResourceAuthorizationDrawerRef.value.open(data.id, data)
277277
}
278278
279279
function refreshFolder() {

ui/src/components/resource-authorization-drawer/index.vue

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,28 @@
119119
</template>
120120
</el-table-column>
121121
</app-table>
122+
<!-- 单个资源授权提示框 -->
123+
<el-dialog
124+
v-model="singleSelectDialogVisible"
125+
:title="$t('views.system.resourceAuthorization.setting.configure')"
126+
destroy-on-close
127+
@close="closeSingleSelectDialog"
128+
>
129+
<el-radio-group v-model="authAllChildren" class="radio-block">
130+
<el-radio :value="false">
131+
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.currentOnly') }}</p>
132+
</el-radio>
133+
<el-radio :value="true">
134+
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.includeAll') }}</p>
135+
</el-radio>
136+
</el-radio-group>
137+
<template #footer>
138+
<div class="dialog-footer mt-24">
139+
<el-button @click="closeSingleSelectDialog">{{ $t('common.cancel') }}</el-button>
140+
<el-button type="primary" @click="confirmSinglePermission">{{ $t('common.confirm') }}</el-button>
141+
</div>
142+
</template>
143+
</el-dialog>
122144

123145
<!-- 批量配置 弹出层 -->
124146
<el-dialog
@@ -128,13 +150,26 @@
128150
@close="closeDialog"
129151
>
130152
<el-radio-group v-model="radioPermission" class="radio-block">
131-
<template v-for="(item, index) in permissionOptions" :key="index">
153+
<template v-for="(item, index) in getFolderPermissionOptions()" :key="index">
132154
<el-radio :value="item.value" class="mr-16">
133155
<p class="color-text-primary lighter">{{ item.label }}</p>
134156
<el-text class="color-secondary lighter">{{ item.desc }}</el-text>
135157
</el-radio>
136158
</template>
137159
</el-radio-group>
160+
<!-- 如果是文件夹,显示子资源选项 -->
161+
<div v-if="isFolder" class="mt-16">
162+
<el-divider />
163+
<div class="color-text-primary mb-8">{{ $t('views.system.resourceAuthorization.setting.effectiveResource') }}</div>
164+
<el-radio-group v-model="batchAuthAllChildren" class="radio-block">
165+
<el-radio :value="false">
166+
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.currentOnly') }}</p>
167+
</el-radio>
168+
<el-radio :value="true">
169+
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.includeAll') }}</p>
170+
</el-radio>
171+
</el-radio-group>
172+
</div>
138173
<template #footer>
139174
<div class="dialog-footer mt-24">
140175
<el-button @click="closeDialog"> {{ $t('common.cancel') }}</el-button>
@@ -151,9 +186,11 @@ import { getPermissionOptions } from '@/views/system/resource-authorization/cons
151186
import AuthorizationApi from '@/api/system/resource-authorization'
152187
import { MsgSuccess, MsgConfirm } from '@/utils/message'
153188
import { t } from '@/locales'
189+
import permissionMap from '@/permission'
154190
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
155191
const route = useRoute()
156192
import useStore from '@/stores'
193+
157194
const { user } = useStore()
158195
const props = defineProps<{
159196
type: string
@@ -169,6 +206,57 @@ const apiType = computed(() => {
169206
}
170207
})
171208
209+
const folderType = computed(() => {
210+
if (route.path.includes('application')) {
211+
return 'application'
212+
}
213+
else if (route.path.includes('knowledge')) {
214+
return 'knowledge'
215+
}
216+
else if (route.path.includes('tool')) {
217+
return 'tool'
218+
}
219+
else {return 'application'}
220+
})
221+
222+
const permissionPrecise = computed(() => {
223+
return permissionMap[folderType.value!]['workspace']
224+
})
225+
226+
// 取出文件夹id
227+
function getAllFolderIds(data: any) {
228+
if (!data) return []
229+
return [data.id,...(data.children?.flatMap((child: any) => getAllFolderIds(child)) || [])]
230+
}
231+
232+
// 过滤没有Manage权限的文件夹ID
233+
function filterHasPermissionFolderIds(folderIds: string[]) {
234+
return folderIds.filter(id => permissionPrecise.value.folderManage(id))
235+
}
236+
237+
function confirmSinglePermission() {
238+
if (!pendingPermissionChange.value) return
239+
const { val, row } = pendingPermissionChange.value
240+
let folderIds: string[] = []
241+
if (authAllChildren.value && folderData.value) {
242+
const allFolderIds = getAllFolderIds(folderData.value)
243+
folderIds = filterHasPermissionFolderIds(allFolderIds)
244+
}
245+
const obj = [
246+
{
247+
user_id: row.id,
248+
permission: val,
249+
include_children: authAllChildren.value,
250+
...(folderIds.length > 0 && {folder_ids: folderIds})
251+
},
252+
]
253+
submitPermissions(obj)
254+
singleSelectDialogVisible.value = false
255+
authAllChildren.value = false
256+
pendingPermissionChange.value = null
257+
getPermissionList()
258+
}
259+
172260
const permissionOptionMap = computed(() => {
173261
return {
174262
rootFolder: getPermissionOptions(true, true),
@@ -207,6 +295,7 @@ watch(drawerVisible, (bool) => {
207295
208296
const loading = ref(false)
209297
const targetId = ref('')
298+
const folderData = ref<any>(null)
210299
const permissionData = ref<any[]>([])
211300
const searchType = ref('nick_name')
212301
const searchForm = ref<any>({
@@ -241,32 +330,59 @@ const handleSelectionChange = (val: any[]) => {
241330
}
242331
243332
const dialogVisible = ref(false)
333+
const singleSelectDialogVisible = ref(false)
334+
const pendingPermissionChange = ref<{ val: any; row: any; } | null>(null)
244335
const radioPermission = ref('')
336+
const authAllChildren = ref(false)
245337
function openMulConfigureDialog() {
246338
if (multipleSelection.value.length === 0) {
247339
return
248340
}
249341
dialogVisible.value = true
250342
}
343+
344+
const batchAuthAllChildren = ref(false)
251345
function submitDialog() {
252346
if (multipleSelection.value.length === 0 || !radioPermission.value) {
253347
return
254348
}
349+
let folderIds: string[] = []
350+
if (props.isFolder && batchAuthAllChildren.value && folderData.value) {
351+
const allFolderIds = getAllFolderIds(folderData.value)
352+
folderIds = filterHasPermissionFolderIds(allFolderIds)
353+
}
354+
255355
const obj = multipleSelection.value.map((item) => ({
256356
user_id: item.id,
257357
permission: radioPermission.value,
358+
include_children: batchAuthAllChildren.value,
359+
...(folderIds.length > 0 && { folder_ids: folderIds })
258360
}))
259361
submitPermissions(obj)
260362
closeDialog()
261363
}
364+
365+
function closeSingleSelectDialog() {
366+
singleSelectDialogVisible.value = false
367+
authAllChildren.value = false
368+
pendingPermissionChange.value = null
369+
getPermissionList()
370+
}
371+
262372
function closeDialog() {
263373
dialogVisible.value = false
264374
radioPermission.value = ''
375+
batchAuthAllChildren.value = false
265376
multipleSelection.value = []
266377
multipleTableRef.value?.clearSelection()
267378
}
268379
269380
function permissionsHandle(val: any, row: any) {
381+
if (props.isFolder) {
382+
singleSelectDialogVisible.value = true
383+
pendingPermissionChange.value = {val, row}
384+
return
385+
}
270386
const obj = [
271387
{
272388
user_id: row.id,
@@ -276,7 +392,7 @@ function permissionsHandle(val: any, row: any) {
276392
submitPermissions(obj)
277393
}
278394
279-
function submitPermissions(obj: any) {
395+
function submitPermissions( obj: any) {
280396
const workspaceId = user.getWorkspaceId() || 'default'
281397
loadSharedApi({ type: 'resourceAuthorization', systemType: apiType.value })
282398
.putResourceAuthorization(workspaceId, targetId.value, props.type, obj, loading)
@@ -311,8 +427,9 @@ const getPermissionList = () => {
311427
})
312428
}
313429
314-
const open = (id: string) => {
430+
const open = (id: string, folder_data?: any) => {
315431
targetId.value = id
432+
folderData.value = folder_data
316433
drawerVisible.value = true
317434
getPermissionList()
318435
}

ui/src/locales/lang/en-US/views/system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ export default {
123123
roleDesc: 'Authorize users based on their roles to access this resource',
124124
notAuthorized: 'Not Authorized',
125125
configure: 'Configure Permission',
126+
currentOnly: 'Current resource only',
127+
includeAll: 'Include all sub-resources',
128+
effectiveResource: 'Effective Resource',
126129
},
127130
},
128131
resource_management: {

ui/src/locales/lang/zh-CN/views/system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ export default {
124124
roleDesc: '根据用户角色中的权限授权用户对该资源的操作权限',
125125
notAuthorized: '不授权',
126126
configure: '配置权限',
127+
currentOnly: '仅当前资源',
128+
includeAll: '包含所有子资源',
129+
effectiveResource: '生效资源',
127130
},
128131
},
129132
resource_management: {

ui/src/locales/lang/zh-Hant/views/system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ export default {
123123
roleDesc: '根據用戶角色中的權限授權用戶對該資源的操作權限',
124124
notAuthorized: '不授權',
125125
configure: '配置權限',
126+
currentOnly: '僅當前資源',
127+
includeAll: '包含所有子資源',
128+
effectiveResource: '生效資源',
126129
},
127130
},
128131
resource_management: {

0 commit comments

Comments
 (0)