perf: perm account 返回 alias

pull/9218/head
ibuler 2022-12-15 16:02:34 +08:00
parent acfce4961c
commit cb7b31e8b8
10 changed files with 278 additions and 217 deletions

View File

@ -1,11 +1,12 @@
from .mixin import *
from .category import *
from .platform import *
from .asset import *
from .label import *
from .account import *
from .node import *
from .domain import *
from .asset import *
from .automations import *
from .gathered_user import *
from .category import *
from .domain import *
from .favorite_asset import *
from .gathered_user import *
from .label import *
from .mixin import *
from .node import *
from .platform import *
from .tree import *

View File

@ -1,43 +1,37 @@
# ~*~ coding: utf-8 ~*~
from functools import partial
from collections import namedtuple, defaultdict
from django.core.exceptions import PermissionDenied
from functools import partial
from rest_framework import status
from rest_framework.generics import get_object_or_404
from rest_framework.serializers import ValidationError
from rest_framework.response import Response
from rest_framework.decorators import action
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import m2m_changed
from django.utils.translation import ugettext_lazy as _
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from common.const.http import POST
from common.exceptions import SomeoneIsDoingThis
from common.const.signals import PRE_REMOVE, POST_REMOVE
from common.mixins.api import SuggestionMixin
from assets.models import Asset
from common.const.http import POST
from common.const.signals import PRE_REMOVE, POST_REMOVE
from common.exceptions import SomeoneIsDoingThis
from common.mixins.api import SuggestionMixin
from common.utils import get_logger
from common.tree import TreeNodeSerializer
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
from .. import serializers
from ..models import Node
from ..tasks import (
update_node_assets_hardware_info_manual,
test_node_assets_connectivity_manual,
check_node_assets_amount_task
)
from .. import serializers
from ..const import AllTypes
from .mixin import SerializeToTreeNodeMixin
from assets.locks import NodeAddChildrenLock
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi',
'NodeAddChildrenApi', 'NodeListAsTreeApi', 'NodeChildrenAsTreeApi',
'NodeTaskCreateApi', 'CategoryTreeApi',
'NodeViewSet', 'NodeAssetsApi', 'NodeAddAssetsApi',
'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi',
'NodeAddChildrenApi', 'NodeTaskCreateApi',
]
@ -74,153 +68,6 @@ class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet):
return super().destroy(request, *args, **kwargs)
class NodeListAsTreeApi(generics.ListAPIView):
"""
获取节点列表树
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
serializer_class = TreeNodeSerializer
@staticmethod
def to_tree_queryset(queryset):
queryset = [node.as_tree_node() for node in queryset]
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.to_tree_queryset(queryset)
return queryset
class NodeChildrenApi(generics.ListCreateAPIView):
serializer_class = serializers.NodeSerializer
search_fields = ('value',)
instance = None
is_initial = False
def initial(self, request, *args, **kwargs):
self.instance = self.get_object()
return super().initial(request, *args, **kwargs)
def perform_create(self, serializer):
with NodeAddChildrenLock(self.instance):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if not value:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value
serializer.instance = node
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
key = self.request.query_params.get("key")
if not pk and not key:
self.is_initial = True
if current_org.is_root():
node = None
else:
node = Node.org_root()
return node
if pk:
node = get_object_or_404(Node, pk=pk)
else:
node = get_object_or_404(Node, key=key)
return node
def get_org_root_queryset(self, query_all):
if query_all:
return Node.objects.all()
else:
return Node.org_root_nodes()
def get_queryset(self):
query_all = self.request.query_params.get("all", "0") == "all"
if self.is_initial and current_org.is_root():
return self.get_org_root_queryset(query_all)
if self.is_initial:
with_self = True
else:
with_self = False
if not self.instance:
return Node.objects.none()
if query_all:
queryset = self.instance.get_all_children(with_self=with_self)
else:
queryset = self.instance.get_children(with_self=with_self)
return queryset
class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
"""
节点子节点作为树返回
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
def filter_queryset(self, queryset):
if not self.request.GET.get('search'):
return queryset
queryset = super().filter_queryset(queryset)
queryset = self.model.get_ancestor_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs):
nodes = self.filter_queryset(self.get_queryset()).order_by('value')
nodes = self.serialize_nodes(nodes, with_asset_amount=True)
assets = self.get_assets()
data = [*nodes, *assets]
return Response(data=data)
def get_assets(self):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not self.instance or not include_assets:
return []
assets = self.instance.get_assets().only(
"id", "name", "address", "platform_id",
"org_id", "is_active",
).prefetch_related('platform')
return self.serialize_assets(assets, self.instance.key)
class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
serializer_class = TreeNodeSerializer
def check_permissions(self, request):
if not request.user.has_perm('assets.view_asset'):
raise PermissionDenied
return True
def list(self, request, *args, **kwargs):
nodes = AllTypes.to_tree_nodes()
serializer = self.get_serializer(nodes, many=True)
return Response(data=serializer.data)
class NodeAssetsApi(generics.ListAPIView):
serializer_class = serializers.AssetSerializer

173
apps/assets/api/tree.py Normal file
View File

@ -0,0 +1,173 @@
# ~*~ coding: utf-8 ~*~
from django.core.exceptions import PermissionDenied
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from assets.locks import NodeAddChildrenLock
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from orgs.mixins import generics
from orgs.utils import current_org
from .mixin import SerializeToTreeNodeMixin
from .. import serializers
from ..const import AllTypes
from ..models import Node
logger = get_logger(__file__)
__all__ = [
'NodeChildrenApi',
'NodeListAsTreeApi',
'NodeChildrenAsTreeApi',
'CategoryTreeApi',
]
class NodeListAsTreeApi(generics.ListAPIView):
"""
获取节点列表树
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
serializer_class = TreeNodeSerializer
@staticmethod
def to_tree_queryset(queryset):
queryset = [node.as_tree_node() for node in queryset]
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.to_tree_queryset(queryset)
return queryset
class NodeChildrenApi(generics.ListCreateAPIView):
serializer_class = serializers.NodeSerializer
search_fields = ('value',)
instance = None
is_initial = False
def initial(self, request, *args, **kwargs):
self.instance = self.get_object()
return super().initial(request, *args, **kwargs)
def perform_create(self, serializer):
with NodeAddChildrenLock(self.instance):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if not value:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value
serializer.instance = node
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
key = self.request.query_params.get("key")
if not pk and not key:
self.is_initial = True
if current_org.is_root():
node = None
else:
node = Node.org_root()
return node
if pk:
node = get_object_or_404(Node, pk=pk)
else:
node = get_object_or_404(Node, key=key)
return node
def get_org_root_queryset(self, query_all):
if query_all:
return Node.objects.all()
else:
return Node.org_root_nodes()
def get_queryset(self):
query_all = self.request.query_params.get("all", "0") == "all"
if self.is_initial and current_org.is_root():
return self.get_org_root_queryset(query_all)
if self.is_initial:
with_self = True
else:
with_self = False
if not self.instance:
return Node.objects.none()
if query_all:
queryset = self.instance.get_all_children(with_self=with_self)
else:
queryset = self.instance.get_children(with_self=with_self)
return queryset
class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
"""
节点子节点作为树返回
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
def filter_queryset(self, queryset):
if not self.request.GET.get('search'):
return queryset
queryset = super().filter_queryset(queryset)
queryset = self.model.get_ancestor_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs):
nodes = self.filter_queryset(self.get_queryset()).order_by('value')
nodes = self.serialize_nodes(nodes, with_asset_amount=True)
assets = self.get_assets()
data = [*nodes, *assets]
return Response(data=data)
def get_assets(self):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not self.instance or not include_assets:
return []
assets = self.instance.get_assets().only(
"id", "name", "address", "platform_id",
"org_id", "is_active",
).prefetch_related('platform')
return self.serialize_assets(assets, self.instance.key)
class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
serializer_class = TreeNodeSerializer
def check_permissions(self, request):
if not request.user.has_perm('assets.view_asset'):
raise PermissionDenied
return True
def list(self, request, *args, **kwargs):
if request.query_params.get('key'):
nodes = []
else:
nodes = AllTypes.to_tree_nodes()
serializer = self.get_serializer(nodes, many=True)
return Response(data=serializer.data)

View File

@ -1,14 +1,14 @@
from collections import defaultdict
from copy import deepcopy
from common.db.models import ChoicesMixin
from common.tree import TreeNode
from .category import Category
from .host import HostTypes
from .device import DeviceTypes
from .database import DatabaseTypes
from .web import WebTypes
from .cloud import CloudTypes
from .database import DatabaseTypes
from .device import DeviceTypes
from .host import HostTypes
from .web import WebTypes
class AllTypes(ChoicesMixin):
@ -54,7 +54,7 @@ class AllTypes(ChoicesMixin):
item_name = item.replace('_enabled', '')
methods = filter_platform_methods(category, tp, item_name)
methods = [{'name': m['name'], 'id': m['id']} for m in methods]
automation_methods[item_name+'_methods'] = methods
automation_methods[item_name + '_methods'] = methods
automation.update(automation_methods)
constraints['automation'] = automation
return constraints
@ -124,7 +124,7 @@ class AllTypes(ChoicesMixin):
@staticmethod
def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None):
node = TreeNode(**{
'id': choice.name,
'id': pid + '_' + choice.name,
'name': choice.label,
'title': choice.label,
'pId': pid,
@ -135,16 +135,57 @@ class AllTypes(ChoicesMixin):
node.meta = meta
return node
@classmethod
def platform_to_node(cls, p, pid):
node = TreeNode(**{
'id': '{}'.format(p.id),
'name': p.name,
'title': p.name,
'pId': pid,
'isParent': True,
'meta': {
'type': 'platform'
}
})
return node
@classmethod
def to_tree_nodes(cls):
root = TreeNode(id='ROOT', name='类型节点', title='类型节点')
from ..models import Asset, Platform
asset_platforms = Asset.objects.all().values_list('platform_id', flat=True)
platform_count = defaultdict(int)
for platform_id in asset_platforms:
platform_count[platform_id] += 1
category_type_mapper = defaultdict(int)
platforms = Platform.objects.all()
tp_platforms = defaultdict(list)
for p in platforms:
category_type_mapper[p.category + '_' + p.type] += platform_count[p.id]
category_type_mapper[p.category] += platform_count[p.id]
tp_platforms[p.category + '_' + p.type].append(p)
root = TreeNode(id='ROOT', name='所有类型', title='所有类型', open=True, isParent=True)
nodes = [root]
for category, types in cls.category_types():
category_node = cls.choice_to_node(category, 'ROOT', meta={'type': 'category'})
meta = {'type': 'category', 'category': category.value}
category_node = cls.choice_to_node(category, 'ROOT', meta=meta)
category_count = category_type_mapper.get(category, 0)
category_node.name += f'({category_count})'
nodes.append(category_node)
for tp in types:
tp_node = cls.choice_to_node(tp, category_node.id, meta={'type': 'type'})
meta = {'type': 'type', 'category': category.value, '_type': tp.value}
tp_node = cls.choice_to_node(tp, category_node.id, opened=False, meta=meta)
tp_count = category_type_mapper.get(category + '_' + tp, 0)
tp_node.name += f'({tp_count})'
nodes.append(tp_node)
for p in tp_platforms.get(category + '_' + tp, []):
platform_node = cls.platform_to_node(p, tp_node.id)
platform_node.name += f'({platform_count.get(p.id, 0)})'
nodes.append(platform_node)
return nodes
@classmethod
@ -253,8 +294,3 @@ class AllTypes(ChoicesMixin):
print("\t- Update platform: {}".format(platform.name))
platform_data = cls.get_type_default_platform(platform.category, platform.type)
cls.create_or_update_by_platform_data(platform.name, platform_data)

View File

@ -3,7 +3,6 @@ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords
from common.utils import lazyproperty
from .base import AbsConnectivity, BaseAccount
__all__ = ['Account', 'AccountTemplate']
@ -74,6 +73,12 @@ class Account(AbsConnectivity, BaseAccount):
def platform(self):
return self.asset.platform
@lazyproperty
def alias(self):
if self.username.startswith('@'):
return self.username
return self.name
def __str__(self):
return '{}'.format(self.username)

View File

@ -266,7 +266,7 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
permed_account = util.validate_permission(user, asset, account_name)
if not permed_account or not permed_account.actions:
msg = 'user `{}` not has asset `{}` permission for login `{}`'.format(
msg = 'user `{}` not has asset `{}` permission for account `{}`'.format(
user, asset, account_name
)
raise PermissionDenied(msg)

View File

@ -6,14 +6,10 @@ from django.db.models import Q
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from common.utils import lazyproperty
from perms.models import AssetPermission
from assets.models import Asset, Node
from . import user_permission as uapi
from perms import serializers
from perms.utils import PermAccountUtil
from assets.api.mixin import SerializeToTreeNodeMixin
from users.models import UserGroup
from assets.models import Asset, Node
from perms import serializers
from perms.models import AssetPermission
__all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
@ -101,11 +97,11 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
granted_node_q |= Q(nodes__key=_key)
granted_asset_q = (
Q(granted_by_permissions__id__in=asset_perm_ids) &
(
Q(nodes__key__startswith=f'{node.key}:') |
Q(nodes__key=node.key)
)
Q(granted_by_permissions__id__in=asset_perm_ids) &
(
Q(nodes__key__startswith=f'{node.key}:') |
Q(nodes__key=node.key)
)
)
assets = Asset.objects.filter(
@ -115,7 +111,7 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
class UserGroupGrantedNodesApi(ListAPIView):
serializer_class = serializers.NodeGrantedSerializer
serializer_class = serializers.NodePermedSerializer
rbac_perms = {
'list': 'perms.view_usergroupassets',
}

View File

@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
#
import abc
from rest_framework.generics import ListAPIView
from assets.models import Node
from common.utils import get_logger, lazyproperty
from perms import serializers
from perms.utils.user_permission import UserGrantedNodesQueryUtils
from common.utils import get_logger, lazyproperty
from .mixin import SelfOrPKUserMixin
logger = get_logger(__name__)
@ -19,7 +19,7 @@ __all__ = [
class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView):
serializer_class = serializers.NodeGrantedSerializer
serializer_class = serializers.NodePermedSerializer
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
@ -37,12 +37,14 @@ class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView):
class UserAllPermedNodesApi(BaseUserPermedNodesApi):
""" 用户授权的节点 """
def get_nodes(self):
return self.query_node_util.get_whole_tree_nodes()
class UserPermedNodeChildrenApi(BaseUserPermedNodesApi):
""" 用户授权的节点下的子节点 """
def get_nodes(self):
key = self.request.query_params.get('key')
nodes = self.query_node_util.get_node_children(key)

View File

@ -11,7 +11,7 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField
from perms.serializers.permission import ActionChoicesField
__all__ = [
'NodeGrantedSerializer', 'AssetPermedSerializer',
'NodePermedSerializer', 'AssetPermedSerializer',
'AccountsPermedSerializer'
]
@ -26,15 +26,14 @@ class AssetPermedSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
only_fields = [
"id", "name", "address",
'domain', 'platform',
"id", "name", "address", 'domain', 'platform',
"comment", "org_id", "is_active",
]
fields = only_fields + ['protocols', 'category', 'type', 'specific'] + ['org_name']
read_only_fields = fields
class NodeGrantedSerializer(serializers.ModelSerializer):
class NodePermedSerializer(serializers.ModelSerializer):
class Meta:
model = Node
fields = [
@ -48,6 +47,8 @@ class AccountsPermedSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'name', 'has_username', 'username',
'has_secret', 'secret_type', 'actions']
fields = [
'alias', 'name', 'username', 'has_username',
'has_secret', 'secret_type', 'actions'
]
read_only_fields = fields

View File

@ -16,7 +16,7 @@ class PermAccountUtil(AssetPermissionUtil):
:param account_name: 可能是 @USER @INPUT 字符串
"""
permed_accounts = self.get_permed_accounts_for_user(user, asset)
accounts_mapper = {account.name: account for account in permed_accounts}
accounts_mapper = {account.alias: account for account in permed_accounts}
account = accounts_mapper.get(account_name)
return account