folder.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. # -*- coding: utf-8 -*-
  2. import uuid_utils.compat as uuid
  3. from django.db import transaction
  4. from django.db.models import QuerySet, Q, Func, F, TextField
  5. from django.db.models.functions import Cast
  6. from django.utils.translation import gettext_lazy as _
  7. from rest_framework import serializers
  8. from application.models.application import Application, ApplicationFolder
  9. from application.serializers.application import ApplicationOperateSerializer
  10. from application.serializers.application_folder import ApplicationFolderTreeSerializer
  11. from common.constants.permission_constants import Group, ResourcePermission, ResourcePermissionRole, RoleConstants
  12. from common.database_model_manage.database_model_manage import DatabaseModelManage
  13. from common.exception.app_exception import AppApiException
  14. from folders.api.folder import FolderCreateRequest
  15. from knowledge.models import KnowledgeFolder, Knowledge
  16. from knowledge.serializers.knowledge import KnowledgeSerializer
  17. from knowledge.serializers.knowledge_folder import KnowledgeFolderTreeSerializer
  18. from system_manage.models import WorkspaceUserResourcePermission
  19. from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer
  20. from tools.models import ToolFolder, Tool
  21. from tools.serializers.tool import ToolSerializer
  22. from tools.serializers.tool_folder import ToolFolderTreeSerializer
  23. from users.serializers.user import is_workspace_manage
  24. def has_exact_permission_by_role(user_id: str, workspace_id: str, permission_id: str, role_type: str):
  25. workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
  26. role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model")
  27. is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None
  28. if is_x_pack_ee:
  29. return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter(
  30. Q(role__rolepermission__permission_id=permission_id) | Q(role__internal=True),
  31. workspace_id=workspace_id,
  32. user_id=user_id,
  33. role__type=role_type,
  34. ).exists()
  35. return False
  36. def get_source_type(source):
  37. if source == Group.TOOL.name:
  38. return Tool
  39. elif source == Group.APPLICATION.name:
  40. return Application
  41. elif source == Group.KNOWLEDGE.name:
  42. return Knowledge
  43. else:
  44. return None
  45. def get_folder_type(source):
  46. if source == Group.TOOL.name:
  47. return ToolFolder
  48. elif source == Group.APPLICATION.name:
  49. return ApplicationFolder
  50. elif source == Group.KNOWLEDGE.name:
  51. return KnowledgeFolder
  52. else:
  53. return None
  54. def get_folder_tree_serializer(source):
  55. if source == Group.TOOL.name:
  56. return ToolFolderTreeSerializer
  57. elif source == Group.APPLICATION.name:
  58. return ApplicationFolderTreeSerializer
  59. elif source == Group.KNOWLEDGE.name:
  60. return KnowledgeFolderTreeSerializer
  61. else:
  62. return None
  63. FOLDER_DEPTH = 10000
  64. def check_depth(source, parent_id, workspace_id, current_depth=0):
  65. # Folder 不能超过3层
  66. Folder = get_folder_type(source) # noqa
  67. if parent_id != workspace_id:
  68. # 计算当前层级
  69. depth = 1 # 当前要创建的节点算一层
  70. current_parent_id = parent_id
  71. # 向上追溯父节点
  72. while current_parent_id != workspace_id:
  73. depth += 1
  74. parent_node = QuerySet(Folder).filter(id=current_parent_id).first()
  75. if parent_node is None:
  76. break
  77. current_parent_id = parent_node.parent_id
  78. # 验证层级深度
  79. if depth + current_depth > FOLDER_DEPTH:
  80. raise serializers.ValidationError(_('Folder depth cannot exceed 10000 levels'))
  81. def get_max_depth(current_node):
  82. if not current_node:
  83. return 0
  84. # 获取所有后代节点
  85. descendants = current_node.get_descendants()
  86. if not descendants.exists():
  87. return 0
  88. # 获取最大深度
  89. max_level = descendants.order_by('-level').first().level
  90. current_level = current_node.level
  91. max_depth = max_level - current_level
  92. return max_depth
  93. def has_target_permission(workspace_id, source, user_id, target):
  94. return QuerySet(WorkspaceUserResourcePermission).filter(workspace_id=workspace_id, user_id=user_id,
  95. auth_target_type=source, target=target,
  96. permission_list__contains=['MANAGE']).exists()
  97. class FolderSerializer(serializers.Serializer):
  98. id = serializers.CharField(required=True, label=_('folder id'))
  99. name = serializers.CharField(required=True, label=_('folder name'))
  100. desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('folder description'))
  101. user_id = serializers.CharField(required=True, label=_('folder user id'))
  102. workspace_id = serializers.CharField(required=False, label=_('workspace id'))
  103. parent_id = serializers.CharField(required=False, label=_('parent id'))
  104. class Create(serializers.Serializer):
  105. workspace_id = serializers.CharField(required=True, label=_('workspace id'))
  106. user_id = serializers.UUIDField(required=True, label=_('user id'))
  107. source = serializers.CharField(required=True, label=_('source'))
  108. def insert(self, instance, with_valid=True):
  109. if with_valid:
  110. self.is_valid(raise_exception=True)
  111. FolderCreateRequest(data=instance).is_valid(raise_exception=True)
  112. workspace_id = self.data.get('workspace_id')
  113. if not workspace_id:
  114. workspace_id = 'default'
  115. parent_id = instance.get('parent_id')
  116. if not parent_id:
  117. parent_id = workspace_id
  118. name = instance.get('name')
  119. Folder = get_folder_type(self.data.get('source')) # noqa
  120. if QuerySet(Folder).filter(name=name, workspace_id=workspace_id, parent_id=parent_id).exists():
  121. raise Exception(_('Folder name already exists'))
  122. # Folder 不能超过3层
  123. check_depth(self.data.get('source'), parent_id, workspace_id)
  124. folder = Folder(
  125. id=uuid.uuid7(),
  126. name=instance.get('name'),
  127. desc=instance.get('desc'),
  128. user_id=self.data.get('user_id'),
  129. workspace_id=workspace_id,
  130. parent_id=parent_id
  131. )
  132. folder.save()
  133. UserResourcePermissionSerializer(data={
  134. 'workspace_id': self.data.get('workspace_id'),
  135. 'user_id': self.data.get('user_id'),
  136. 'auth_target_type': self.data.get('source')
  137. }).auth_resource(str(folder.id), is_folder=True)
  138. return FolderSerializer(folder).data
  139. class Operate(serializers.Serializer):
  140. id = serializers.CharField(required=True, label=_('folder id'))
  141. workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id'))
  142. source = serializers.CharField(required=True, label=_('source'))
  143. user_id = serializers.UUIDField(required=True, label=_('user id'))
  144. @transaction.atomic
  145. def edit(self, instance):
  146. self.is_valid(raise_exception=True)
  147. Folder = get_folder_type(self.data.get('source')) # noqa
  148. current_id = self.data.get('id')
  149. current_node = Folder.objects.get(id=current_id)
  150. if current_node is None:
  151. raise serializers.ValidationError(_('Folder does not exist'))
  152. # 模块间的移动
  153. parent_id = instance.get('parent_id')
  154. if parent_id is None:
  155. parent_id = current_node.parent_id
  156. # 如果要修改文件夹名称,检查同级目录下是否存在同名文件夹
  157. new_name = instance.get('name')
  158. if new_name is not None and new_name != current_node.name:
  159. if QuerySet(Folder).filter(
  160. name=new_name,
  161. parent_id=parent_id,
  162. workspace_id=current_node.workspace_id
  163. ).exclude(id=current_id).exists():
  164. raise serializers.ValidationError(_('Folder name already exists'))
  165. edit_field_list = ['name', 'desc']
  166. edit_dict = {field: instance.get(field) for field in edit_field_list if (
  167. field in instance and instance.get(field) is not None)}
  168. QuerySet(Folder).filter(id=current_id).update(**edit_dict)
  169. current_node.refresh_from_db()
  170. if parent_id is not None and current_id != current_node.workspace_id and current_node.parent_id != parent_id:
  171. source_type = self.data.get('source')
  172. if has_target_permission(current_node.workspace_id, source_type, self.data.get('user_id'),
  173. parent_id) or is_workspace_manage(self.data.get('user_id'),
  174. current_node.workspace_id):
  175. current_depth = get_max_depth(current_node)
  176. check_depth(self.data.get('source'), parent_id, current_node.workspace_id, current_depth)
  177. parent = Folder.objects.get(id=parent_id)
  178. if QuerySet(Folder).filter(name=current_node.name, parent_id=parent_id,
  179. workspace_id=current_node.workspace_id).exists():
  180. raise serializers.ValidationError(_('Folder name already exists'))
  181. current_node.parent = parent
  182. current_node.save()
  183. current_node.refresh_from_db()
  184. else:
  185. raise AppApiException(403, _('No permission for the target folder'))
  186. return self.one()
  187. def one(self):
  188. self.is_valid(raise_exception=True)
  189. Folder = get_folder_type(self.data.get('source')) # noqa
  190. folder = QuerySet(Folder).filter(id=self.data.get('id')).first()
  191. return FolderSerializer(folder).data
  192. @transaction.atomic
  193. def delete(self):
  194. self.is_valid(raise_exception=True)
  195. Folder = get_folder_type(self.data.get('source')) # noqa
  196. Source = get_source_type(self.data.get('source')) # noqa
  197. folder = Folder.objects.filter(id=self.data.get('id')).first()
  198. if not folder:
  199. raise serializers.ValidationError(_('Folder does not exist'))
  200. if folder.id == folder.workspace_id:
  201. raise serializers.ValidationError(_('Cannot delete root folder'))
  202. # 工作空间管理员可以删除
  203. workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))
  204. if workspace_manage:
  205. nodes = Folder.objects.filter(id=self.data.get('id')).get_descendants(include_self=True)
  206. for node in nodes:
  207. # print(node)
  208. # 删除相关的资源
  209. self.delete_source(node)
  210. # 删除节点
  211. node.delete()
  212. # 普通用户删除的文件夹内全部都得是自己有权限的资源
  213. else:
  214. nodes = Folder.objects.filter(id=self.data.get('id')).get_descendants(include_self=True)
  215. for node in nodes:
  216. # 删除相关的资源
  217. source_ids = (Source.objects.filter(folder_id=node.id)
  218. .annotate(id_str=Cast('id', TextField()))
  219. .values_list('id_str', flat=True))
  220. # 检查文件夹是否存在未授权当前用户的资源
  221. auth_list = QuerySet(WorkspaceUserResourcePermission).filter(
  222. Q(workspace_id=self.data.get('workspace_id')) &
  223. Q(user_id=self.data.get('user_id')) &
  224. Q(auth_target_type=self.data.get('source')) &
  225. Q(target__in=source_ids) &
  226. Q(permission_list__overlap=[ResourcePermission.MANAGE, ResourcePermissionRole.ROLE])
  227. ).count()
  228. if auth_list != len(source_ids):
  229. raise AppApiException(500, _('This folder contains resources that you dont have permission'))
  230. self.delete_source(node)
  231. node.delete()
  232. def delete_source(self, node):
  233. Source = get_source_type(self.data.get('source')) # noqa
  234. source_ids = Source.objects.filter(folder_id=node.id).values_list('id', flat=True)
  235. source = self.data.get('source')
  236. for source_id in source_ids:
  237. if source == Group.TOOL.name:
  238. ToolSerializer.Operate(data={
  239. 'workspace_id': self.data.get('workspace_id'),
  240. 'id': source_id,
  241. }).delete()
  242. elif source == Group.APPLICATION.name:
  243. ApplicationOperateSerializer(data={
  244. 'workspace_id': self.data.get('workspace_id'),
  245. 'application_id': source_id,
  246. 'user_id': self.data.get('user_id'),
  247. }).delete()
  248. elif source == Group.KNOWLEDGE.name:
  249. KnowledgeSerializer.Operate(data={
  250. 'workspace_id': self.data.get('workspace_id'),
  251. 'knowledge_id': source_id,
  252. 'user_id': self.data.get('user_id'),
  253. }).delete()
  254. class FolderTreeSerializer(serializers.Serializer):
  255. workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id'))
  256. source = serializers.CharField(required=True, label=_('source'))
  257. @staticmethod
  258. def _check_tree_integrity(queryset):
  259. """检查树结构完整性"""
  260. for folder in queryset:
  261. if folder.lft >= folder.rght:
  262. return True # 需要重建
  263. if folder.is_leaf_node() and folder.get_children().exists():
  264. return True # 需要重建
  265. return False
  266. @staticmethod
  267. def _having_read_permission_by_role(user_id: str, workspace_id: str, source: str):
  268. workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
  269. role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model")
  270. is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None
  271. if is_x_pack_ee:
  272. return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter(
  273. Q(role__rolepermission__permission_id=f"{source}_FOLDER:READ") | Q(role__internal=True),
  274. workspace_id=workspace_id,
  275. user_id=user_id,
  276. role__type=RoleConstants.USER.value.__str__(),
  277. ).exists()
  278. return False
  279. def get_folder_tree(self,
  280. current_user, name=None):
  281. self.is_valid(raise_exception=True)
  282. user_id = current_user.id
  283. workspace_id = self.data.get('workspace_id')
  284. source = self.data.get('source')
  285. Folder = get_folder_type(source) # noqa
  286. # 检查特定工作空间的树结构完整性
  287. workspace_folders = Folder.objects.filter(workspace_id=workspace_id)
  288. # 如果发现数据不一致,重建整个表(这是 MPTT 的限制)
  289. if self._check_tree_integrity(workspace_folders):
  290. Folder.objects.rebuild()
  291. workspace_manage = is_workspace_manage(user_id, workspace_id)
  292. base_q = Q(workspace_id=workspace_id)
  293. if name is not None:
  294. base_q &= Q(name__contains=name)
  295. if not workspace_manage:
  296. having_read_permission_by_role = has_exact_permission_by_role(user_id, workspace_id, f"{source}_FOLDER:READ", RoleConstants.USER.value.__str__())
  297. permission_condition = ['VIEW']
  298. if having_read_permission_by_role:
  299. permission_condition = ['VIEW', 'ROLE']
  300. base_q &= (Q(id__in=WorkspaceUserResourcePermission.objects.filter(user_id=current_user.id,
  301. auth_target_type=self.data.get('source'),
  302. workspace_id=self.data.get(
  303. 'workspace_id'),
  304. permission_list__overlap=permission_condition)
  305. .values_list(
  306. 'target', flat=True)) | Q(id=self.data.get('workspace_id')))
  307. nodes = Folder.objects.filter(base_q).get_cached_trees()
  308. TreeSerializer = get_folder_tree_serializer(self.data.get('source')) # noqa
  309. serializer = TreeSerializer(nodes, many=True)
  310. return [d for d in serializer.data if
  311. d.get('id') == d.get('workspace_id')] if name is None else serializer.data # 这是可序列化的字典