mirror of https://github.com/jumpserver/jumpserver
Dev beta (#3167)
* [Update] 添加loading * [Update] 修改避免游离资产 * [Update] 修改nodes * [Update] 修改支持未分组pull/3171/head
parent
1fe18e8073
commit
8cd8f41cb0
|
@ -13,8 +13,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import time
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
|
|
|
@ -7,5 +7,7 @@ class AssetsConfig(AppConfig):
|
|||
name = 'assets'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
super().ready()
|
||||
from . import signals_handler
|
||||
from .models import Node
|
||||
Node.initial_some_nodes()
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.utils.translation import ugettext
|
|||
from django.core.cache import cache
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from orgs.utils import set_current_org, get_current_org
|
||||
from orgs.utils import set_current_org, get_current_org, tmp_to_root_org, tmp_to_org
|
||||
from orgs.models import Organization
|
||||
|
||||
|
||||
|
@ -25,37 +25,42 @@ class NodeQuerySet(models.QuerySet):
|
|||
|
||||
class TreeMixin:
|
||||
tree_created_time = None
|
||||
tree_updated_time_cache_key = 'NODE_TREE_CREATED_AT'
|
||||
tree_update_time_cache_time = 3600
|
||||
tree_updated_time_cache_key = 'NODE_TREE_UPDATED_AT'
|
||||
tree_cache_time = 3600
|
||||
tree_assets_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT'
|
||||
tree_assets_created_time = None
|
||||
_tree_service = None
|
||||
|
||||
@classmethod
|
||||
def tree(cls):
|
||||
# Todo: 有待优化, 因为每次刷新都会导致其他节点的tree失效 完成
|
||||
# TOdo: 游离的资产,在树上显示的数量不对
|
||||
# Todo: ungroup node
|
||||
# Todo: api key页面有bug 完成
|
||||
from ..utils import TreeService
|
||||
tree_updated_time = cache.get(cls.tree_updated_time_cache_key, 0)
|
||||
if not cls.tree_created_time or \
|
||||
tree_updated_time > cls.tree_created_time:
|
||||
print("New tree")
|
||||
tree = TreeService.new()
|
||||
cls.tree_created_time = time.time()
|
||||
cls.tree_assets_created_time = time.time()
|
||||
cls._tree_service = tree
|
||||
return tree
|
||||
node_assets_updated_time = cache.get(cls.tree_assets_cache_key, 0)
|
||||
if not cls.tree_assets_created_time or \
|
||||
node_assets_updated_time > cls.tree_assets_created_time:
|
||||
cls._tree_service.init_assets_async()
|
||||
return cls._tree_service
|
||||
|
||||
@classmethod
|
||||
def expire_cache_tree(cls):
|
||||
def refresh_tree(cls):
|
||||
key = cls.tree_updated_time_cache_key
|
||||
ttl = cls.tree_update_time_cache_time
|
||||
ttl = cls.tree_cache_time
|
||||
value = time.time()
|
||||
cache.set(key, value, ttl)
|
||||
|
||||
@classmethod
|
||||
def refresh_tree(cls):
|
||||
cls.expire_cache_tree()
|
||||
def refresh_node_assets(cls):
|
||||
key = cls.tree_assets_cache_key
|
||||
ttl = cls.tree_cache_time
|
||||
value = time.time()
|
||||
cache.set(key, value, ttl)
|
||||
|
||||
@property
|
||||
def _tree(self):
|
||||
|
@ -183,6 +188,31 @@ class FamilyMixin:
|
|||
key_list.pop()
|
||||
return keys
|
||||
|
||||
def get_next_child_key(self):
|
||||
mark = self.child_mark
|
||||
self.child_mark += 1
|
||||
self.save()
|
||||
return "{}:{}".format(self.key, mark)
|
||||
|
||||
def get_next_child_preset_name(self):
|
||||
name = ugettext("New node")
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_children()
|
||||
if child.value.startswith(name)
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values) + 1 if values else 1
|
||||
return '{} {}'.format(name, count)
|
||||
|
||||
def create_child(self, value, _id=None):
|
||||
with transaction.atomic():
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(
|
||||
id=_id, key=child_key, value=value
|
||||
)
|
||||
return child
|
||||
|
||||
|
||||
class FullValueMixin:
|
||||
_full_value = None
|
||||
|
@ -246,7 +276,85 @@ class NodeAssetsMixin:
|
|||
return Asset.objects.filter(nodes__key__regex=pattern)
|
||||
|
||||
|
||||
class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
|
||||
class SomeNodesMixin:
|
||||
key = ''
|
||||
default_key = '1'
|
||||
default_value = 'Default'
|
||||
ungrouped_key = '-10'
|
||||
ungrouped_value = _('ungrouped')
|
||||
empty_key = '-11'
|
||||
empty_value = _("empty")
|
||||
|
||||
def is_default_node(self):
|
||||
return self.key == self.default_key
|
||||
|
||||
def is_org_root(self):
|
||||
if self.key.isdigit():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def create_org_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
ori_org = get_current_org()
|
||||
with transaction.atomic():
|
||||
if not ori_org.is_real():
|
||||
return cls.default_node()
|
||||
set_current_org(Organization.root())
|
||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
|
||||
if not org_nodes_roots_keys:
|
||||
org_nodes_roots_keys = ['1']
|
||||
key = max([int(k) for k in org_nodes_roots_keys])
|
||||
key = str(key + 1) if key != 0 else '2'
|
||||
set_current_org(ori_org)
|
||||
root = cls.objects.create(key=key, value=ori_org.name)
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
def org_root(cls):
|
||||
root = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
if root:
|
||||
return root[0]
|
||||
else:
|
||||
return cls.create_org_root_node()
|
||||
|
||||
@classmethod
|
||||
def ungrouped_node(cls):
|
||||
with tmp_to_org(Organization.system()):
|
||||
defaults = {'value': cls.ungrouped_key}
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.ungrouped_key
|
||||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def empty_node(cls):
|
||||
with tmp_to_org(Organization.system()):
|
||||
defaults = {'value': cls.empty_value}
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.empty_key
|
||||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def default_node(cls):
|
||||
with tmp_to_org(Organization.default()):
|
||||
defaults = {'value': cls.default_value}
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.default_key,
|
||||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def initial_some_nodes(cls):
|
||||
cls.default_node()
|
||||
cls.empty_node()
|
||||
cls.ungrouped_node()
|
||||
|
||||
|
||||
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
|
@ -290,75 +398,13 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
|
|||
def level(self):
|
||||
return len(self.key.split(':'))
|
||||
|
||||
def get_next_child_key(self):
|
||||
mark = self.child_mark
|
||||
self.child_mark += 1
|
||||
self.save()
|
||||
return "{}:{}".format(self.key, mark)
|
||||
|
||||
def get_next_child_preset_name(self):
|
||||
name = ugettext("New node")
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_children()
|
||||
if child.value.startswith(name)
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values) + 1 if values else 1
|
||||
return '{} {}'.format(name, count)
|
||||
|
||||
def create_child(self, value, _id=None):
|
||||
with transaction.atomic():
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(
|
||||
id=_id, key=child_key, value=value
|
||||
)
|
||||
return child
|
||||
|
||||
@classmethod
|
||||
def refresh_nodes(cls):
|
||||
cls.refresh_tree()
|
||||
|
||||
def is_default_node(self):
|
||||
return self.key == '1'
|
||||
|
||||
def is_org_root(self):
|
||||
if self.key.isdigit():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def create_org_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
ori_org = get_current_org()
|
||||
with transaction.atomic():
|
||||
if not ori_org.is_real():
|
||||
return cls.default_node()
|
||||
set_current_org(Organization.root())
|
||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
|
||||
if not org_nodes_roots_keys:
|
||||
org_nodes_roots_keys = ['1']
|
||||
key = max([int(k) for k in org_nodes_roots_keys])
|
||||
key = str(key + 1) if key != 0 else '2'
|
||||
set_current_org(ori_org)
|
||||
root = cls.objects.create(key=key, value=ori_org.name)
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
def org_root(cls):
|
||||
root = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
if root:
|
||||
return root[0]
|
||||
else:
|
||||
return cls.create_org_root_node()
|
||||
|
||||
@classmethod
|
||||
def default_node(cls):
|
||||
defaults = {'value': 'Default'}
|
||||
obj, created = cls.objects.get_or_create(defaults=defaults, key='1')
|
||||
return obj
|
||||
def refresh_assets(cls):
|
||||
cls.refresh_node_assets()
|
||||
|
||||
def as_tree_node(self):
|
||||
from common.tree import TreeNode
|
||||
|
|
|
@ -14,7 +14,9 @@ __all__ = [
|
|||
|
||||
class NodeSerializer(BulkOrgResourceModelSerializer):
|
||||
name = serializers.ReadOnlyField(source='value')
|
||||
value = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("value"))
|
||||
value = serializers.CharField(
|
||||
required=False, allow_blank=True, allow_null=True, label=_("value")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
#
|
||||
from collections import defaultdict
|
||||
from django.db.models.signals import (
|
||||
post_save, m2m_changed, pre_delete, pre_save, pre_init, post_init
|
||||
post_save, m2m_changed, post_delete
|
||||
)
|
||||
from django.db.models.aggregates import Count
|
||||
from django.dispatch import receiver
|
||||
|
||||
from common.utils import get_logger
|
||||
|
@ -29,29 +30,36 @@ def test_asset_conn_on_created(asset):
|
|||
test_asset_connectivity_util.delay([asset])
|
||||
|
||||
|
||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
@receiver(post_save, sender=Asset)
|
||||
@on_transaction_commit
|
||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
"""
|
||||
当资产创建时,更新硬件信息,更新可连接性
|
||||
确保资产必须属于一个节点
|
||||
"""
|
||||
if created:
|
||||
logger.info("Asset `{}` create signal received".format(instance))
|
||||
logger.info("Asset create signal recv: {}".format(instance))
|
||||
|
||||
# 获取资产硬件信息
|
||||
update_asset_hardware_info_on_created(instance)
|
||||
test_asset_conn_on_created(instance)
|
||||
|
||||
# 确保资产存在一个节点
|
||||
has_node = instance.nodes.all().exists()
|
||||
if not has_node:
|
||||
instance.nodes.add(Node.org_root())
|
||||
|
||||
@receiver(pre_delete, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
|
||||
@receiver(post_delete, sender=Asset)
|
||||
def on_asset_delete(sender, instance=None, **kwargs):
|
||||
"""
|
||||
当资产删除时,刷新节点,节点中存在节点和资产的关系
|
||||
"""
|
||||
Node.refresh_nodes()
|
||||
logger.debug("Asset delete signal recv: {}".format(instance))
|
||||
Node.refresh_assets()
|
||||
|
||||
|
||||
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
||||
@receiver(post_save, sender=SystemUser, dispatch_uid="jms")
|
||||
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
||||
"""
|
||||
当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上,
|
||||
|
@ -60,61 +68,126 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
|||
关联到上面
|
||||
"""
|
||||
if instance and not created:
|
||||
logger.info("System user `{}` update signal received".format(instance))
|
||||
logger.info("System user update signal recv: {}".format(instance))
|
||||
assets = instance.assets.all().valid()
|
||||
push_system_user_to_assets.delay(instance, assets)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.assets.through, dispatch_uid="my_unique_identifier")
|
||||
def on_system_user_assets_change(sender, instance=None, **kwargs):
|
||||
@receiver(m2m_changed, sender=SystemUser.assets.through)
|
||||
def on_system_user_assets_change(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
|
||||
"""
|
||||
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
|
||||
"""
|
||||
if instance and kwargs["action"] == "post_add":
|
||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
push_system_user_to_assets.delay(instance, assets)
|
||||
if action != "post_add":
|
||||
return
|
||||
logger.debug("System user assets change signal recv: {}".format(instance))
|
||||
queryset = model.objects.filter(pk__in=pk_set)
|
||||
if model == Asset:
|
||||
system_users = [instance]
|
||||
assets = queryset
|
||||
else:
|
||||
system_users = queryset
|
||||
assets = [instance]
|
||||
for system_user in system_users:
|
||||
push_system_user_to_assets.delay(system_user, assets)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.nodes.through, dispatch_uid="my_unique_identifier")
|
||||
def on_system_user_nodes_change(sender, instance=None, **kwargs):
|
||||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
||||
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
|
||||
"""
|
||||
当系统用户和节点关系发生变化时,应该将节点关联到新的系统用户上
|
||||
当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上
|
||||
"""
|
||||
if instance and kwargs["action"] == "post_add":
|
||||
logger.info("System user `{}` nodes update signal received".format(instance))
|
||||
nodes_keys = kwargs['model'].objects.filter(
|
||||
pk__in=kwargs['pk_set']
|
||||
).values_list('key', flat=True)
|
||||
assets = Node.get_nodes_all_assets(nodes_keys)
|
||||
instance.assets.add(*tuple(assets))
|
||||
if action != "post_add":
|
||||
return
|
||||
logger.info("System user `{}` nodes update signal recv".format(instance))
|
||||
|
||||
queryset = model.objects.filter(pk__in=pk_set)
|
||||
if model == Node:
|
||||
nodes_keys = queryset.values_list('key', flat=True)
|
||||
system_users = [instance]
|
||||
else:
|
||||
nodes_keys = [instance.key]
|
||||
system_users = queryset
|
||||
assets = Node.get_nodes_all_assets(nodes_keys)
|
||||
for system_user in system_users:
|
||||
system_user.assets.add(*tuple(assets))
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through, dispatch_uid="my_unique_identifier")
|
||||
def on_asset_nodes_changed(sender, instance=None, **kwargs):
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_nodes_change(sender, instance=None, action='', **kwargs):
|
||||
"""
|
||||
资产节点发生变化时,刷新节点
|
||||
"""
|
||||
if action.startswith('post'):
|
||||
logger.debug("Asset nodes change signal recv: {}".format(instance))
|
||||
Node.refresh_assets()
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_nodes_add(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
|
||||
"""
|
||||
当资产的节点发生变化时,或者 当节点的资产关系发生变化时,
|
||||
节点下新增的资产,添加到节点关联的系统用户中
|
||||
并刷新节点
|
||||
"""
|
||||
if isinstance(instance, Asset):
|
||||
logger.debug("Asset nodes change signal received: {}".format(instance))
|
||||
# 节点资产发生变化时,将资产关联到节点关联的系统用户
|
||||
if kwargs['action'] == 'post_add':
|
||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
system_users_assets = defaultdict(set)
|
||||
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
||||
for system_user in system_users:
|
||||
system_users_assets[system_user].add(instance)
|
||||
for system_user, assets in system_users_assets.items():
|
||||
system_user.assets.add(*tuple(assets))
|
||||
if isinstance(instance, Node):
|
||||
logger.debug("Node assets change signal received: {}".format(instance))
|
||||
|
||||
Node.refresh_nodes()
|
||||
if action != "post_add":
|
||||
return
|
||||
logger.debug("Assets node add signal recv: {}".format(action))
|
||||
queryset = model.objects.filter(pk__in=pk_set)
|
||||
if model == Node:
|
||||
nodes = queryset
|
||||
assets = [instance]
|
||||
else:
|
||||
nodes = [instance]
|
||||
assets = queryset
|
||||
# 节点资产发生变化时,将资产关联到节点关联的系统用户, 只关注新增的
|
||||
system_users_assets = defaultdict(set)
|
||||
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
||||
for system_user in system_users:
|
||||
system_users_assets[system_user].update(set(assets))
|
||||
for system_user, _assets in system_users_assets.items():
|
||||
system_user.assets.add(*tuple(_assets))
|
||||
|
||||
|
||||
@receiver(post_save, sender=Node)
|
||||
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_nodes_remove(sender, instance=None, action='', model=None,
|
||||
pk_set=None, **kwargs):
|
||||
|
||||
"""
|
||||
监控资产删除节点关系, 或节点删除资产,避免产生游离资产
|
||||
"""
|
||||
if action not in ["post_remove", "pre_clear", "post_clear"]:
|
||||
return
|
||||
if action == "pre_clear":
|
||||
if model == Node:
|
||||
instance._nodes = list(instance.nodes.all())
|
||||
else:
|
||||
instance._assets = list(instance.assets.all())
|
||||
return
|
||||
logger.debug("Assets node remove signal recv: {}".format(action))
|
||||
if action == "post_remove":
|
||||
queryset = model.objects.filter(pk__in=pk_set)
|
||||
else:
|
||||
if model == Node:
|
||||
queryset = instance._nodes
|
||||
else:
|
||||
queryset = instance._assets
|
||||
if model == Node:
|
||||
assets = [instance]
|
||||
else:
|
||||
assets = queryset
|
||||
if isinstance(assets, list):
|
||||
assets_not_has_node = []
|
||||
for asset in assets:
|
||||
if asset.nodes.all().count() == 0:
|
||||
assets_not_has_node.append(asset.id)
|
||||
else:
|
||||
assets_not_has_node = assets.annotate(nodes_count=Count('nodes'))\
|
||||
.filter(nodes_count=0).values_list('id', flat=True)
|
||||
Node.org_root().assets.add(*tuple(assets_not_has_node))
|
||||
|
||||
|
||||
@receiver([post_save, post_delete], sender=Node)
|
||||
def on_node_update_or_created(sender, **kwargs):
|
||||
# 刷新节点
|
||||
Node.refresh_nodes()
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager" id="tree-node-id">
|
||||
<div id="{% block treeID %}nodeTree{% endblock %}" class="ztree">
|
||||
{% trans 'Loading' %} ...
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
|
|
@ -124,7 +124,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'assets/_node_tree.html' %}
|
||||
{% include 'assets/_asset_update_modal.html' %}
|
||||
{% include 'assets/_asset_import_modal.html' %}
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
|
|
|
@ -59,6 +59,8 @@ class TreeService(Tree):
|
|||
tag_sep = ' / '
|
||||
cache_key = '_NODE_FULL_TREE'
|
||||
cache_time = 3600
|
||||
has_empty_node = False
|
||||
has_ungrouped_node = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -119,9 +121,9 @@ class TreeService(Tree):
|
|||
return [self.get_node(i, deep=deep) for i in ancestor_ids]
|
||||
|
||||
def get_node_full_tag(self, nid):
|
||||
ancestors = self.ancestors(nid)
|
||||
ancestors = self.ancestors(nid, with_self=True)
|
||||
ancestors.reverse()
|
||||
return self.tag_sep.join(n.tag for n in ancestors)
|
||||
return self.tag_sep.join([n.tag for n in ancestors])
|
||||
|
||||
def get_family(self, nid, deep=False):
|
||||
ancestors = self.ancestors(nid, with_self=False, deep=deep)
|
||||
|
|
|
@ -290,10 +290,10 @@ LOGGING = {
|
|||
'handlers': ['syslog'],
|
||||
'level': 'INFO'
|
||||
},
|
||||
'django.db': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG'
|
||||
}
|
||||
# 'django.db': {
|
||||
# 'handlers': ['console', 'file'],
|
||||
# 'level': 'DEBUG'
|
||||
# }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,8 +64,11 @@ class OrgModelMixin(models.Model):
|
|||
sep = '@'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if current_org is not None and current_org.is_real():
|
||||
self.org_id = current_org.id
|
||||
org = get_current_org()
|
||||
if org is not None and (org.is_real() or org.is_system()):
|
||||
self.org_id = org.id
|
||||
elif org is not None and org.is_default():
|
||||
self.org_id = ''
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
|
|
@ -21,6 +21,8 @@ class Organization(models.Model):
|
|||
ROOT_NAME = 'ROOT'
|
||||
DEFAULT_ID = 'DEFAULT'
|
||||
DEFAULT_NAME = 'DEFAULT'
|
||||
SYSTEM_ID = '00000000-0000-0000-0000-000000000002'
|
||||
SYSTEM_NAME = 'SYSTEM'
|
||||
_user_admin_orgs = None
|
||||
|
||||
class Meta:
|
||||
|
@ -55,6 +57,8 @@ class Organization(models.Model):
|
|||
return cls.default()
|
||||
elif id_or_name in [cls.ROOT_ID, cls.ROOT_NAME]:
|
||||
return cls.root()
|
||||
elif id_or_name in [cls.SYSTEM_ID, cls.SYSTEM_NAME]:
|
||||
return cls.system()
|
||||
|
||||
try:
|
||||
if is_uuid(id_or_name):
|
||||
|
@ -89,7 +93,7 @@ class Organization(models.Model):
|
|||
return False
|
||||
|
||||
def is_real(self):
|
||||
return self.id not in (self.DEFAULT_NAME, self.ROOT_ID)
|
||||
return self.id not in (self.DEFAULT_NAME, self.ROOT_ID, self.SYSTEM_ID)
|
||||
|
||||
@classmethod
|
||||
def get_user_admin_orgs(cls, user):
|
||||
|
@ -111,17 +115,18 @@ class Organization(models.Model):
|
|||
def root(cls):
|
||||
return cls(id=cls.ROOT_ID, name=cls.ROOT_NAME)
|
||||
|
||||
@classmethod
|
||||
def system(cls):
|
||||
return cls(id=cls.SYSTEM_ID, name=cls.SYSTEM_NAME)
|
||||
|
||||
def is_root(self):
|
||||
if self.id is self.ROOT_ID:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return self.id is self.ROOT_ID
|
||||
|
||||
def is_default(self):
|
||||
if self.id is self.DEFAULT_ID:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return self.id is self.DEFAULT_ID
|
||||
|
||||
def is_system(self):
|
||||
return self.id is self.SYSTEM_ID
|
||||
|
||||
def change_to(self):
|
||||
from .utils import set_current_org
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from werkzeug.local import LocalProxy
|
||||
from contextlib import contextmanager
|
||||
|
||||
from common.local import thread_local
|
||||
from .models import Organization
|
||||
|
@ -52,4 +53,22 @@ def get_current_org_id_for_serializer():
|
|||
return org_id
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tmp_to_root_org():
|
||||
ori_org = get_current_org()
|
||||
set_to_root_org()
|
||||
yield
|
||||
if ori_org is not None:
|
||||
set_current_org(ori_org)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tmp_to_org(org):
|
||||
ori_org = get_current_org()
|
||||
set_current_org(org)
|
||||
yield
|
||||
if ori_org is not None:
|
||||
set_current_org(ori_org)
|
||||
|
||||
|
||||
current_org = LocalProxy(get_current_org)
|
||||
|
|
|
@ -40,24 +40,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||
return self.serializer_class
|
||||
|
||||
def filter_valid(self, queryset):
|
||||
valid = self.request.query_params.get('is_valid', None)
|
||||
if valid is None:
|
||||
valid_query = self.request.query_params.get('is_valid', None)
|
||||
if valid_query is None:
|
||||
return queryset
|
||||
if valid in ['0', 'N', 'false', 'False']:
|
||||
valid = False
|
||||
invalid = valid_query in ['0', 'N', 'false', 'False']
|
||||
if invalid:
|
||||
queryset = queryset.invalid()
|
||||
else:
|
||||
valid = True
|
||||
now = timezone.now()
|
||||
if valid:
|
||||
queryset = queryset.filter(is_active=True).filter(
|
||||
date_start__lt=now, date_expired__gt=now,
|
||||
)
|
||||
else:
|
||||
queryset = queryset.filter(
|
||||
Q(is_active=False) |
|
||||
Q(date_start__gt=now) |
|
||||
Q(date_expired__lt=now)
|
||||
)
|
||||
queryset = queryset.valid()
|
||||
return queryset
|
||||
|
||||
def filter_system_user(self, queryset):
|
||||
|
|
|
@ -15,10 +15,6 @@ from orgs.utils import set_to_root_org
|
|||
from ..utils import (
|
||||
ParserNode, AssetPermissionUtilV2
|
||||
)
|
||||
from .mixin import (
|
||||
UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
|
||||
)
|
||||
from .. import const
|
||||
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
|
||||
from .. import serializers
|
||||
from ..models import Action
|
||||
|
@ -66,8 +62,8 @@ class UserGrantedAssetsApi(UserPermissionMixin, ListAPIView):
|
|||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.AssetGrantedSerializer
|
||||
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
|
||||
filter_fields = ['hostname', 'ip']
|
||||
search_fields = filter_fields
|
||||
filter_fields = ['hostname', 'ip', 'id', 'comment']
|
||||
search_fields = ['hostname', 'ip', 'comment']
|
||||
|
||||
def filter_by_nodes(self, queryset):
|
||||
node_id = self.request.query_params.get("node")
|
||||
|
@ -109,12 +105,21 @@ class UserGrantedNodesApi(UserPermissionMixin, ListAPIView):
|
|||
查询用户授权的所有节点的API
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.GrantedNodeSerializer
|
||||
serializer_class = serializers.NodeGrantedSerializer
|
||||
only_fields = NodeSerializer.Meta.only_fields
|
||||
util = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.util = AssetPermissionUtilV2(self.obj)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["tree"] = self.util.user_tree
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
util = AssetPermissionUtilV2(self.obj)
|
||||
node_keys = util.get_nodes()
|
||||
node_keys = self.util.get_nodes()
|
||||
queryset = Node.objects.filter(key__in=node_keys)
|
||||
return queryset
|
||||
|
||||
|
@ -131,7 +136,6 @@ class UserGrantedNodeChildrenApi(UserGrantedNodesApi):
|
|||
system_user_id = self.request.query_params.get("system_user")
|
||||
self.util = AssetPermissionUtilV2(self.obj)
|
||||
if system_user_id:
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
self.util.filter_permissions(system_users=system_user_id)
|
||||
self.tree = self.util.get_user_tree()
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002"
|
||||
UNGROUPED_NODE_KEY = '-2'
|
||||
UNGROUPED_NODE_VALUE = _("Ungrouped")
|
||||
EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003"
|
||||
EMPTY_NODE_KEY = "1:-2"
|
||||
EMPTY_NODE_KEY = "-3"
|
||||
EMPTY_NODE_VALUE = _("Empty")
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.db import models
|
|||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import date_expired_default, set_or_append_attr_bulk
|
||||
from common.utils import date_expired_default
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import uuid
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
|
@ -24,6 +25,18 @@ class BasePermissionQuerySet(models.QuerySet):
|
|||
return self.active().filter(date_start__lt=timezone.now()) \
|
||||
.filter(date_expired__gt=timezone.now())
|
||||
|
||||
def inactive(self):
|
||||
return self.filter(is_active=False)
|
||||
|
||||
def invalid(self):
|
||||
now = timezone.now
|
||||
q = (
|
||||
Q(is_active=False) |
|
||||
Q(date_start__gt=now) |
|
||||
Q(date_expired__lt=now)
|
||||
)
|
||||
return self.filter(q)
|
||||
|
||||
|
||||
class BasePermissionManager(OrgManager):
|
||||
def valid(self):
|
||||
|
|
|
@ -9,8 +9,8 @@ from assets.serializers import ProtocolsField
|
|||
from .asset_permission import ActionsField
|
||||
|
||||
__all__ = [
|
||||
'GrantedNodeSerializer',
|
||||
'NodeGrantedSerializer', 'AssetGrantedSerializer',
|
||||
'NodeGrantedSerializer',
|
||||
'AssetGrantedSerializer',
|
||||
'ActionsSerializer', 'AssetSystemUserSerializer',
|
||||
]
|
||||
|
||||
|
@ -43,36 +43,29 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||
"id", "hostname", "ip", "protocols", "os", 'domain',
|
||||
"platform", "comment", "org_id",
|
||||
]
|
||||
fields = only_fields
|
||||
fields = only_fields + ['org_name']
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class NodeGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
授权资产组
|
||||
"""
|
||||
# assets_granted = AssetGrantedSerializer(many=True, read_only=True)
|
||||
# assets_amount = serializers.ReadOnlyField()
|
||||
name = serializers.ReadOnlyField(source='value')
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
|
||||
# assets_only_fields = AssetGrantedSerializer.Meta.only_fields
|
||||
# system_users_only_fields = AssetGrantedSerializer.system_users_only_fields
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
only_fields = ['id', 'key', 'value', "org_id"]
|
||||
fields = only_fields + ['name']
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class GrantedNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'name', 'key', 'value',
|
||||
'id', 'name', 'key', 'value', 'org_id', "assets_amount"
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.tree = self.context.get("tree")
|
||||
|
||||
def get_assets_amount(self, obj):
|
||||
if not self.tree:
|
||||
return 0
|
||||
return self.tree.assets_amount(obj.key)
|
||||
|
||||
|
||||
class ActionsSerializer(serializers.Serializer):
|
||||
actions = ActionsField(read_only=True)
|
||||
|
|
|
@ -15,25 +15,23 @@ logger = get_logger(__file__)
|
|||
@on_transaction_commit
|
||||
def on_permission_created(sender, instance=None, created=False, **kwargs):
|
||||
pass
|
||||
# AssetPermissionUtil.expire_all_cache()
|
||||
|
||||
|
||||
@receiver(post_save, sender=AssetPermission)
|
||||
def on_permission_update(sender, **kwargs):
|
||||
pass
|
||||
# AssetPermissionUtil.expire_all_cache()
|
||||
|
||||
|
||||
@receiver(post_delete, sender=AssetPermission)
|
||||
def on_permission_delete(sender, **kwargs):
|
||||
pass
|
||||
# AssetPermissionUtil.expire_all_cache()
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=AssetPermission.nodes.through)
|
||||
def on_permission_nodes_changed(sender, instance=None, **kwargs):
|
||||
# AssetPermissionUtil.expire_all_cache()
|
||||
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
|
||||
def on_permission_nodes_changed(sender, instance=None, action='', **kwargs):
|
||||
if action != 'post_add':
|
||||
return
|
||||
if isinstance(instance, AssetPermission):
|
||||
logger.debug("Asset permission nodes change signal received")
|
||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
system_users = instance.system_users.all()
|
||||
|
@ -42,9 +40,10 @@ def on_permission_nodes_changed(sender, instance=None, **kwargs):
|
|||
|
||||
|
||||
@receiver(m2m_changed, sender=AssetPermission.assets.through)
|
||||
def on_permission_assets_changed(sender, instance=None, **kwargs):
|
||||
# AssetPermissionUtil.expire_all_cache()
|
||||
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
|
||||
def on_permission_assets_changed(sender, instance=None, action='', **kwargs):
|
||||
if action != 'post_add':
|
||||
return
|
||||
if isinstance(instance, AssetPermission):
|
||||
logger.debug("Asset permission assets change signal received")
|
||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
system_users = instance.system_users.all()
|
||||
|
@ -53,13 +52,15 @@ def on_permission_assets_changed(sender, instance=None, **kwargs):
|
|||
|
||||
|
||||
@receiver(m2m_changed, sender=AssetPermission.system_users.through)
|
||||
def on_permission_system_users_changed(sender, instance=None, **kwargs):
|
||||
# AssetPermissionUtil.expire_all_cache()
|
||||
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
|
||||
logger.debug("Asset permission system_users change signal received")
|
||||
def on_permission_system_users_changed(sender, instance=None, action='', **kwargs):
|
||||
if action != 'post_add':
|
||||
return
|
||||
if isinstance(instance, AssetPermission):
|
||||
system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
logger.debug("Asset permission system_users change signal received")
|
||||
assets = instance.assets.all()
|
||||
nodes = instance.nodes.all()
|
||||
for system_user in system_users:
|
||||
system_user.nodes.add(*tuple(nodes))
|
||||
system_user.assets.add(*tuple(assets))
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from collections import defaultdict
|
|||
from functools import reduce
|
||||
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
|
||||
from orgs.utils import set_to_root_org
|
||||
from common.utils import get_logger, timeit
|
||||
|
@ -11,6 +12,7 @@ from common.tree import TreeNode
|
|||
from assets.utils import TreeService
|
||||
from ..models import AssetPermission
|
||||
from ..hands import Node, Asset, SystemUser
|
||||
from .. import const
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -129,6 +131,9 @@ class AssetPermissionUtilV2:
|
|||
|
||||
@timeit
|
||||
def add_direct_nodes_to_user_tree(self, user_tree):
|
||||
"""
|
||||
将授权规则的节点放到用户树上, 从full tree中粘贴子树
|
||||
"""
|
||||
nodes_direct_keys = self.permissions \
|
||||
.exclude(nodes__isnull=True) \
|
||||
.values_list('nodes__key', flat=True) \
|
||||
|
@ -153,6 +158,10 @@ class AssetPermissionUtilV2:
|
|||
|
||||
@timeit
|
||||
def add_single_assets_node_to_user_tree(self, user_tree):
|
||||
"""
|
||||
将单独授权的资产放到树上,如果设置了单独资产到 未分组中,则放到未分组中
|
||||
如果没有,则查询资产属于的资产组,放到树上
|
||||
"""
|
||||
# 添加单独授权资产的节点
|
||||
nodes_single_assets = defaultdict(set)
|
||||
queryset = self.permissions.exclude(assets__isnull=True) \
|
||||
|
@ -161,13 +170,26 @@ class AssetPermissionUtilV2:
|
|||
|
||||
for item in queryset:
|
||||
nodes_single_assets[item[1]].add(item[0])
|
||||
# Todo: 游离资产
|
||||
nodes_single_assets.pop(None, None)
|
||||
|
||||
for key in tuple(nodes_single_assets.keys()):
|
||||
if user_tree.contains(key):
|
||||
nodes_single_assets.pop(key)
|
||||
|
||||
# 如果要设置到ungroup中
|
||||
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
|
||||
node_key = Node.ungrouped_key
|
||||
node_value = Node.ungrouped_value
|
||||
user_tree.create_node(
|
||||
identifier=node_key, tag=node_value,
|
||||
parent=user_tree.root,
|
||||
)
|
||||
assets = set()
|
||||
for _assets in nodes_single_assets.values():
|
||||
assets.update(set(_assets))
|
||||
user_tree.set_assets(node_key, assets)
|
||||
return
|
||||
|
||||
# 获取单独授权资产,并没有在授权的节点上
|
||||
for key, assets in nodes_single_assets.items():
|
||||
node = self.full_tree.get_node(key, deep=True)
|
||||
|
@ -180,11 +202,17 @@ class AssetPermissionUtilV2:
|
|||
|
||||
@timeit
|
||||
def parse_user_tree_to_full_tree(self, user_tree):
|
||||
"""
|
||||
经过前面两个动作,用户授权的节点已放到树上,但是树不是完整的,
|
||||
这里要讲树构造成一个完整的书
|
||||
"""
|
||||
# 开始修正user_tree,保证父节点都在树上
|
||||
root_children = user_tree.children('')
|
||||
for child in root_children:
|
||||
if child.identifier.isdigit():
|
||||
continue
|
||||
if child.identifier.startswith('-'):
|
||||
continue
|
||||
ancestors = self.full_tree.ancestors(
|
||||
child.identifier, with_self=False, deep=True
|
||||
)
|
||||
|
@ -194,6 +222,19 @@ class AssetPermissionUtilV2:
|
|||
user_tree.safe_add_ancestors(ancestors)
|
||||
user_tree.move_node(child.identifier, parent_id)
|
||||
|
||||
@staticmethod
|
||||
def add_empty_node_if_need(user_tree):
|
||||
"""
|
||||
添加空节点,如果根节点没有子节点的话
|
||||
"""
|
||||
if not user_tree.children(user_tree.root):
|
||||
node_key = Node.empty_key
|
||||
node_value = Node.empty_value
|
||||
user_tree.create_node(
|
||||
identifier=node_key, tag=node_value,
|
||||
parent=user_tree.root,
|
||||
)
|
||||
|
||||
@timeit
|
||||
def get_user_tree(self):
|
||||
if self._user_tree:
|
||||
|
@ -207,6 +248,7 @@ class AssetPermissionUtilV2:
|
|||
self.add_direct_nodes_to_user_tree(user_tree)
|
||||
self.add_single_assets_node_to_user_tree(user_tree)
|
||||
self.parse_user_tree_to_full_tree(user_tree)
|
||||
self.add_empty_node_if_need(user_tree)
|
||||
self._user_tree = user_tree
|
||||
return user_tree
|
||||
|
||||
|
|
|
@ -74,6 +74,14 @@ def construct_remote_apps_tree_root():
|
|||
|
||||
|
||||
def parse_remote_app_to_tree_node(parent, remote_app):
|
||||
system_user = remote_app.system_user
|
||||
user = {
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
'protocol': system_user.protocol,
|
||||
'login_mode': system_user.login_mode,
|
||||
}
|
||||
tree_node = {
|
||||
'id': remote_app.id,
|
||||
'name': remote_app.name,
|
||||
|
@ -82,6 +90,6 @@ def parse_remote_app_to_tree_node(parent, remote_app):
|
|||
'open': False,
|
||||
'isParent': False,
|
||||
'iconSkin': 'file',
|
||||
'meta': {'type': 'remote_app'}
|
||||
'meta': {'type': 'remote_app', 'user': user}
|
||||
}
|
||||
return TreeNode(**tree_node)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<div class="ibox-content mailbox-content" style="padding-top: 0">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree">
|
||||
{% trans 'Loading' %} ...
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue