perf: 优化授权规则 user-permission 用户授权相关的 API; 包括 assets, nodes, tree-asset, tree-node, tree-node-with-asset;

pull/9169/head
Bai 2022-12-07 18:38:03 +08:00
parent 1cab84bb62
commit 6cda28c63d
17 changed files with 339 additions and 428 deletions

View File

@ -30,16 +30,13 @@ __all__ = [
class AssetFilterSet(BaseFilterSet): class AssetFilterSet(BaseFilterSet):
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact") type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
category = django_filters.CharFilter( category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
field_name="platform__category", lookup_expr="exact"
)
hostname = django_filters.CharFilter(field_name="name", lookup_expr="exact")
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
"id", "name", "address", "is_active", "id", "name", "address", "is_active",
"type", "category", "hostname" "type", "category"
] ]

View File

@ -23,8 +23,8 @@ __all__ = [
class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedAssetsApi(ListAPIView):
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetPermedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetPermedSerializer.Meta.only_fields
filterset_fields = ['name', 'address', 'id', 'comment'] filterset_fields = ['name', 'address', 'id', 'comment']
search_fields = ['name', 'address', 'comment'] search_fields = ['name', 'address', 'comment']
rbac_perms = { rbac_perms = {
@ -60,8 +60,8 @@ class UserGroupGrantedAssetsApi(ListAPIView):
class UserGroupGrantedNodeAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView):
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetPermedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetPermedSerializer.Meta.only_fields
filterset_fields = ['name', 'address', 'id', 'comment'] filterset_fields = ['name', 'address', 'id', 'comment']
search_fields = ['name', 'address', 'comment'] search_fields = ['name', 'address', 'comment']
rbac_perms = { rbac_perms = {

View File

@ -2,5 +2,5 @@
# #
from .nodes import * from .nodes import *
from .assets import * from .assets import *
from .nodes_with_assets import *
from .accounts import * from .accounts import *
from .tree import *

View File

@ -25,6 +25,5 @@ class UserPermedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView):
return asset return asset
def get_queryset(self): def get_queryset(self):
util = PermAccountUtil() accounts = PermAccountUtil().get_permed_accounts_for_user(self.user, self.asset)
accounts = util.get_permed_accounts_for_user(self.user, self.asset)
return accounts return accounts

View File

@ -1,109 +1,76 @@
from django.conf import settings import abc
from rest_framework.generics import ListAPIView from rest_framework.generics import ListAPIView
from assets.models import Asset, Node from assets.models import Asset, Node
from common.utils import get_logger from assets.api.asset.asset import AssetFilterSet
from perms import serializers from perms import serializers
from perms.pagination import AllGrantedAssetPagination from perms.pagination import AllPermedAssetPagination
from perms.pagination import NodeGrantedAssetPagination from perms.pagination import NodePermedAssetPagination
from perms.utils.user_permission import UserGrantedAssetsQueryUtils from perms.utils.user_permission import UserGrantedAssetsQueryUtils
from common.utils import get_logger, lazyproperty
from .mixin import ( from .mixin import (
SelfOrPKUserMixin, RebuildTreeMixin, SelfOrPKUserMixin
PermedAssetSerializerMixin, AssetsTreeFormatMixin
) )
__all__ = [ __all__ = [
'UserAllPermedAssetsApi',
'UserDirectPermedAssetsApi', 'UserDirectPermedAssetsApi',
'UserFavoriteAssetsApi', 'UserFavoriteAssetsApi',
'UserDirectPermedAssetsAsTreeApi',
'UserUngroupAssetsAsTreeApi',
'UserAllPermedAssetsApi',
'UserPermedNodeAssetsApi', 'UserPermedNodeAssetsApi',
] ]
logger = get_logger(__name__) logger = get_logger(__name__)
class UserDirectPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): class BaseUserPermedAssetsApi(SelfOrPKUserMixin, ListAPIView):
""" 直接授权给用户的资产 """ ordering = ('name',)
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields ordering_fields = ("name", "address")
search_fields = ('name', 'address', 'comment')
filterset_class = AssetFilterSet
serializer_class = serializers.AssetPermedSerializer
only_fields = serializers.AssetPermedSerializer.Meta.only_fields
def get_queryset(self): def get_queryset(self):
if getattr(self, 'swagger_fake_view', False): if getattr(self, 'swagger_fake_view', False):
return Asset.objects.none() return Asset.objects.none()
assets = self.get_assets()
assets = UserGrantedAssetsQueryUtils(self.user) \
.get_direct_granted_assets() \
.prefetch_related('platform') \
.only(*self.only_fields)
return assets
class UserFavoriteAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView):
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
""" 用户收藏的授权资产 """
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
return Asset.objects.none()
user = self.user
utils = UserGrantedAssetsQueryUtils(user)
assets = utils.get_favorite_assets()
assets = assets.prefetch_related('platform').only(*self.only_fields) assets = assets.prefetch_related('platform').only(*self.only_fields)
return assets return assets
@abc.abstractmethod
def get_assets(self):
return Asset.objects.none()
class UserDirectPermedAssetsAsTreeApi(RebuildTreeMixin, AssetsTreeFormatMixin, UserDirectPermedAssetsApi): @lazyproperty
""" 用户直接授权的资产作为树 """ def query_asset_util(self):
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields return UserGrantedAssetsQueryUtils(self.user)
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
return Asset.objects.none()
assets = UserGrantedAssetsQueryUtils(self.user) \
.get_direct_granted_assets() \
.prefetch_related('platform') \
.only(*self.only_fields)
return assets
class UserUngroupAssetsAsTreeApi(UserDirectPermedAssetsAsTreeApi): class UserAllPermedAssetsApi(BaseUserPermedAssetsApi):
""" 用户未分组节点下的资产作为树 """ pagination_class = AllPermedAssetPagination
def get_queryset(self): def get_assets(self):
queryset = super().get_queryset() return self.query_asset_util.get_all_granted_assets()
if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
queryset = queryset.none()
return queryset
class UserAllPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): class UserDirectPermedAssetsApi(BaseUserPermedAssetsApi):
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields def get_assets(self):
pagination_class = AllGrantedAssetPagination return self.query_asset_util.get_direct_granted_assets()
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
return Asset.objects.none()
queryset = UserGrantedAssetsQueryUtils(self.user).get_all_granted_assets()
only_fields = [i for i in self.only_fields if i not in ['protocols']]
queryset = queryset.prefetch_related('platform', 'protocols').only(*only_fields)
return queryset
class UserPermedNodeAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): class UserFavoriteAssetsApi(BaseUserPermedAssetsApi):
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields def get_assets(self):
pagination_class = NodeGrantedAssetPagination return self.query_asset_util.get_favorite_assets()
kwargs: dict
class UserPermedNodeAssetsApi(BaseUserPermedAssetsApi):
pagination_class = NodePermedAssetPagination
pagination_node: Node pagination_node: Node
def get_queryset(self): def get_assets(self):
if getattr(self, 'swagger_fake_view', False):
return Asset.objects.none()
node_id = self.kwargs.get("node_id") node_id = self.kwargs.get("node_id")
node, assets = self.query_asset_util.get_node_all_assets(node_id)
node, assets = UserGrantedAssetsQueryUtils(self.user).get_node_all_assets(node_id)
assets = assets.prefetch_related('platform').only(*self.only_fields)
self.pagination_node = node self.pagination_node = node
return assets return assets

View File

@ -3,27 +3,13 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response
from assets.api.asset.asset import AssetFilterSet
from assets.api.mixin import SerializeToTreeNodeMixin
from common.exceptions import JMSObjectDoesNotExist
from common.http import is_true
from common.utils import is_uuid
from perms import serializers
from perms.utils.user_permission import UserGrantedTreeRefreshController
from rbac.permissions import RBACPermission
from users.models import User from users.models import User
from rbac.permissions import RBACPermission
from common.utils import is_uuid
from common.exceptions import JMSObjectDoesNotExist
__all__ = ['SelfOrPKUserMixin']
class RebuildTreeMixin:
user: User
def get(self, request: Request, *args, **kwargs):
force = is_true(request.query_params.get('rebuild_tree'))
controller = UserGrantedTreeRefreshController(self.user)
controller.refresh_if_need(force)
return super().get(request, *args, **kwargs)
class SelfOrPKUserMixin: class SelfOrPKUserMixin:
@ -72,32 +58,3 @@ class SelfOrPKUserMixin:
def request_user_is_self(self): def request_user_is_self(self):
return self.kwargs.get('user') in ['my', 'self'] return self.kwargs.get('user') in ['my', 'self']
class PermedAssetSerializerMixin:
serializer_class = serializers.AssetGrantedSerializer
filterset_class = AssetFilterSet
search_fields = ['name', 'address', 'comment']
ordering_fields = ("name", "address")
ordering = ('name',)
class AssetsTreeFormatMixin(SerializeToTreeNodeMixin):
"""
资产 序列化成树的结构返回
"""
filter_queryset: callable
get_queryset: callable
filterset_fields = ['name', 'address', 'id', 'comment']
search_fields = ['name', 'address', 'comment']
def list(self, request: Request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
if request.query_params.get('search'):
# 如果用户搜索的条件不精准,会导致返回大量的无意义数据。
# 这里限制一下返回数据的最大条数
queryset = queryset[:999]
queryset = sorted(queryset, key=lambda asset: asset.name)
data = self.serialize_assets(queryset, None)
return Response(data=data)

View File

@ -1,133 +1,49 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import abc import abc
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.generics import ListAPIView from rest_framework.generics import ListAPIView
from common.utils import get_logger from assets.models import Node
from assets.api.mixin import SerializeToTreeNodeMixin
from perms import serializers from perms import serializers
from perms.hands import User
from perms.utils.user_permission import UserGrantedNodesQueryUtils from perms.utils.user_permission import UserGrantedNodesQueryUtils
from common.utils import get_logger, lazyproperty
from .mixin import SelfOrPKUserMixin, RebuildTreeMixin from .mixin import SelfOrPKUserMixin
logger = get_logger(__name__) logger = get_logger(__name__)
__all__ = [ __all__ = [
'UserPermedNodesApi', 'UserAllPermedNodesApi',
'UserPermedNodesAsTreeApi',
'UserPermedNodeChildrenApi', 'UserPermedNodeChildrenApi',
'UserPermedNodeChildrenAsTreeApi',
'BaseGrantedNodeAsTreeApi',
'UserGrantedNodesMixin',
] ]
class _GrantedNodeStructApi(ListAPIView, metaclass=abc.ABCMeta): class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView):
@property
def user(self):
raise NotImplementedError
def get_nodes(self):
# 不使用 `get_queryset` 单独定义 `get_nodes` 的原因是
# `get_nodes` 返回的不一定是 `queryset`
raise NotImplementedError
class NodeChildrenMixin:
def get_children(self):
raise NotImplementedError
def get_nodes(self):
nodes = self.get_children()
return nodes
class BaseGrantedNodeApi(_GrantedNodeStructApi, metaclass=abc.ABCMeta):
serializer_class = serializers.NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
def list(self, request, *args, **kwargs): def get_queryset(self):
nodes = self.get_nodes() if getattr(self, 'swagger_fake_view', False):
serializer = self.get_serializer(nodes, many=True) return Node.objects.none()
return Response(serializer.data) return self.get_nodes()
class BaseNodeChildrenApi(NodeChildrenMixin, BaseGrantedNodeApi, metaclass=abc.ABCMeta):
pass
class BaseGrantedNodeAsTreeApi(SerializeToTreeNodeMixin, _GrantedNodeStructApi, metaclass=abc.ABCMeta):
def list(self, request: Request, *args, **kwargs):
nodes = self.get_nodes()
nodes = self.serialize_nodes(nodes, with_asset_amount=True)
return Response(data=nodes)
class BaseNodeChildrenAsTreeApi(NodeChildrenMixin, BaseGrantedNodeAsTreeApi, metaclass=abc.ABCMeta):
pass
class UserGrantedNodeChildrenMixin:
user: User
request: Request
def get_children(self):
user = self.user
key = self.request.query_params.get('key')
nodes = UserGrantedNodesQueryUtils(user).get_node_children(key)
return nodes
class UserGrantedNodesMixin:
"""
查询用户授权的所有节点 直接授权节点 + 授权资产关联的节点
"""
user: User
@abc.abstractmethod
def get_nodes(self): def get_nodes(self):
utils = UserGrantedNodesQueryUtils(self.user) return []
nodes = utils.get_whole_tree_nodes()
return nodes @lazyproperty
def query_node_util(self):
return UserGrantedNodesQueryUtils(self.user)
# API class UserAllPermedNodesApi(BaseUserPermedNodesApi):
class UserPermedNodeChildrenApi(
SelfOrPKUserMixin,
UserGrantedNodeChildrenMixin,
BaseNodeChildrenApi
):
""" 用户授权的节点下的子节点"""
pass
class UserPermedNodeChildrenAsTreeApi(
SelfOrPKUserMixin,
RebuildTreeMixin,
UserGrantedNodeChildrenMixin,
BaseNodeChildrenAsTreeApi
):
""" 用户授权的节点下的子节点树"""
pass
class UserPermedNodesApi(
SelfOrPKUserMixin,
UserGrantedNodesMixin,
BaseGrantedNodeApi
):
""" 用户授权的节点 """ """ 用户授权的节点 """
pass def get_nodes(self):
return self.query_node_util.get_whole_tree_nodes()
class UserPermedNodesAsTreeApi( class UserPermedNodeChildrenApi(BaseUserPermedNodesApi):
SelfOrPKUserMixin, """ 用户授权的节点下的子节点 """
RebuildTreeMixin, def get_nodes(self):
UserGrantedNodesMixin, key = self.request.query_params.get('key')
BaseGrantedNodeAsTreeApi nodes = self.query_node_util.get_node_children(key)
): return nodes
""" 用户授权的节点树 """
pass

View File

@ -1,158 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.conf import settings
from django.db.models import F, Value, CharField
from rest_framework.generics import ListAPIView
from rest_framework.request import Request
from rest_framework.response import Response
from common.utils import get_logger, get_object_or_none
from common.utils.common import timeit
from common.permissions import IsValidUser
from assets.models import Asset
from assets.api import SerializeToTreeNodeMixin
from perms.hands import Node
from perms.models import AssetPermission, PermNode
from perms.utils.user_permission import (
UserGrantedTreeBuildUtils, get_user_all_asset_perm_ids,
UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils,
)
from .mixin import SelfOrPKUserMixin, RebuildTreeMixin
logger = get_logger(__name__)
class MyGrantedNodesWithAssetsAsTreeApi(SerializeToTreeNodeMixin, ListAPIView):
permission_classes = (IsValidUser,)
@timeit
def add_ungrouped_resource(self, data: list, nodes_query_utils, assets_query_utils):
if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
return
ungrouped_node = nodes_query_utils.get_ungrouped_node()
direct_granted_assets = assets_query_utils.get_direct_granted_assets().annotate(
parent_key=Value(ungrouped_node.key, output_field=CharField())
).prefetch_related('platform')
data.extend(self.serialize_nodes([ungrouped_node], with_asset_amount=True))
data.extend(self.serialize_assets(direct_granted_assets))
@timeit
def add_favorite_resource(self, data: list, nodes_query_utils, assets_query_utils):
favorite_node = nodes_query_utils.get_favorite_node()
favorite_assets = assets_query_utils.get_favorite_assets()
favorite_assets = favorite_assets.annotate(
parent_key=Value(favorite_node.key, output_field=CharField())
).prefetch_related('platform')
data.extend(self.serialize_nodes([favorite_node], with_asset_amount=True))
data.extend(self.serialize_assets(favorite_assets))
@timeit
def add_node_filtered_by_system_user(self, data: list, user, asset_perm_ids):
utils = UserGrantedTreeBuildUtils(user, asset_perm_ids)
nodes = utils.get_whole_tree_nodes()
data.extend(self.serialize_nodes(nodes, with_asset_amount=True))
def add_assets(self, data: list, assets_query_utils: UserGrantedAssetsQueryUtils):
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
all_assets = assets_query_utils.get_direct_granted_nodes_assets()
else:
all_assets = assets_query_utils.get_all_granted_assets()
all_assets = all_assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform')
data.extend(self.serialize_assets(all_assets))
def list(self, request: Request, *args, **kwargs):
"""
此算法依赖 UserGrantedMappingNode
获取所有授权的节点和资产
Node = UserGrantedMappingNode + 授权节点的子节点
Asset = 授权节点的资产 + 直接授权的资产
"""
user = request.user
data = []
asset_perm_ids = get_user_all_asset_perm_ids(user)
system_user_id = request.query_params.get('system_user')
if system_user_id:
asset_perm_ids = list(AssetPermission.objects.valid().filter(
id__in=asset_perm_ids, system_users__id=system_user_id, actions__gt=0
).values_list('id', flat=True).distinct())
nodes_query_utils = UserGrantedNodesQueryUtils(user, asset_perm_ids)
assets_query_utils = UserGrantedAssetsQueryUtils(user, asset_perm_ids)
self.add_ungrouped_resource(data, nodes_query_utils, assets_query_utils)
self.add_favorite_resource(data, nodes_query_utils, assets_query_utils)
if system_user_id:
# 有系统用户筛选的需要重新计算树结构
self.add_node_filtered_by_system_user(data, user, asset_perm_ids)
else:
all_nodes = nodes_query_utils.get_whole_tree_nodes(with_special=False)
data.extend(self.serialize_nodes(all_nodes, with_asset_amount=True))
self.add_assets(data, assets_query_utils)
return Response(data=data)
class GrantedNodeChildrenWithAssetsAsTreeApiMixin(SerializeToTreeNodeMixin,
ListAPIView):
"""
带资产的授权树
"""
user: None
def ensure_key(self):
key = self.request.query_params.get('key', None)
id = self.request.query_params.get('id', None)
if key is not None:
return key
node = get_object_or_none(Node, id=id)
if node:
return node.key
def list(self, request: Request, *args, **kwargs):
user = self.user
key = self.ensure_key()
nodes_query_utils = UserGrantedNodesQueryUtils(user)
assets_query_utils = UserGrantedAssetsQueryUtils(user)
nodes = PermNode.objects.none()
assets = Asset.objects.none()
all_tree_nodes = []
if not key:
nodes = nodes_query_utils.get_top_level_nodes()
elif key == PermNode.UNGROUPED_NODE_KEY:
assets = assets_query_utils.get_ungroup_assets()
elif key == PermNode.FAVORITE_NODE_KEY:
assets = assets_query_utils.get_favorite_assets()
else:
nodes = nodes_query_utils.get_node_children(key)
assets = assets_query_utils.get_node_assets(key)
assets = assets.prefetch_related('platform')
tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True)
tree_assets = self.serialize_assets(assets, key)
all_tree_nodes.extend(tree_nodes)
all_tree_nodes.extend(tree_assets)
return Response(data=all_tree_nodes)
class UserGrantedNodeChildrenWithAssetsAsTreeApi(
SelfOrPKUserMixin,
RebuildTreeMixin,
GrantedNodeChildrenWithAssetsAsTreeApiMixin
):
""" 用户授权的节点的子节点与资产树 """
pass

View File

@ -0,0 +1,3 @@
from .asset import *
from .node import *
from .node_with_asset import *

View File

@ -0,0 +1,48 @@
from django.conf import settings
from rest_framework.response import Response
from assets.models import Asset
from assets.api import SerializeToTreeNodeMixin
from common.utils import get_logger
from ..assets import UserDirectPermedAssetsApi
from .mixin import RebuildTreeMixin
logger = get_logger(__name__)
__all__ = [
'UserDirectPermedAssetsAsTreeApi',
'UserUngroupAssetsAsTreeApi',
]
class AssetTreeMixin(RebuildTreeMixin, SerializeToTreeNodeMixin):
""" 将资产序列化成树节点的结构返回 """
filter_queryset: callable
get_queryset: callable
ordering = ('name',)
filterset_fields = ('id', 'name', 'address', 'comment')
search_fields = ('name', 'address', 'comment')
def list(self, request, *args, **kwargs):
assets = self.filter_queryset(self.get_queryset())
if request.query_params.get('search'):
""" 限制返回数量, 搜索的条件不精准时,会返回大量的无意义数据 """
assets = assets[:999]
data = self.serialize_assets(assets, None)
return Response(data=data)
class UserDirectPermedAssetsAsTreeApi(AssetTreeMixin, UserDirectPermedAssetsApi):
""" 用户 '直接授权的资产' 作为树 """
pass
class UserUngroupAssetsAsTreeApi(UserDirectPermedAssetsAsTreeApi):
""" 用户 '未分组节点的资产(直接授权的资产)' 作为树 """
def get_assets(self):
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
return super().get_assets()
return Asset.objects.none()

View File

@ -0,0 +1,17 @@
from rest_framework.request import Request
from users.models import User
from perms.utils.user_permission import UserGrantedTreeRefreshController
from common.http import is_true
__all__ = ['RebuildTreeMixin']
class RebuildTreeMixin:
user: User
def get(self, request: Request, *args, **kwargs):
force = is_true(request.query_params.get('rebuild_tree'))
UserGrantedTreeRefreshController(self.user).refresh_if_need(force)
return super().get(request, *args, **kwargs)

View File

@ -0,0 +1,39 @@
from rest_framework.response import Response
from assets.api import SerializeToTreeNodeMixin
from common.utils import get_logger
from .mixin import RebuildTreeMixin
from ..nodes import (
UserAllPermedNodesApi,
UserPermedNodeChildrenApi,
)
logger = get_logger(__name__)
__all__ = [
'UserAllPermedNodesAsTreeApi',
'UserPermedNodeChildrenAsTreeApi',
]
class NodeTreeMixin(RebuildTreeMixin, SerializeToTreeNodeMixin):
filter_queryset: callable
get_queryset: callable
def list(self, request, *args, **kwargs):
nodes = self.filter_queryset(self.get_queryset())
data = self.serialize_nodes(nodes, with_asset_amount=True)
return Response(data)
class UserAllPermedNodesAsTreeApi(NodeTreeMixin, UserAllPermedNodesApi):
""" 用户 '授权的节点' 作为树 """
pass
class UserPermedNodeChildrenAsTreeApi(NodeTreeMixin, UserPermedNodeChildrenApi):
""" 用户授权的节点下的子节点树 """
pass

View File

@ -0,0 +1,125 @@
import abc
from django.conf import settings
from django.db.models import F, Value, CharField
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from assets.models import Asset
from assets.api import SerializeToTreeNodeMixin
from perms.hands import Node
from perms.models import PermNode
from perms.utils.user_permission import (
UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils,
)
from perms.utils.permission import AssetPermissionUtil
from common.utils import get_object_or_none, lazyproperty
from common.utils.common import timeit
from ..mixin import SelfOrPKUserMixin
from .mixin import RebuildTreeMixin
__all__ = [
'UserPermedNodesWithAssetsAsTreeApi',
'UserPermedNodeChildrenWithAssetsAsTreeApi'
]
class BaseUserNodeWithAssetAsTreeApi(SelfOrPKUserMixin, RebuildTreeMixin, SerializeToTreeNodeMixin,
ListAPIView):
def list(self, request, *args, **kwargs):
nodes, assets = self.get_nodes_assets()
tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True)
tree_assets = self.serialize_assets(assets, node_key=self.node_key_for_serializer_assets)
data = list(tree_nodes) + list(tree_assets)
return Response(data=data)
@abc.abstractmethod
def get_nodes_assets(self):
return [], []
@lazyproperty
def node_key_for_serializer_assets(self):
return None
class UserPermedNodesWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi):
query_node_util: UserGrantedNodesQueryUtils
query_asset_util: UserGrantedAssetsQueryUtils
def get_nodes_assets(self):
perm_ids = AssetPermissionUtil().get_permissions_for_user(self.request.user, flat=True)
self.query_node_util = UserGrantedNodesQueryUtils(self.request.user, perm_ids)
self.query_asset_util = UserGrantedAssetsQueryUtils(self.request.user, perm_ids)
ung_nodes, ung_assets = self._get_nodes_assets_for_ungrouped()
fav_nodes, fav_assets = self._get_nodes_assets_for_favorite()
all_nodes, all_assets = self._get_nodes_assets_for_all()
nodes = list(ung_nodes) + list(fav_nodes) + list(all_nodes)
assets = list(ung_assets) + list(fav_assets) + list(all_assets)
return nodes, assets
@timeit
def _get_nodes_assets_for_ungrouped(self):
if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
return [], []
node = self.query_node_util.get_ungrouped_node()
assets = self.query_asset_util.get_ungroup_assets()
assets = assets.annotate(parent_key=Value(node.key, output_field=CharField())) \
.prefetch_related('platform')
return [node], assets
@timeit
def _get_nodes_assets_for_favorite(self):
node = self.query_node_util.get_favorite_node()
assets = self.query_asset_util.get_favorite_assets()
assets = assets.annotate(parent_key=Value(node.key, output_field=CharField())) \
.prefetch_related('platform')
return [node], assets
def _get_nodes_assets_for_all(self):
nodes = self.query_node_util.get_whole_tree_nodes(with_special=False)
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
assets = self.query_asset_util.get_direct_granted_nodes_assets()
else:
assets = self.query_asset_util.get_all_granted_assets()
assets = assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform')
return nodes, assets
class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi):
""" 用户授权的节点的子节点与资产树 """
def get_nodes_assets(self):
nodes = PermNode.objects.none()
assets = Asset.objects.none()
query_node_util = UserGrantedNodesQueryUtils(self.user)
query_asset_util = UserGrantedAssetsQueryUtils(self.user)
node_key = self.query_node_key
if not node_key:
nodes = query_node_util.get_top_level_nodes()
elif node_key == PermNode.UNGROUPED_NODE_KEY:
assets = query_asset_util.get_ungroup_assets()
elif node_key == PermNode.FAVORITE_NODE_KEY:
assets = query_asset_util.get_favorite_assets()
else:
nodes = query_node_util.get_node_children(node_key)
assets = query_asset_util.get_node_assets(node_key)
assets = assets.prefetch_related('platform')
return nodes, assets
@lazyproperty
def query_node_key(self):
node_key = self.request.query_params.get('key', None)
if node_key is not None:
return node_key
node_id = self.request.query_params.get('id', None)
node = get_object_or_none(Node, id=node_id)
node_key = getattr(node, 'key', None)
return node_key
@lazyproperty
def node_key_for_serializer_assets(self):
return self.query_node_key

View File

@ -16,7 +16,7 @@ class GrantedAssetPaginationBase(AssetPaginationBase):
self._user = view.user self._user = view.user
class NodeGrantedAssetPagination(GrantedAssetPaginationBase): class NodePermedAssetPagination(GrantedAssetPaginationBase):
def get_count_from_nodes(self, queryset): def get_count_from_nodes(self, queryset):
node = getattr(self._view, 'pagination_node', None) node = getattr(self._view, 'pagination_node', None)
if node: if node:
@ -29,7 +29,7 @@ class NodeGrantedAssetPagination(GrantedAssetPaginationBase):
return None return None
class AllGrantedAssetPagination(GrantedAssetPaginationBase): class AllPermedAssetPagination(GrantedAssetPaginationBase):
def get_count_from_nodes(self, queryset): def get_count_from_nodes(self, queryset):
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
return None return None

View File

@ -11,12 +11,12 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField
from perms.serializers.permission import ActionChoicesField from perms.serializers.permission import ActionChoicesField
__all__ = [ __all__ = [
'NodeGrantedSerializer', 'AssetGrantedSerializer', 'NodeGrantedSerializer', 'AssetPermedSerializer',
'AccountsPermedSerializer' 'AccountsPermedSerializer'
] ]
class AssetGrantedSerializer(serializers.ModelSerializer): class AssetPermedSerializer(serializers.ModelSerializer):
""" 被授权资产的数据结构 """ """ 被授权资产的数据结构 """
platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))

