feat: Folder authorization can optionally include sub-resources
parent
cbac86a15d
commit
349402b2d8
|
|
@ -11,7 +11,8 @@ import os
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db import models
|
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 django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
@ -208,7 +209,7 @@ class UserResourcePermissionSerializer(serializers.Serializer):
|
||||||
auth_target_type=auth_target_type,
|
auth_target_type=auth_target_type,
|
||||||
permission_list=[ResourcePermission.VIEW,
|
permission_list=[ResourcePermission.VIEW,
|
||||||
ResourcePermission.MANAGE] if (
|
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],
|
ResourcePermissionRole.ROLE],
|
||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
|
@ -326,6 +327,12 @@ class ResourceUserPermissionSerializer(serializers.Serializer):
|
||||||
auth_target_type = serializers.CharField(required=True, label=_('resource'))
|
auth_target_type = serializers.CharField(required=True, label=_('resource'))
|
||||||
users_permission = ResourceUserPermissionEditRequest(required=False, many=True, label=_('users_permission'))
|
users_permission = ResourceUserPermissionEditRequest(required=False, many=True, label=_('users_permission'))
|
||||||
|
|
||||||
|
RESOURCE_MODEL_MAP = {
|
||||||
|
'APPLICATION': Application,
|
||||||
|
'KNOWLEDGE': Knowledge,
|
||||||
|
'TOOL': Tool
|
||||||
|
}
|
||||||
|
|
||||||
def get_queryset(self, instance):
|
def get_queryset(self, instance):
|
||||||
|
|
||||||
user_query_set = QuerySet(model=get_dynamics_model({
|
user_query_set = QuerySet(model=get_dynamics_model({
|
||||||
|
|
@ -392,7 +399,28 @@ class ResourceUserPermissionSerializer(serializers.Serializer):
|
||||||
))
|
))
|
||||||
return resource_user_permission_page_list
|
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:
|
if with_valid:
|
||||||
self.is_valid(raise_exception=True)
|
self.is_valid(raise_exception=True)
|
||||||
ResourceUserPermissionEditRequest(data=instance, many=True).is_valid(
|
ResourceUserPermissionEditRequest(data=instance, many=True).is_valid(
|
||||||
|
|
@ -404,27 +432,36 @@ class ResourceUserPermissionSerializer(serializers.Serializer):
|
||||||
users_permission = instance
|
users_permission = instance
|
||||||
|
|
||||||
users_id = [item["user_id"] for item in users_permission]
|
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(
|
QuerySet(WorkspaceUserResourcePermission).filter(
|
||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
target=target,
|
target__in=managed_resource_ids,
|
||||||
auth_target_type=auth_target_type,
|
auth_target_type=auth_target_type,
|
||||||
user_id__in=users_id
|
user_id__in=users_id
|
||||||
).delete()
|
).delete()
|
||||||
|
|
||||||
save_list = []
|
save_list = [
|
||||||
for item in users_permission:
|
WorkspaceUserResourcePermission(
|
||||||
permission = item['permission']
|
target=resource_id,
|
||||||
auth_type, permission_list = permission_map[permission]
|
|
||||||
|
|
||||||
save_list.append(WorkspaceUserResourcePermission(
|
|
||||||
target=target,
|
|
||||||
auth_target_type=auth_target_type,
|
auth_target_type=auth_target_type,
|
||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
auth_type=auth_type,
|
auth_type=permission_map[item['permission']][0],
|
||||||
user_id=item["user_id"],
|
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:
|
if save_list:
|
||||||
QuerySet(WorkspaceUserResourcePermission).bulk_create(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):
|
def put(self, request: Request, workspace_id: str, target: str, resource: str):
|
||||||
return result.success(ResourceUserPermissionSerializer(
|
return result.success(ResourceUserPermissionSerializer(
|
||||||
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource, })
|
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):
|
class Page(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ const currentNode = ref<Tree | null>(null)
|
||||||
const ResourceAuthorizationDrawerRef = ref()
|
const ResourceAuthorizationDrawerRef = ref()
|
||||||
function openAuthorization(data: any) {
|
function openAuthorization(data: any) {
|
||||||
currentNode.value = data
|
currentNode.value = data
|
||||||
ResourceAuthorizationDrawerRef.value.open(data.id)
|
ResourceAuthorizationDrawerRef.value.open(data.id, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshFolder() {
|
function refreshFolder() {
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,28 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</app-table>
|
</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
|
<el-dialog
|
||||||
|
|
@ -128,13 +150,26 @@
|
||||||
@close="closeDialog"
|
@close="closeDialog"
|
||||||
>
|
>
|
||||||
<el-radio-group v-model="radioPermission" class="radio-block">
|
<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">
|
<el-radio :value="item.value" class="mr-16">
|
||||||
<p class="color-text-primary lighter">{{ item.label }}</p>
|
<p class="color-text-primary lighter">{{ item.label }}</p>
|
||||||
<el-text class="color-secondary lighter">{{ item.desc }}</el-text>
|
<el-text class="color-secondary lighter">{{ item.desc }}</el-text>
|
||||||
</el-radio>
|
</el-radio>
|
||||||
</template>
|
</template>
|
||||||
</el-radio-group>
|
</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>
|
<template #footer>
|
||||||
<div class="dialog-footer mt-24">
|
<div class="dialog-footer mt-24">
|
||||||
<el-button @click="closeDialog"> {{ $t('common.cancel') }}</el-button>
|
<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 AuthorizationApi from '@/api/system/resource-authorization'
|
||||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
|
import permissionMap from '@/permission'
|
||||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
|
|
||||||
const { user } = useStore()
|
const { user } = useStore()
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: string
|
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(() => {
|
const permissionOptionMap = computed(() => {
|
||||||
return {
|
return {
|
||||||
rootFolder: getPermissionOptions(true, true),
|
rootFolder: getPermissionOptions(true, true),
|
||||||
|
|
@ -207,6 +295,7 @@ watch(drawerVisible, (bool) => {
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const targetId = ref('')
|
const targetId = ref('')
|
||||||
|
const folderData = ref<any>(null)
|
||||||
const permissionData = ref<any[]>([])
|
const permissionData = ref<any[]>([])
|
||||||
const searchType = ref('nick_name')
|
const searchType = ref('nick_name')
|
||||||
const searchForm = ref<any>({
|
const searchForm = ref<any>({
|
||||||
|
|
@ -241,32 +330,59 @@ const handleSelectionChange = (val: any[]) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
const singleSelectDialogVisible = ref(false)
|
||||||
|
const pendingPermissionChange = ref<{ val: any; row: any; } | null>(null)
|
||||||
const radioPermission = ref('')
|
const radioPermission = ref('')
|
||||||
|
const authAllChildren = ref(false)
|
||||||
function openMulConfigureDialog() {
|
function openMulConfigureDialog() {
|
||||||
if (multipleSelection.value.length === 0) {
|
if (multipleSelection.value.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const batchAuthAllChildren = ref(false)
|
||||||
function submitDialog() {
|
function submitDialog() {
|
||||||
if (multipleSelection.value.length === 0 || !radioPermission.value) {
|
if (multipleSelection.value.length === 0 || !radioPermission.value) {
|
||||||
return
|
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) => ({
|
const obj = multipleSelection.value.map((item) => ({
|
||||||
user_id: item.id,
|
user_id: item.id,
|
||||||
permission: radioPermission.value,
|
permission: radioPermission.value,
|
||||||
|
include_children: batchAuthAllChildren.value,
|
||||||
|
...(folderIds.length > 0 && { folder_ids: folderIds })
|
||||||
}))
|
}))
|
||||||
submitPermissions(obj)
|
submitPermissions(obj)
|
||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeSingleSelectDialog() {
|
||||||
|
singleSelectDialogVisible.value = false
|
||||||
|
authAllChildren.value = false
|
||||||
|
pendingPermissionChange.value = null
|
||||||
|
getPermissionList()
|
||||||
|
}
|
||||||
|
|
||||||
function closeDialog() {
|
function closeDialog() {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
radioPermission.value = ''
|
radioPermission.value = ''
|
||||||
|
batchAuthAllChildren.value = false
|
||||||
multipleSelection.value = []
|
multipleSelection.value = []
|
||||||
multipleTableRef.value?.clearSelection()
|
multipleTableRef.value?.clearSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
function permissionsHandle(val: any, row: any) {
|
function permissionsHandle(val: any, row: any) {
|
||||||
|
if (props.isFolder) {
|
||||||
|
singleSelectDialogVisible.value = true
|
||||||
|
pendingPermissionChange.value = {val, row}
|
||||||
|
return
|
||||||
|
}
|
||||||
const obj = [
|
const obj = [
|
||||||
{
|
{
|
||||||
user_id: row.id,
|
user_id: row.id,
|
||||||
|
|
@ -276,7 +392,7 @@ function permissionsHandle(val: any, row: any) {
|
||||||
submitPermissions(obj)
|
submitPermissions(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitPermissions(obj: any) {
|
function submitPermissions( obj: any) {
|
||||||
const workspaceId = user.getWorkspaceId() || 'default'
|
const workspaceId = user.getWorkspaceId() || 'default'
|
||||||
loadSharedApi({ type: 'resourceAuthorization', systemType: apiType.value })
|
loadSharedApi({ type: 'resourceAuthorization', systemType: apiType.value })
|
||||||
.putResourceAuthorization(workspaceId, targetId.value, props.type, obj, loading)
|
.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
|
targetId.value = id
|
||||||
|
folderData.value = folder_data
|
||||||
drawerVisible.value = true
|
drawerVisible.value = true
|
||||||
getPermissionList()
|
getPermissionList()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ export default {
|
||||||
roleDesc: 'Authorize users based on their roles to access this resource',
|
roleDesc: 'Authorize users based on their roles to access this resource',
|
||||||
notAuthorized: 'Not Authorized',
|
notAuthorized: 'Not Authorized',
|
||||||
configure: 'Configure Permission',
|
configure: 'Configure Permission',
|
||||||
|
currentOnly: 'Current resource only',
|
||||||
|
includeAll: 'Include all sub-resources',
|
||||||
|
effectiveResource: 'Effective Resource',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resource_management: {
|
resource_management: {
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,9 @@ export default {
|
||||||
roleDesc: '根据用户角色中的权限授权用户对该资源的操作权限',
|
roleDesc: '根据用户角色中的权限授权用户对该资源的操作权限',
|
||||||
notAuthorized: '不授权',
|
notAuthorized: '不授权',
|
||||||
configure: '配置权限',
|
configure: '配置权限',
|
||||||
|
currentOnly: '仅当前资源',
|
||||||
|
includeAll: '包含所有子资源',
|
||||||
|
effectiveResource: '生效资源',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resource_management: {
|
resource_management: {
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ export default {
|
||||||
roleDesc: '根據用戶角色中的權限授權用戶對該資源的操作權限',
|
roleDesc: '根據用戶角色中的權限授權用戶對該資源的操作權限',
|
||||||
notAuthorized: '不授權',
|
notAuthorized: '不授權',
|
||||||
configure: '配置權限',
|
configure: '配置權限',
|
||||||
|
currentOnly: '僅當前資源',
|
||||||
|
includeAll: '包含所有子資源',
|
||||||
|
effectiveResource: '生效資源',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resource_management: {
|
resource_management: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue