diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py
index ae1311971..fc1885720 100644
--- a/apps/assets/api/node.py
+++ b/apps/assets/api/node.py
@@ -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
diff --git a/apps/assets/apps.py b/apps/assets/apps.py
index 59e31dd97..e210a1708 100644
--- a/apps/assets/apps.py
+++ b/apps/assets/apps.py
@@ -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()
diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index 889d1de1f..2594d2763 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -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
diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py
index 7be8b9d47..e22857d54 100644
--- a/apps/assets/serializers/node.py
+++ b/apps/assets/serializers/node.py
@@ -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
diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py
index 266fe44bf..03b14f79b 100644
--- a/apps/assets/signals_handler.py
+++ b/apps/assets/signals_handler.py
@@ -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()
diff --git a/apps/assets/templates/assets/_node_tree.html b/apps/assets/templates/assets/_node_tree.html
index cd64e5b85..f6c48f22a 100644
--- a/apps/assets/templates/assets/_node_tree.html
+++ b/apps/assets/templates/assets/_node_tree.html
@@ -37,6 +37,7 @@
+ {% trans 'Loading' %} ...
diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html
index 9e994eba8..58e0d86e5 100644
--- a/apps/assets/templates/assets/asset_list.html
+++ b/apps/assets/templates/assets/asset_list.html
@@ -124,7 +124,6 @@
-{% include 'assets/_node_tree.html' %}
{% include 'assets/_asset_update_modal.html' %}
{% include 'assets/_asset_import_modal.html' %}
{% include 'assets/_asset_list_modal.html' %}
diff --git a/apps/assets/utils.py b/apps/assets/utils.py
index 473f6c46b..222933f94 100644
--- a/apps/assets/utils.py
+++ b/apps/assets/utils.py
@@ -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)
diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py
index 803c62b83..27df6314f 100644
--- a/apps/jumpserver/settings.py
+++ b/apps/jumpserver/settings.py
@@ -290,10 +290,10 @@ LOGGING = {
'handlers': ['syslog'],
'level': 'INFO'
},
- 'django.db': {
- 'handlers': ['console', 'file'],
- 'level': 'DEBUG'
- }
+ # 'django.db': {
+ # 'handlers': ['console', 'file'],
+ # 'level': 'DEBUG'
+ # }
}
}
diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py
index 3f54e14a7..672b975dc 100644
--- a/apps/orgs/mixins/models.py
+++ b/apps/orgs/mixins/models.py
@@ -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
diff --git a/apps/orgs/models.py b/apps/orgs/models.py
index e46c2b55d..03b46e9bc 100644
--- a/apps/orgs/models.py
+++ b/apps/orgs/models.py
@@ -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
diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py
index 8f380b169..90dde34d4 100644
--- a/apps/orgs/utils.py
+++ b/apps/orgs/utils.py
@@ -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)
diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py
index 0c9a43ae5..36bf00d9d 100644
--- a/apps/perms/api/asset_permission.py
+++ b/apps/perms/api/asset_permission.py
@@ -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):
diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py
index bb29ce6a1..f965f8ec4 100644
--- a/apps/perms/api/user_permission.py
+++ b/apps/perms/api/user_permission.py
@@ -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()
diff --git a/apps/perms/const.py b/apps/perms/const.py
index 4ccab5e38..476fc9d3f 100644
--- a/apps/perms/const.py
+++ b/apps/perms/const.py
@@ -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")
diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py
index 7215e01bb..fff6c56f3 100644
--- a/apps/perms/models/asset_permission.py
+++ b/apps/perms/models/asset_permission.py
@@ -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
diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py
index 02e580c8a..b36d009b3 100644
--- a/apps/perms/models/base.py
+++ b/apps/perms/models/base.py
@@ -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):
diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py
index 9f7ea4ecf..149b618eb 100644
--- a/apps/perms/serializers/user_permission.py
+++ b/apps/perms/serializers/user_permission.py
@@ -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)
diff --git a/apps/perms/signals_handler.py b/apps/perms/signals_handler.py
index faadaf1a8..3736e3a84 100644
--- a/apps/perms/signals_handler.py
+++ b/apps/perms/signals_handler.py
@@ -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))
+
diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py
index 8fa37beb5..3f7874753 100644
--- a/apps/perms/utils/asset_permission.py
+++ b/apps/perms/utils/asset_permission.py
@@ -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
diff --git a/apps/perms/utils/remote_app_permission.py b/apps/perms/utils/remote_app_permission.py
index a612b9ffb..ae60766ae 100644
--- a/apps/perms/utils/remote_app_permission.py
+++ b/apps/perms/utils/remote_app_permission.py
@@ -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)
diff --git a/apps/users/templates/users/_granted_assets.html b/apps/users/templates/users/_granted_assets.html
index 25240ee03..7904af4fb 100644
--- a/apps/users/templates/users/_granted_assets.html
+++ b/apps/users/templates/users/_granted_assets.html
@@ -4,6 +4,7 @@
+ {% trans 'Loading' %} ...