View File

@ -4,39 +4,38 @@ from .. import api
user_permission_urlpatterns = [ user_permission_urlpatterns = [
# <str:user> such as: my | self | user.id # <str:user> such as: my | self | user.id
# assets # assets
path('<str:user>/assets/', api.UserAllPermedAssetsApi.as_view(), path('<str:user>/assets/', api.UserAllPermedAssetsApi.as_view(),
name='user-assets'), name='user-all-assets'),
path('<str:user>/assets/tree/', api.UserDirectPermedAssetsAsTreeApi.as_view(), path('<str:user>/nodes/ungrouped/assets/', api.UserDirectPermedAssetsApi.as_view(),
name='user-assets-as-tree'), name='user-direct-assets'),
path('<str:user>/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(), path('<str:user>/nodes/favorite/assets/', api.UserFavoriteAssetsApi.as_view(),
name='user-ungroup-assets-as-tree'), name='user-favorite-assets'),
# nodes
path('<str:user>/nodes/', api.UserPermedNodesApi.as_view(),
name='user-nodes'),
path('<str:user>/nodes/tree/', api.UserPermedNodesAsTreeApi.as_view(),
name='user-nodes-as-tree'),
path('<str:user>/nodes/children/', api.UserPermedNodeChildrenApi.as_view(),
name='user-nodes-children'),
path('<str:user>/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(),
name='user-nodes-children-as-tree'),
# node-assets
path('<str:user>/nodes/<uuid:node_id>/assets/', api.UserPermedNodeAssetsApi.as_view(), path('<str:user>/nodes/<uuid:node_id>/assets/', api.UserPermedNodeAssetsApi.as_view(),
name='user-node-assets'), name='user-node-assets'),
path('<str:user>/nodes/ungrouped/assets/', api.UserDirectPermedAssetsApi.as_view(),
name='user-ungrouped-assets'),
path('<str:user>/nodes/favorite/assets/', api.UserFavoriteAssetsApi.as_view(),
name='user-ungrouped-assets'),
# nodes
path('<str:user>/nodes/', api.UserAllPermedNodesApi.as_view(),
name='user-all-nodes'),
path('<str:user>/nodes/children/', api.UserPermedNodeChildrenApi.as_view(),
name='user-node-children'),
# tree-asset
path('<str:user>/assets/tree/', api.UserDirectPermedAssetsAsTreeApi.as_view(),
name='user-direct-assets-as-tree'),
path('<str:user>/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(),
name='user-ungroup-assets-as-tree'),
# tree-node
path('<str:user>/nodes/tree/', api.UserAllPermedNodesAsTreeApi.as_view(),
name='user-all-nodes-as-tree'),
path('<str:user>/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(),
name='user-node-children-as-tree'),
# tree-node-with-asset
path('<str:user>/nodes/children-with-assets/tree/', path('<str:user>/nodes/children-with-assets/tree/',
api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(),
name='user-nodes-children-with-assets-as-tree'), name='user-node-children-with-assets-as-tree'),
path('<str:user>/nodes-with-assets/tree/', api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
name='my-nodes-with-assets-as-tree'),
# accounts # accounts
path('<str:user>/assets/<uuid:asset_id>/accounts/', api.UserPermedAssetAccountsApi.as_view(), path('<str:user>/assets/<uuid:asset_id>/accounts/', api.UserPermedAssetAccountsApi.as_view(),

View File

@ -4,6 +4,8 @@ from perms.models import AssetPermission
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['AssetPermissionUtil']
class AssetPermissionUtil(object): class AssetPermissionUtil(object):
""" 资产授权相关的方法工具 """ """ 资产授权相关的方法工具 """