mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: Folder authorization can optionally include sub-resources
This commit is contained in:
parent
cbac86a15d
commit
349402b2d8
|
|
@ -11,7 +11,8 @@ import os
|
|||
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.db.models import QuerySet, Q
|
||||
from django.db.models import QuerySet, Q, TextField
|
||||
from django.db.models.functions import Cast
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
|
|
@ -208,7 +209,7 @@ class UserResourcePermissionSerializer(serializers.Serializer):
|
|||
auth_target_type=auth_target_type,
|
||||
permission_list=[ResourcePermission.VIEW,
|
||||
ResourcePermission.MANAGE] if (
|
||||
auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP or is_folder) else [
|
||||
auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP or is_folder) else [
|
||||
ResourcePermissionRole.ROLE],
|
||||
workspace_id=workspace_id,
|
||||
user_id=user_id,
|
||||
|
|
@ -326,6 +327,12 @@ class ResourceUserPermissionSerializer(serializers.Serializer):
|
|||
auth_target_type = serializers.CharField(required=True, label=_('resource'))
|
||||
users_permission = ResourceUserPermissionEditRequest(required=False, many=True, label=_('users_permission'))
|
||||
|
||||
RESOURCE_MODEL_MAP = {
|
||||
'APPLICATION': Application,
|
||||
'KNOWLEDGE': Knowledge,
|
||||
'TOOL': Tool
|
||||
}
|
||||
|
||||
def get_queryset(self, instance):
|
||||
|
||||
user_query_set = QuerySet(model=get_dynamics_model({
|
||||
|
|
@ -392,7 +399,28 @@ class ResourceUserPermissionSerializer(serializers.Serializer):
|
|||
))
|
||||
return resource_user_permission_page_list
|
||||
|
||||
def edit(self, instance, with_valid=True):
|
||||
def get_has_manage_permission_resource_under_folders(self, current_user_id, folder_ids):
|
||||
|
||||
workspace_id = self.data.get("workspace_id")
|
||||
auth_target_type = self.data.get("auth_target_type")
|
||||
workspace_manage = is_workspace_manage(current_user_id, workspace_id)
|
||||
resource_model = self.RESOURCE_MODEL_MAP[auth_target_type]
|
||||
|
||||
if workspace_manage:
|
||||
current_user_managed_resources_ids = QuerySet(resource_model).filter(workspace_id=workspace_id, folder__in=folder_ids).annotate(
|
||||
id_str=Cast('id', TextField())
|
||||
).values_list("id", flat=True)
|
||||
else:
|
||||
current_user_managed_resources_ids = QuerySet(WorkspaceUserResourcePermission).filter(
|
||||
workspace_id=workspace_id, user_id=current_user_id, auth_target_type=auth_target_type,
|
||||
target__in=QuerySet(resource_model).filter(workspace_id=workspace_id, folder__in=folder_ids).annotate(
|
||||
id_str=Cast('id', TextField())
|
||||
).values_list("id", flat=True),
|
||||
permission_list__contains=['MANAGE']).values_list('target', flat=True)
|
||||
|
||||
return current_user_managed_resources_ids
|
||||
|
||||
def edit(self, instance, with_valid=True, current_user_id=None):
|
||||
if with_valid:
|
||||
self.is_valid(raise_exception=True)
|
||||
ResourceUserPermissionEditRequest(data=instance, many=True).is_valid(
|
||||
|
|
@ -404,27 +432,36 @@ class ResourceUserPermissionSerializer(serializers.Serializer):
|
|||
users_permission = instance
|
||||
|
||||
users_id = [item["user_id"] for item in users_permission]
|
||||
include_children = users_permission[0].get('include_children')
|
||||
folder_ids = users_permission[0].get('folder_ids')
|
||||
# 删除已存在的对应的用户在该资源下的权限
|
||||
|
||||
if include_children:
|
||||
managed_resource_ids = list(
|
||||
self.get_has_manage_permission_resource_under_folders(current_user_id, folder_ids)) + folder_ids
|
||||
|
||||
else:
|
||||
managed_resource_ids = [target]
|
||||
QuerySet(WorkspaceUserResourcePermission).filter(
|
||||
workspace_id=workspace_id,
|
||||
target=target,
|
||||
target__in=managed_resource_ids,
|
||||
auth_target_type=auth_target_type,
|
||||
user_id__in=users_id
|
||||
).delete()
|
||||
|
||||
save_list = []
|
||||
for item in users_permission:
|
||||
permission = item['permission']
|
||||
auth_type, permission_list = permission_map[permission]
|
||||
|
||||
save_list.append(WorkspaceUserResourcePermission(
|
||||
target=target,
|
||||
save_list = [
|
||||
WorkspaceUserResourcePermission(
|
||||
target=resource_id,
|
||||
auth_target_type=auth_target_type,
|
||||
workspace_id=workspace_id,
|
||||
auth_type=auth_type,
|
||||
auth_type=permission_map[item['permission']][0],
|
||||
user_id=item["user_id"],
|
||||
permission_list=permission_list
|
||||
))
|
||||
permission_list=permission_map[item['permission']][1]
|
||||
)
|
||||
for resource_id in managed_resource_ids
|
||||
for item in users_permission
|
||||
]
|
||||
|
||||
if save_list:
|
||||
QuerySet(WorkspaceUserResourcePermission).bulk_create(save_list)
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ class WorkspaceResourceUserPermissionView(APIView):
|
|||
def put(self, request: Request, workspace_id: str, target: str, resource: str):
|
||||
return result.success(ResourceUserPermissionSerializer(
|
||||
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource, })
|
||||
.edit(instance=request.data))
|
||||
.edit(instance=request.data, current_user_id=request.user.id))
|
||||
|
||||
class Page(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ const currentNode = ref<Tree | null>(null)
|
|||
const ResourceAuthorizationDrawerRef = ref()
|
||||
function openAuthorization(data: any) {
|
||||
currentNode.value = data
|
||||
ResourceAuthorizationDrawerRef.value.open(data.id)
|
||||
ResourceAuthorizationDrawerRef.value.open(data.id, data)
|
||||
}
|
||||
|
||||
function refreshFolder() {
|
||||
|
|
|
|||
|
|
@ -119,6 +119,28 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</app-table>
|
||||
<!-- 单个资源授权提示框 -->
|
||||
<el-dialog
|
||||
v-model="singleSelectDialogVisible"
|
||||
:title="$t('views.system.resourceAuthorization.setting.configure')"
|
||||
destroy-on-close
|
||||
@close="closeSingleSelectDialog"
|
||||
>
|
||||
<el-radio-group v-model="authAllChildren" class="radio-block">
|
||||
<el-radio :value="false">
|
||||
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.currentOnly') }}</p>
|
||||
</el-radio>
|
||||
<el-radio :value="true">
|
||||
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.includeAll') }}</p>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<template #footer>
|
||||
<div class="dialog-footer mt-24">
|
||||
<el-button @click="closeSingleSelectDialog">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="confirmSinglePermission">{{ $t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量配置 弹出层 -->
|
||||
<el-dialog
|
||||
|
|
@ -128,13 +150,26 @@
|
|||
@close="closeDialog"
|
||||
>
|
||||
<el-radio-group v-model="radioPermission" class="radio-block">
|
||||
<template v-for="(item, index) in permissionOptions" :key="index">
|
||||
<template v-for="(item, index) in getFolderPermissionOptions()" :key="index">
|
||||
<el-radio :value="item.value" class="mr-16">
|
||||
<p class="color-text-primary lighter">{{ item.label }}</p>
|
||||
<el-text class="color-secondary lighter">{{ item.desc }}</el-text>
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
<!-- 如果是文件夹,显示子资源选项 -->
|
||||
<div v-if="isFolder" class="mt-16">
|
||||
<el-divider />
|
||||
<div class="color-text-primary mb-8">{{ $t('views.system.resourceAuthorization.setting.effectiveResource') }}</div>
|
||||
<el-radio-group v-model="batchAuthAllChildren" class="radio-block">
|
||||
<el-radio :value="false">
|
||||
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.currentOnly') }}</p>
|
||||
</el-radio>
|
||||
<el-radio :value="true">
|
||||
<p class="color-text-primary lighter">{{ $t('views.system.resourceAuthorization.setting.includeAll') }}</p>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer mt-24">
|
||||
<el-button @click="closeDialog"> {{ $t('common.cancel') }}</el-button>
|
||||
|
|
@ -151,9 +186,11 @@ import { getPermissionOptions } from '@/views/system/resource-authorization/cons
|
|||
import AuthorizationApi from '@/api/system/resource-authorization'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
import permissionMap from '@/permission'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
const route = useRoute()
|
||||
import useStore from '@/stores'
|
||||
|
||||
const { user } = useStore()
|
||||
const props = defineProps<{
|
||||
type: string
|
||||
|
|
@ -169,6 +206,57 @@ const apiType = computed(() => {
|
|||
}
|
||||
})
|
||||
|
||||
const folderType = computed(() => {
|
||||
if (route.path.includes('application')) {
|
||||
return 'application'
|
||||
}
|
||||
else if (route.path.includes('knowledge')) {
|
||||
return 'knowledge'
|
||||
}
|
||||
else if (route.path.includes('tool')) {
|
||||
return 'tool'
|
||||
}
|
||||
else {return 'application'}
|
||||
})
|
||||
|
||||
const permissionPrecise = computed(() => {
|
||||
return permissionMap[folderType.value!]['workspace']
|
||||
})
|
||||
|
||||
// 取出文件夹id
|
||||
function getAllFolderIds(data: any) {
|
||||
if (!data) return []
|
||||
return [data.id,...(data.children?.flatMap((child: any) => getAllFolderIds(child)) || [])]
|
||||
}
|
||||
|
||||
// 过滤没有Manage权限的文件夹ID
|
||||
function filterHasPermissionFolderIds(folderIds: string[]) {
|
||||
return folderIds.filter(id => permissionPrecise.value.folderManage(id))
|
||||
}
|
||||
|
||||
function confirmSinglePermission() {
|
||||
if (!pendingPermissionChange.value) return
|
||||
const { val, row } = pendingPermissionChange.value
|
||||
let folderIds: string[] = []
|
||||
if (authAllChildren.value && folderData.value) {
|
||||
const allFolderIds = getAllFolderIds(folderData.value)
|
||||
folderIds = filterHasPermissionFolderIds(allFolderIds)
|
||||
}
|
||||
const obj = [
|
||||
{
|
||||
user_id: row.id,
|
||||
permission: val,
|
||||
include_children: authAllChildren.value,
|
||||
...(folderIds.length > 0 && {folder_ids: folderIds})
|
||||
},
|
||||
]
|
||||
submitPermissions(obj)
|
||||
singleSelectDialogVisible.value = false
|
||||
authAllChildren.value = false
|
||||
pendingPermissionChange.value = null
|
||||
getPermissionList()
|
||||
}
|
||||
|
||||
const permissionOptionMap = computed(() => {
|
||||
return {
|
||||
rootFolder: getPermissionOptions(true, true),
|
||||
|
|
@ -207,6 +295,7 @@ watch(drawerVisible, (bool) => {
|
|||
|
||||
const loading = ref(false)
|
||||
const targetId = ref('')
|
||||
const folderData = ref<any>(null)
|
||||
const permissionData = ref<any[]>([])
|
||||
const searchType = ref('nick_name')
|
||||
const searchForm = ref<any>({
|
||||
|
|
@ -241,32 +330,59 @@ const handleSelectionChange = (val: any[]) => {
|
|||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const singleSelectDialogVisible = ref(false)
|
||||
const pendingPermissionChange = ref<{ val: any; row: any; } | null>(null)
|
||||
const radioPermission = ref('')
|
||||
const authAllChildren = ref(false)
|
||||
function openMulConfigureDialog() {
|
||||
if (multipleSelection.value.length === 0) {
|
||||
return
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const batchAuthAllChildren = ref(false)
|
||||
function submitDialog() {
|
||||
if (multipleSelection.value.length === 0 || !radioPermission.value) {
|
||||
return
|
||||
}
|
||||
let folderIds: string[] = []
|
||||
if (props.isFolder && batchAuthAllChildren.value && folderData.value) {
|
||||
const allFolderIds = getAllFolderIds(folderData.value)
|
||||
folderIds = filterHasPermissionFolderIds(allFolderIds)
|
||||
}
|
||||
|
||||
const obj = multipleSelection.value.map((item) => ({
|
||||
user_id: item.id,
|
||||
permission: radioPermission.value,
|
||||
include_children: batchAuthAllChildren.value,
|
||||
...(folderIds.length > 0 && { folder_ids: folderIds })
|
||||
}))
|
||||
submitPermissions(obj)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
function closeSingleSelectDialog() {
|
||||
singleSelectDialogVisible.value = false
|
||||
authAllChildren.value = false
|
||||
pendingPermissionChange.value = null
|
||||
getPermissionList()
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
dialogVisible.value = false
|
||||
radioPermission.value = ''
|
||||
batchAuthAllChildren.value = false
|
||||
multipleSelection.value = []
|
||||
multipleTableRef.value?.clearSelection()
|
||||
}
|
||||
|
||||
function permissionsHandle(val: any, row: any) {
|
||||
if (props.isFolder) {
|
||||
singleSelectDialogVisible.value = true
|
||||
pendingPermissionChange.value = {val, row}
|
||||
return
|
||||
}
|
||||
const obj = [
|
||||
{
|
||||
user_id: row.id,
|
||||
|
|
@ -276,7 +392,7 @@ function permissionsHandle(val: any, row: any) {
|
|||
submitPermissions(obj)
|
||||
}
|
||||
|
||||
function submitPermissions(obj: any) {
|
||||
function submitPermissions( obj: any) {
|
||||
const workspaceId = user.getWorkspaceId() || 'default'
|
||||
loadSharedApi({ type: 'resourceAuthorization', systemType: apiType.value })
|
||||
.putResourceAuthorization(workspaceId, targetId.value, props.type, obj, loading)
|
||||
|
|
@ -311,8 +427,9 @@ const getPermissionList = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const open = (id: string) => {
|
||||
const open = (id: string, folder_data?: any) => {
|
||||
targetId.value = id
|
||||
folderData.value = folder_data
|
||||
drawerVisible.value = true
|
||||
getPermissionList()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ export default {
|
|||
roleDesc: 'Authorize users based on their roles to access this resource',
|
||||
notAuthorized: 'Not Authorized',
|
||||
configure: 'Configure Permission',
|
||||
currentOnly: 'Current resource only',
|
||||
includeAll: 'Include all sub-resources',
|
||||
effectiveResource: 'Effective Resource',
|
||||
},
|
||||
},
|
||||
resource_management: {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ export default {
|
|||
roleDesc: '根据用户角色中的权限授权用户对该资源的操作权限',
|
||||
notAuthorized: '不授权',
|
||||
configure: '配置权限',
|
||||
currentOnly: '仅当前资源',
|
||||
includeAll: '包含所有子资源',
|
||||
effectiveResource: '生效资源',
|
||||
},
|
||||
},
|
||||
resource_management: {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ export default {
|
|||
roleDesc: '根據用戶角色中的權限授權用戶對該資源的操作權限',
|
||||
notAuthorized: '不授權',
|
||||
configure: '配置權限',
|
||||
currentOnly: '僅當前資源',
|
||||
includeAll: '包含所有子資源',
|
||||
effectiveResource: '生效資源',
|
||||
},
|
||||
},
|
||||
resource_management: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue