diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 51f9a8739..2478303d0 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -131,7 +131,7 @@ class NodeChildrenAsTreeApi(generics.ListAPIView): if not include_assets: return queryset assets = self.node.get_assets().only( - "id", "hostname", "ip", 'platform', "os", "org_id", + "id", "hostname", "ip", 'platform', "os", "org_id", "protocols", ) for asset in assets: queryset.append(asset.as_tree_node(self.node)) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index a37aa16b1..2161113e2 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -6,7 +6,8 @@ import uuid import logging import random from functools import reduce -from collections import OrderedDict +from collections import OrderedDict, defaultdict +from django.core.cache import cache from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -96,7 +97,53 @@ class ProtocolsMixin: return self.protocols_as_dict.get("ssh", 22) -class Asset(ProtocolsMixin, OrgModelMixin): +class NodesRelationMixin: + NODES_CACHE_KEY = 'ASSET_NODES_{}' + ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES' + CACHE_TIME = 3600 * 24 * 7 + id = "" + _all_nodes_keys = None + + @classmethod + def get_all_nodes_keys(cls): + """ + :return: {asset.id: [node.key, ]} + """ + from .node import Node + cache_key = cls.ALL_ASSET_NODES_CACHE_KEY + cached = cache.get(cache_key) + if cached: + return cached + assets = Asset.objects.all().only('id').prefetch_related( + models.Prefetch('nodes', queryset=Node.objects.all().only('key')) + ) + assets_nodes_keys = {} + for asset in assets: + assets_nodes_keys[asset.id] = [n.key for n in asset.nodes.all()] + cache.set(cache_key, assets_nodes_keys, cls.CACHE_TIME) + return assets_nodes_keys + + @classmethod + def expire_all_nodes_keys_cache(cls): + cache_key = cls.ALL_ASSET_NODES_CACHE_KEY + cache.delete(cache_key) + + def get_nodes(self): + from .node import Node + nodes = self.nodes.all() or [Node.root()] + return nodes + + def get_all_nodes(self, flat=False): + nodes = [] + for node in self.get_nodes(): + _nodes = node.get_ancestor(with_self=True) + nodes.append(_nodes) + if flat: + nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) + return nodes + + +class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): # Important PLATFORM_CHOICES = ( ('Linux', 'Linux'), @@ -182,20 +229,6 @@ class Asset(ProtocolsMixin, OrgModelMixin): def is_support_ansible(self): return self.has_protocol('ssh') and self.platform not in ("Other",) - def get_nodes(self): - from .node import Node - nodes = self.nodes.all() or [Node.root()] - return nodes - - def get_all_nodes(self, flat=False): - nodes = [] - for node in self.get_nodes(): - _nodes = node.get_ancestor(with_self=True) - nodes.append(_nodes) - if flat: - nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) - return nodes - @property def cpu_info(self): info = "" diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index aab5ebaf4..d668cea51 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -212,14 +212,12 @@ class AssetsAmountMixin: if cached is not None: return cached assets_amount = self.get_all_assets().count() - self.assets_amount = assets_amount + cache.set(cache_key, assets_amount, self.cache_time) return assets_amount @assets_amount.setter def assets_amount(self, value): self._assets_amount = value - cache_key = self._assets_amount_cache_key.format(self.key) - cache.set(cache_key, value, self.cache_time) def expire_assets_amount(self): ancestor_keys = self.get_ancestor_keys(with_self=True) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 32f569b80..bbd808b80 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -117,16 +117,6 @@ class SystemUser(AssetUser): def __str__(self): return '{0.name}({0.username})'.format(self) - def to_json(self): - return { - 'id': self.id, - 'name': self.name, - 'username': self.username, - 'protocol': self.protocol, - 'priority': self.priority, - 'auto_push': self.auto_push, - } - @property def login_mode_display(self): return self.get_login_mode_display() diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index b05de01f6..b8295457f 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -21,7 +21,7 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): model = AdminUser fields = [ 'id', 'name', 'username', 'password', 'private_key', 'public_key', - 'comment', 'connectivity_amount', 'assets_amount', + 'comment', 'assets_amount', 'date_created', 'date_updated', 'created_by', ] @@ -33,7 +33,6 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'date_updated': {'read_only': True}, 'created_by': {'read_only': True}, 'assets_amount': {'label': _('Asset')}, - 'connectivity_amount': {'label': _('Connectivity')}, } diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index b33f22116..ea4aa7355 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer): class Meta: model = Node - fields = [ - 'id', 'key', 'value', 'assets_amount', 'org_id', - ] + only_fields = ['id', 'key', 'value', 'org_id'] + fields = only_fields + ['assets_amount'] read_only_fields = [ 'key', 'assets_amount', 'org_id', ] diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index d853984e2..70855c9f7 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'id', 'name', 'username', 'password', 'public_key', 'private_key', 'login_mode', 'login_mode_display', 'priority', 'protocol', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', - 'assets_amount', 'connectivity_amount', 'auto_generate_key' + 'assets_amount', 'auto_generate_key' ] extra_kwargs = { 'password': {"write_only": True}, 'public_key': {"write_only": True}, 'private_key': {"write_only": True}, 'assets_amount': {'label': _('Asset')}, - 'connectivity_amount': {'label': _('Connectivity')}, 'login_mode_display': {'label': _('Login mode display')}, 'created_by': {'read_only': True}, } diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 59d01c98a..b2316d7d0 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs): @receiver(m2m_changed, sender=Asset.nodes.through) def on_asset_node_changed(sender, instance=None, **kwargs): logger.debug("Asset nodes change signal received") + Asset.expire_all_nodes_keys_cache() if isinstance(instance, Asset): if kwargs['action'] == 'pre_remove': nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) diff --git a/apps/assets/templates/assets/_node_tree.html b/apps/assets/templates/assets/_node_tree.html index 93aed7d89..61737184c 100644 --- a/apps/assets/templates/assets/_node_tree.html +++ b/apps/assets/templates/assets/_node_tree.html @@ -291,42 +291,6 @@ function defaultCallback(action) { $(document).ready(function () { }) -.on('click', '.btn-refresh-hardware', function () { - var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}"; - var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id); - function success(data) { - rMenu.css({"visibility" : "hidden"}); - var task_id = data.task; - var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); - window.open(url, '', 'width=800,height=600') - } - APIUpdateAttr({ - url: the_url, - method: "GET", - success: success, - flash_message: false - }); - -}) -.on('click', '.btn-test-connective', function () { - var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}"; - if (!current_node_id) { - return null; - } - var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id); - function success(data) { - rMenu.css({"visibility" : "hidden"}); - var task_id = data.task; - var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); - window.open(url, '', 'width=800,height=600') - } - APIUpdateAttr({ - url: the_url, - method: "GET", - success: success, - flash_message: false - }); -}) .on('click', '.btn-show-current-asset', function(){ hideRMenu(); $(this).css('display', 'none'); @@ -341,17 +305,5 @@ $(document).ready(function () { setCookie('show_current_asset', ''); location.reload(); }) -.on('click', '.btn-test-connective', function () { - hideRMenu(); -}) -.on('click', '#menu_refresh_assets_amount', function () { - hideRMenu(); - var url = "{% url 'api-assets:refresh-assets-amount' %}"; - APIUpdateAttr({ - 'url': url, - 'method': 'GET' - }); - window.location.reload(); -}) </script> \ No newline at end of file diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 0a17eccaa..61389ad08 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -2,8 +2,6 @@ {% load i18n static %} {% block help_message %} <div class="alert alert-info help-message"> -{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#} -{# Windows或其它硬件可以随意设置一个#} {% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%} {% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%} {% trans 'You can set any one for Windows or other hardware.' %} @@ -47,9 +45,9 @@ <th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Username' %}</th> <th class="text-center">{% trans 'Asset' %}</th> - <th class="text-center">{% trans 'Reachable' %}</th> - <th class="text-center">{% trans 'Unreachable' %}</th> - <th class="text-center">{% trans 'Ratio' %}</th> +{# <th class="text-center">{% trans 'Reachable' %}</th>#} +{# <th class="text-center">{% trans 'Unreachable' %}</th>#} +{# <th class="text-center">{% trans 'Ratio' %}</th>#} <th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Action' %}</th> </tr> @@ -73,44 +71,44 @@ function initTable() { var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id); }}, - {targets: 4, createdCell: function (td, cellData) { - var innerHtml = ""; - var data = cellData.reachable; - if (data !== 0) { - innerHtml = "<span class='text-navy'>" + data + "</span>"; - } else { - innerHtml = "<span>" + data + "</span>"; - } - $(td).html(innerHtml) - }}, - {targets: 5, createdCell: function (td, cellData) { - var data = cellData.unreachable; - var innerHtml = ""; - if (data !== 0) { - innerHtml = "<span class='text-danger'>" + data + "</span>"; - } else { - innerHtml = "<span>" + data + "</span>"; - } - $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>'); - }}, - {targets: 6, createdCell: function (td, cellData, rowData) { - var val = 0; - var innerHtml = ""; - var total = rowData.assets_amount; - var reachable = cellData.reachable; - if (total !== 0) { - val = reachable/total * 100; - } - - if (val === 100) { - innerHtml = "<span class='text-navy'>" + val + "% </span>"; - } else { - var num = new Number(val); - innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; - } - $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); - }}, - {targets: 8, createdCell: function (td, cellData, rowData) { + {#{targets: 4, createdCell: function (td, cellData) {#} + {# var innerHtml = "";#} + {# var data = cellData.reachable;#} + {# if (data !== 0) {#} + {# innerHtml = "<span class='text-navy'>" + data + "</span>";#} + {# } else {#} + {# innerHtml = "<span>" + data + "</span>";#} + {# }#} + {# $(td).html(innerHtml)#} + {#}},#} + {#{targets: 5, createdCell: function (td, cellData) {#} + {# var data = cellData.unreachable;#} + {# var innerHtml = "";#} + {# if (data !== 0) {#} + {# innerHtml = "<span class='text-danger'>" + data + "</span>";#} + {# } else {#} + {# innerHtml = "<span>" + data + "</span>";#} + {# }#} + {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#} + {#}},#} + {#{targets: 6, createdCell: function (td, cellData, rowData) {#} + {# var val = 0;#} + {# var innerHtml = "";#} + {# var total = rowData.assets_amount;#} + {# var reachable = cellData.reachable;#} + {# if (total !== 0) {#} + {# val = reachable/total * 100;#} + {# }#} + {##} + {# if (val === 100) {#} + {# innerHtml = "<span class='text-navy'>" + val + "% </span>";#} + {# } else {#} + {# var num = new Number(val);#} + {# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#} + {# }#} + {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#} + {#}},#} + {targets: 5, createdCell: function (td, cellData, rowData) { var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) @@ -118,7 +116,7 @@ function initTable() { ajax_url: '{% url "api-assets:admin-user-list" %}', columns: [ {data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, - {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, + {#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#} {data: "comment"}, {data: "id"} ] }; diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 3b9df3373..8f889feb7 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -167,7 +167,7 @@ function initTable() { }}, {targets: 5, createdCell: function (td, cellData, rowData) { var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData); - var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); + var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) }} ], @@ -325,8 +325,7 @@ $(document).ready(function(){ } window.open(url, '_self'); }) - -.on('click', '.btn_asset_delete', function () { +.on('click', '.btn-asset-delete', function () { var $this = $(this); var $data_table = $("#asset_list_table").DataTable(); var name = $(this).closest("tr").find(":nth-child(2)").children('a').html(); @@ -513,6 +512,40 @@ $(document).ready(function(){ update_node_action = "add" }).on('click', '#menu_asset_move', function () { update_node_action = "move" +}).on('click', '.btn-test-connective', function () { + var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}"; + if (!current_node_id) { + return null; + } + var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id); + function success(data) { + rMenu.css({"visibility" : "hidden"}); + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') + } + APIUpdateAttr({ + url: the_url, + method: "GET", + success: success, + flash_message: false + }); +}).on('click', '.btn-refresh-hardware', function () { + var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}"; + var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id); + function success(data) { + rMenu.css({"visibility" : "hidden"}); + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') + } + APIUpdateAttr({ + url: the_url, + method: "GET", + success: success, + flash_message: false + }); + }) </script> diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index 680bfa421..6c5ff3339 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -53,9 +53,9 @@ <th class="text-center">{% trans 'Protocol' %}</th> <th class="text-center">{% trans 'Login mode' %}</th> <th class="text-center">{% trans 'Asset' %}</th> - <th class="text-center">{% trans 'Reachable' %}</th> - <th class="text-center">{% trans 'Unreachable' %}</th> - <th class="text-center">{% trans 'Ratio' %}</th> +{# <th class="text-center">{% trans 'Reachable' %}</th>#} +{# <th class="text-center">{% trans 'Unreachable' %}</th>#} +{# <th class="text-center">{% trans 'Ratio' %}</th>#} <th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Action' %}</th> </tr> @@ -78,44 +78,44 @@ function initTable() { var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, - {targets: 6, createdCell: function (td, cellData) { - var innerHtml = ""; - var data = cellData.reachable; - if (data !== 0) { - innerHtml = "<span class='text-navy'>" + data + "</span>"; - } else { - innerHtml = "<span>" + data + "</span>"; - } - $(td).html(innerHtml) - }}, - {targets: 7, createdCell: function (td, cellData) { - var data = cellData.unreachable; - var innerHtml = ""; - if (data !== 0) { - innerHtml = "<span class='text-danger'>" + data + "</span>"; - } else { - innerHtml = "<span>" + data + "</span>"; - } - $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>'); - }}, - {targets: 8, createdCell: function (td, cellData, rowData) { - var val = 0; - var innerHtml = ""; - var total = rowData.assets_amount; - var reachable = cellData.reachable; - if (total && total !== 0) { - val = reachable/total * 100; - } - - if (val === 100) { - innerHtml = "<span class='text-navy'>" + val + "% </span>"; - } else { - var num = new Number(val); - innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; - } - $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); - }}, - {targets: 10, createdCell: function (td, cellData, rowData) { + {#{targets: 6, createdCell: function (td, cellData) {#} + {# var innerHtml = "";#} + {# var data = cellData.reachable;#} + {# if (data !== 0) {#} + {# innerHtml = "<span class='text-navy'>" + data + "</span>";#} + {# } else {#} + {# innerHtml = "<span>" + data + "</span>";#} + {# }#} + {# $(td).html(innerHtml)#} + {#}},#} + {#{targets: 7, createdCell: function (td, cellData) {#} + {# var data = cellData.unreachable;#} + {# var innerHtml = "";#} + {# if (data !== 0) {#} + {# innerHtml = "<span class='text-danger'>" + data + "</span>";#} + {# } else {#} + {# innerHtml = "<span>" + data + "</span>";#} + {# }#} + {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#} + {#}},#} + {#{targets: 8, createdCell: function (td, cellData, rowData) {#} + {# var val = 0;#} + {# var innerHtml = "";#} + {# var total = rowData.assets_amount;#} + {# var reachable = cellData.reachable;#} + {# if (total && total !== 0) {#} + {# val = reachable/total * 100;#} + {# }#} + {##} + {# if (val === 100) {#} + {# innerHtml = "<span class='text-navy'>" + val + "% </span>";#} + {# } else {#} + {# var num = new Number(val);#} + {# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#} + {# }#} + {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#} + {#}},#} + {targets: 7, createdCell: function (td, cellData, rowData) { var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) @@ -124,7 +124,7 @@ function initTable() { ajax_url: '{% url "api-assets:system-user-list" %}', columns: [ {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" }, - {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "comment" }, {data: "id" } + {data: "comment" }, {data: "id" } ], op_html: $('#actions').html() }; diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html index 23d0b34ed..f441baa39 100644 --- a/apps/assets/templates/assets/user_asset_list.html +++ b/apps/assets/templates/assets/user_asset_list.html @@ -43,7 +43,6 @@ <th class="text-center"><input type="checkbox" class="ipt_check_all"></th> <th class="text-center">{% trans 'Hostname' %}</th> <th class="text-center">{% trans 'IP' %}</th> - <th class="text-center">{% trans 'Active' %}</th> <th class="text-center">{% trans 'System users' %}</th> <th class="text-center">{% trans 'Action' %}</th> </tr> @@ -62,7 +61,7 @@ {% block custom_foot_js %} <script> -var treeUrl = "{% url 'api-perms:my-nodes-assets-as-tree' %}?show_assets=0&cache_policy=1"; +var treeUrl = "{% url 'api-perms:my-nodes-as-tree' %}?&cache_policy=1"; var zTree, asset_table, show=0; var inited = false; var url; @@ -83,20 +82,13 @@ function initTable() { $(td).html(detail_btn.replace("rowData_id", rowData.id)); }}, {targets: 3, createdCell: function (td, cellData) { - if (!cellData) { - $(td).html('<i class="fa fa-times text-danger"></i>') - } else { - $(td).html('<i class="fa fa-check text-navy"></i>') - } - }}, - {targets: 4, createdCell: function (td, cellData) { var users = []; $.each(cellData, function (id, data) { users.push(data.name); }); $(td).html(users.join(', ')) }}, - {targets: 5, createdCell: function (td, cellData) { + {targets: 4, createdCell: function (td, cellData) { var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData); $(td).html(conn_btn) }} @@ -104,7 +96,6 @@ function initTable() { ajax_url: url, columns: [ {data: "id"}, {data: "hostname" }, {data: "ip" }, - {data: "is_active", orderable: false }, {data: "system_users_granted", orderable: false}, {data: "id", orderable: false} ] diff --git a/apps/assets/utils.py b/apps/assets/utils.py index b64fea35e..a0de3b481 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -104,7 +104,7 @@ class NodeUtil: _node._assets_amount = len(_node._assets) delattr(_node, '_assets') self.stack.top._children.append(_node) - self.stack.top._all_children.extend([_node] + _node._children) + self.stack.top._all_children.extend([_node] + _node._all_children) def init(self): all_nodes = self.get_all_nodes() @@ -145,29 +145,69 @@ class NodeUtil: def nodes(self): return list(self._nodes.values()) + def get_family_by_key(self, key): + tree_nodes = set() + node = self.get_node_by_key(key) + if not node: + return [] + tree_nodes.update(node._parents) + tree_nodes.add(node) + tree_nodes.update(node._all_children) + return list(tree_nodes) + # 使用给定节点生成一颗树 # 找到他们的祖先节点 # 可选找到他们的子孙节点 - def get_family(self, nodes, with_children=False): - tree_nodes = set() - for n in nodes: - node = self.get_node_by_key(n.key) - if not node: - continue - tree_nodes.update(node._parents) - tree_nodes.add(node) - if with_children: - tree_nodes.update(node._children) - return list(tree_nodes) + def get_family(self, node): + return self.get_family_by_key(node.key) - def get_nodes_parents(self, nodes, with_self=True): + def get_family_keys_by_key(self, key): + nodes = self.get_family_by_key(key) + return [n.key for n in nodes] + + def get_some_nodes_family_by_keys(self, keys): + family = set() + for key in keys: + family.update(self.get_family_by_key(key)) + return family + + def get_some_nodes_family_keys_by_keys(self, keys): + family = self.get_some_nodes_family_by_keys(keys) + return [n.key for n in family] + + def get_nodes_parents_by_key(self, key, with_self=True): parents = set() - for n in nodes: - node = self.get_node_by_key(n.key) - parents.update(set(node._parents)) - if with_self: - parents.add(node) - return parents + node = self.get_node_by_key(key) + if not node: + return [] + parents.update(set(node._parents)) + if with_self: + parents.add(node) + return list(parents) + + def get_node_parents(self, node, with_self=True): + return self.get_nodes_parents_by_key(node.key, with_self=with_self) + + def get_nodes_parents_keys_by_key(self, key, with_self=True): + nodes = self.get_nodes_parents_by_key(key, with_self=with_self) + return [n.key for n in nodes] + + def get_all_children_by_key(self, key, with_self=True): + children = set() + node = self.get_node_by_key(key) + if not node: + return [] + children.update(set(node._all_children)) + if with_self: + children.add(node) + return list(children) + + def get_children(self, node, with_self=True): + return self.get_all_children_by_key(node.key, with_self=with_self) + + def get_children_keys_by_key(self, key, with_self=True): + nodes = self.get_all_children_by_key(key, with_self=with_self) + return [n.key for n in nodes] def test_node_tree(): diff --git a/apps/common/api.py b/apps/common/api.py index bf41312d7..a3b1938be 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -4,11 +4,13 @@ import os import uuid from django.core.cache import cache +from django.views.decorators.csrf import csrf_exempt from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import generics, serializers +from .http import HttpResponseTemporaryRedirect from .const import KEY_CACHE_RESOURCES_ID __all__ = [ @@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView): cache_key = KEY_CACHE_RESOURCES_ID.format(spm) cache.set(cache_key, resources_id, 300) return Response({'spm': spm}) + + +@csrf_exempt +def redirect_plural_name_api(request, *args, **kwargs): + resource = kwargs.get("resource", "") + full_path = request.get_full_path() + full_path = full_path.replace(resource, resource+"s", 1) + return HttpResponseTemporaryRedirect(full_path) diff --git a/apps/common/http.py b/apps/common/http.py new file mode 100644 index 000000000..df6b9a78f --- /dev/null +++ b/apps/common/http.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# +from django.http import HttpResponse +from django.utils.encoding import iri_to_uri + + +class HttpResponseTemporaryRedirect(HttpResponse): + status_code = 307 + + def __init__(self, redirect_to): + HttpResponse.__init__(self) + self['Location'] = iri_to_uri(redirect_to) diff --git a/apps/common/tree.py b/apps/common/tree.py index 13756a35d..e73b43aa6 100644 --- a/apps/common/tree.py +++ b/apps/common/tree.py @@ -46,12 +46,17 @@ class TreeNode: def __gt__(self, other): if self.isParent and not other.isParent: - return False + result = False elif not self.isParent and other.isParent: - return True - if self.pId != other.pId: - return self.pId > other.pId - return self.name > other.name + result = True + elif self.pId != other.pId: + result = self.pId > other.pId + else: + result = self.name > other.name + return result + + def __le__(self, other): + return not self.__gt__(other) def __eq__(self, other): return self.id == other.id @@ -74,7 +79,7 @@ class Tree: raise ValueError("Parent must not be node parent") node.pId = parent.id parent.isParent = True - self.nodes[node.id] = node + self.nodes[node.key] = node def get_nodes(self): return sorted(self.nodes.values()) diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index fdd4827d3..c0bd771e4 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -7,6 +7,7 @@ import logging import datetime import uuid from functools import wraps +import time import copy import ipaddress @@ -179,3 +180,18 @@ def random_string(length): charset = string.ascii_letters + string.digits s = [random.choice(charset) for i in range(length)] return ''.join(s) + + +logger = get_logger(__name__) + + +def timeit(func): + def wrapper(*args, **kwargs): + logger.debug("Start call: {}".format(func.__name__)) + now = time.time() + result = func(*args, **kwargs) + using = (time.time() - now) * 1000 + msg = "Call {} end, using: {:.1f}ms".format(func.__name__, using) + logger.debug(msg) + return result + return wrapper diff --git a/apps/jumpserver/views.py b/apps/jumpserver/views.py index 7f7662add..9ad8f7b39 100644 --- a/apps/jumpserver/views.py +++ b/apps/jumpserver/views.py @@ -2,7 +2,7 @@ import datetime import re import time -from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponseRedirect from django.conf import settings from django.views.generic import TemplateView, View from django.utils import timezone @@ -13,13 +13,14 @@ from rest_framework.response import Response from rest_framework.views import APIView from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse -from django.utils.encoding import iri_to_uri + from users.models import User from assets.models import Asset from terminal.models import Session from orgs.utils import current_org from common.permissions import PermissionsMixin, IsValidUser +from common.http import HttpResponseTemporaryRedirect class IndexView(PermissionsMixin, TemplateView): @@ -203,14 +204,6 @@ class I18NView(View): api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$') -class HttpResponseTemporaryRedirect(HttpResponse): - status_code = 307 - - def __init__(self, redirect_to): - HttpResponse.__init__(self) - self['Location'] = iri_to_uri(redirect_to) - - @csrf_exempt def redirect_format_api(request, *args, **kwargs): _path, query = request.path, request.GET.urlencode() diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 3ca6363f6..c702b74b1 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 8406a09cd..e7d79281b 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-07-08 15:32+0800\n" +"POT-Creation-Date: 2019-07-11 11:23+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler <ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n" @@ -76,10 +76,10 @@ msgstr "运行参数" #: applications/templates/applications/remote_app_list.html:22 #: applications/templates/applications/user_remote_app_list.html:18 #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:286 assets/models/authbook.py:24 +#: assets/models/asset.py:342 assets/models/authbook.py:24 #: assets/serializers/admin_user.py:35 assets/serializers/asset_user.py:81 #: assets/serializers/system_user.py:30 -#: assets/templates/assets/admin_user_list.html:49 +#: assets/templates/assets/admin_user_list.html:47 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/label_list.html:16 @@ -95,7 +95,7 @@ msgstr "运行参数" #: terminal/templates/terminal/command_list.html:66 #: terminal/templates/terminal/session_list.html:28 #: terminal/templates/terminal/session_list.html:72 -#: xpack/plugins/change_auth_plan/forms.py:114 +#: xpack/plugins/change_auth_plan/forms.py:115 #: xpack/plugins/change_auth_plan/models.py:413 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 @@ -139,7 +139,7 @@ msgstr "系统用户" #: assets/models/cmd_filter.py:20 assets/models/domain.py:20 #: assets/models/group.py:20 assets/models/label.py:18 #: assets/templates/assets/admin_user_detail.html:56 -#: assets/templates/assets/admin_user_list.html:47 +#: assets/templates/assets/admin_user_list.html:45 #: assets/templates/assets/cmd_filter_detail.html:61 #: assets/templates/assets/cmd_filter_list.html:24 #: assets/templates/assets/domain_detail.html:56 @@ -173,7 +173,7 @@ msgstr "系统用户" #: users/templates/users/user_list.html:35 #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:53 -#: xpack/plugins/change_auth_plan/forms.py:97 +#: xpack/plugins/change_auth_plan/forms.py:98 #: xpack/plugins/change_auth_plan/models.py:61 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 @@ -205,7 +205,7 @@ msgstr "参数" #: applications/models/remote_app.py:43 #: applications/templates/applications/remote_app_detail.html:77 -#: assets/models/asset.py:151 assets/models/base.py:36 +#: assets/models/asset.py:221 assets/models/base.py:36 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 #: assets/models/cmd_filter.py:58 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 @@ -229,7 +229,7 @@ msgstr "创建者" # msgstr "创建者" #: applications/models/remote_app.py:46 #: applications/templates/applications/remote_app_detail.html:73 -#: assets/models/asset.py:152 assets/models/base.py:34 +#: assets/models/asset.py:222 assets/models/base.py:34 #: assets/models/cluster.py:26 assets/models/domain.py:23 #: assets/models/group.py:22 assets/models/label.py:25 #: assets/templates/assets/admin_user_detail.html:64 @@ -257,12 +257,12 @@ msgstr "创建日期" #: applications/templates/applications/remote_app_detail.html:81 #: applications/templates/applications/remote_app_list.html:24 #: applications/templates/applications/user_remote_app_list.html:20 -#: assets/models/asset.py:153 assets/models/base.py:33 +#: assets/models/asset.py:223 assets/models/base.py:33 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 #: assets/models/cmd_filter.py:55 assets/models/domain.py:21 #: assets/models/domain.py:53 assets/models/group.py:23 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 -#: assets/templates/assets/admin_user_list.html:53 +#: assets/templates/assets/admin_user_list.html:51 #: assets/templates/assets/asset_detail.html:132 #: assets/templates/assets/cmd_filter_detail.html:65 #: assets/templates/assets/cmd_filter_list.html:27 @@ -412,8 +412,8 @@ msgstr "详情" #: applications/templates/applications/remote_app_list.html:56 #: assets/templates/assets/_asset_user_list.html:70 #: assets/templates/assets/admin_user_detail.html:24 -#: assets/templates/assets/admin_user_list.html:29 -#: assets/templates/assets/admin_user_list.html:114 +#: assets/templates/assets/admin_user_list.html:27 +#: assets/templates/assets/admin_user_list.html:112 #: assets/templates/assets/asset_detail.html:27 #: assets/templates/assets/asset_list.html:78 #: assets/templates/assets/asset_list.html:169 @@ -456,7 +456,7 @@ msgstr "更新" #: applications/templates/applications/remote_app_detail.html:25 #: applications/templates/applications/remote_app_list.html:57 #: assets/templates/assets/admin_user_detail.html:28 -#: assets/templates/assets/admin_user_list.html:115 +#: assets/templates/assets/admin_user_list.html:113 #: assets/templates/assets/asset_detail.html:31 #: assets/templates/assets/asset_list.html:170 #: assets/templates/assets/cmd_filter_detail.html:33 @@ -515,7 +515,7 @@ msgstr "创建远程应用" #: applications/templates/applications/user_remote_app_list.html:21 #: assets/models/cmd_filter.py:54 #: assets/templates/assets/_asset_user_list.html:20 -#: assets/templates/assets/admin_user_list.html:54 +#: assets/templates/assets/admin_user_list.html:52 #: assets/templates/assets/asset_list.html:100 #: assets/templates/assets/cmd_filter_list.html:28 #: assets/templates/assets/cmd_filter_rule_list.html:63 @@ -598,25 +598,21 @@ msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" #: assets/const.py:77 assets/models/utils.py:43 -#: assets/templates/assets/admin_user_list.html:51 -#: assets/templates/assets/system_user_list.html:57 msgid "Unreachable" msgstr "不可达" #: assets/const.py:78 assets/models/utils.py:44 -#: assets/templates/assets/admin_user_list.html:50 #: assets/templates/assets/asset_list.html:99 -#: assets/templates/assets/system_user_list.html:56 #: users/templates/users/user_group_granted_asset.html:47 msgid "Reachable" msgstr "可连接" #: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:9 -#: xpack/plugins/license/models.py:79 +#: xpack/plugins/license/models.py:78 msgid "Unknown" msgstr "未知" -#: assets/forms/asset.py:24 assets/models/asset.py:117 +#: assets/forms/asset.py:24 assets/models/asset.py:187 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:69 #: assets/templates/assets/user_asset_list.html:168 @@ -624,7 +620,7 @@ msgstr "未知" msgid "Port" msgstr "端口" -#: assets/forms/asset.py:45 assets/models/asset.py:122 +#: assets/forms/asset.py:45 assets/models/asset.py:192 #: assets/models/user.py:107 assets/templates/assets/asset_detail.html:190 #: assets/templates/assets/asset_detail.html:198 #: assets/templates/assets/system_user_assets.html:83 @@ -633,7 +629,7 @@ msgstr "端口" msgid "Nodes" msgstr "节点" -#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:126 +#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:196 #: assets/models/cluster.py:19 assets/models/user.py:65 #: assets/templates/assets/asset_detail.html:76 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 @@ -651,7 +647,7 @@ msgstr "管理用户" msgid "Label" msgstr "标签" -#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:121 +#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:191 #: assets/models/domain.py:26 assets/models/domain.py:52 #: assets/templates/assets/asset_detail.html:80 #: assets/templates/assets/user_asset_list.html:173 @@ -660,14 +656,14 @@ msgid "Domain" msgstr "网域" #: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93 -#: assets/forms/asset.py:128 assets/models/node.py:255 +#: assets/forms/asset.py:128 assets/models/node.py:254 #: assets/templates/assets/asset_create.html:42 #: perms/forms/asset_permission.py:71 perms/forms/asset_permission.py:78 #: perms/models/asset_permission.py:101 #: perms/templates/perms/asset_permission_list.html:49 #: perms/templates/perms/asset_permission_list.html:70 #: perms/templates/perms/asset_permission_list.html:120 -#: xpack/plugins/change_auth_plan/forms.py:115 +#: xpack/plugins/change_auth_plan/forms.py:116 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15 #: xpack/plugins/cloud/models.py:123 @@ -696,7 +692,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, #: assets/forms/asset.py:108 assets/forms/asset.py:112 #: assets/forms/domain.py:17 assets/forms/label.py:15 #: perms/templates/perms/asset_permission_asset.html:88 -#: xpack/plugins/change_auth_plan/forms.py:105 +#: xpack/plugins/change_auth_plan/forms.py:106 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:84 msgid "Select assets" msgstr "选择资产" @@ -719,7 +715,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: assets/templates/assets/_asset_user_auth_view_modal.html:21 #: assets/templates/assets/_asset_user_list.html:16 #: assets/templates/assets/admin_user_detail.html:60 -#: assets/templates/assets/admin_user_list.html:48 +#: assets/templates/assets/admin_user_list.html:46 #: assets/templates/assets/domain_gateway_list.html:71 #: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_list.html:52 audits/models.py:94 @@ -734,7 +730,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: users/templates/users/user_detail.html:67 #: users/templates/users/user_list.html:36 #: users/templates/users/user_profile.html:47 -#: xpack/plugins/change_auth_plan/forms.py:99 +#: xpack/plugins/change_auth_plan/forms.py:100 #: xpack/plugins/change_auth_plan/models.py:63 #: xpack/plugins/change_auth_plan/models.py:409 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65 @@ -809,7 +805,7 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写" msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" -#: assets/models/asset.py:112 assets/models/domain.py:49 +#: assets/models/asset.py:182 assets/models/domain.py:49 #: assets/serializers/asset_user.py:28 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_user_list.html:15 @@ -826,7 +822,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" msgid "IP" msgstr "IP" -#: assets/models/asset.py:113 assets/serializers/asset_user.py:27 +#: assets/models/asset.py:183 assets/serializers/asset_user.py:27 #: assets/templates/assets/_asset_list_modal.html:45 #: assets/templates/assets/_asset_user_auth_update_modal.html:9 #: assets/templates/assets/_asset_user_auth_view_modal.html:15 @@ -843,101 +839,100 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:116 assets/models/domain.py:51 +#: assets/models/asset.py:186 assets/models/domain.py:51 #: assets/models/user.py:110 assets/templates/assets/asset_detail.html:72 #: assets/templates/assets/domain_gateway_list.html:70 #: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_list.html:53 #: assets/templates/assets/user_asset_list.html:169 #: terminal/templates/terminal/session_list.html:31 -#: terminal/templates/terminal/session_list.html:75 msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:119 assets/serializers/asset.py:63 +#: assets/models/asset.py:189 assets/serializers/asset.py:63 #: assets/templates/assets/asset_create.html:24 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:120 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:190 assets/templates/assets/asset_detail.html:104 #: assets/templates/assets/user_asset_list.html:170 msgid "Platform" msgstr "系统平台" -#: assets/models/asset.py:123 assets/models/cmd_filter.py:21 +#: assets/models/asset.py:193 assets/models/cmd_filter.py:21 #: assets/models/domain.py:54 assets/models/label.py:22 #: assets/templates/assets/asset_detail.html:112 #: assets/templates/assets/user_asset_list.html:174 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:129 assets/templates/assets/asset_detail.html:68 +#: assets/models/asset.py:199 assets/templates/assets/asset_detail.html:68 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:130 assets/templates/assets/asset_detail.html:120 +#: assets/models/asset.py:200 assets/templates/assets/asset_detail.html:120 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:133 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:84 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:134 assets/templates/assets/asset_detail.html:88 +#: assets/models/asset.py:204 assets/templates/assets/asset_detail.html:88 msgid "Model" msgstr "型号" -#: assets/models/asset.py:135 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:205 assets/templates/assets/asset_detail.html:116 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:137 +#: assets/models/asset.py:207 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:138 +#: assets/models/asset.py:208 #: xpack/plugins/license/templates/license/license_detail.html:80 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:139 +#: assets/models/asset.py:209 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:140 +#: assets/models/asset.py:210 msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:141 assets/templates/assets/asset_detail.html:96 +#: assets/models/asset.py:211 assets/templates/assets/asset_detail.html:96 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:142 +#: assets/models/asset.py:212 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:143 +#: assets/models/asset.py:213 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:145 assets/templates/assets/asset_detail.html:108 +#: assets/models/asset.py:215 assets/templates/assets/asset_detail.html:108 #: assets/templates/assets/user_asset_list.html:171 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:146 +#: assets/models/asset.py:216 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:147 +#: assets/models/asset.py:217 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:148 +#: assets/models/asset.py:218 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:150 assets/templates/assets/asset_create.html:46 +#: assets/models/asset.py:220 assets/templates/assets/asset_create.html:46 #: assets/templates/assets/asset_detail.html:227 templates/_nav.html:26 msgid "Labels" msgstr "标签管理" @@ -1003,7 +998,6 @@ msgid "Operator" msgstr "运营商" #: assets/models/cluster.py:36 assets/models/group.py:34 -#: perms/utils/asset_permission.py:106 msgid "Default" msgstr "默认" @@ -1136,7 +1130,7 @@ msgstr "默认资产组" msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:246 +#: assets/models/label.py:19 assets/models/node.py:245 #: assets/templates/assets/label_list.html:15 settings/models.py:30 msgid "Value" msgstr "值" @@ -1145,11 +1139,11 @@ msgstr "值" msgid "Category" msgstr "分类" -#: assets/models/node.py:245 +#: assets/models/node.py:244 msgid "Key" msgstr "键" -#: assets/models/node.py:303 +#: assets/models/node.py:302 msgid "New node" msgstr "新节点" @@ -1208,12 +1202,6 @@ msgstr "登录模式" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/serializers/admin_user.py:36 assets/serializers/asset.py:64 -#: assets/serializers/asset_user.py:29 assets/serializers/system_user.py:31 -#: assets/templates/assets/_asset_user_list.html:18 -msgid "Connectivity" -msgstr "连接" - #: assets/serializers/asset.py:21 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" @@ -1222,6 +1210,11 @@ msgstr "协议格式 {}/{}" msgid "Protocol duplicate: {}" msgstr "协议重复: {}" +#: assets/serializers/asset.py:64 assets/serializers/asset_user.py:29 +#: assets/templates/assets/_asset_user_list.html:18 +msgid "Connectivity" +msgstr "连接" + #: assets/serializers/asset.py:90 msgid "Hardware info" msgstr "硬件信息" @@ -1247,19 +1240,19 @@ msgstr "ssh公钥" msgid "private key invalid" msgstr "密钥不合法" -#: assets/serializers/node.py:33 +#: assets/serializers/node.py:32 msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:32 +#: assets/serializers/system_user.py:31 msgid "Login mode display" msgstr "登录模式显示" -#: assets/serializers/system_user.py:67 +#: assets/serializers/system_user.py:66 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:76 +#: assets/serializers/system_user.py:75 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" @@ -1401,7 +1394,7 @@ msgid "Update asset user auth" msgstr "更新资产用户认证信息" #: assets/templates/assets/_asset_user_auth_update_modal.html:23 -#: xpack/plugins/change_auth_plan/forms.py:101 +#: xpack/plugins/change_auth_plan/forms.py:102 msgid "Please input password" msgstr "请输入密码" @@ -1490,19 +1483,19 @@ msgstr "重命名节点" msgid "Delete node" msgstr "删除节点" -#: assets/templates/assets/_node_tree.html:155 +#: assets/templates/assets/_node_tree.html:154 msgid "Create node failed" msgstr "创建节点失败" -#: assets/templates/assets/_node_tree.html:167 +#: assets/templates/assets/_node_tree.html:166 msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/_node_tree.html:169 +#: assets/templates/assets/_node_tree.html:168 msgid "Have assets, cancel" msgstr "存在资产,不能删除" -#: assets/templates/assets/_node_tree.html:243 +#: assets/templates/assets/_node_tree.html:242 msgid "Rename success" msgstr "重命名成功" @@ -1582,14 +1575,14 @@ msgstr "替换资产的管理员" #: assets/templates/assets/admin_user_detail.html:91 #: perms/templates/perms/asset_permission_asset.html:116 -#: xpack/plugins/change_auth_plan/forms.py:109 +#: xpack/plugins/change_auth_plan/forms.py:110 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:112 msgid "Select nodes" msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:207 -#: assets/templates/assets/asset_list.html:396 +#: assets/templates/assets/asset_list.html:395 #: assets/templates/assets/cmd_filter_detail.html:106 #: assets/templates/assets/system_user_assets.html:100 #: assets/templates/assets/system_user_detail.html:182 @@ -1611,24 +1604,24 @@ msgstr "选择节点" msgid "Confirm" msgstr "确认" -#: assets/templates/assets/admin_user_list.html:7 +#: assets/templates/assets/admin_user_list.html:5 msgid "" "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL " "sudo permissions users, " msgstr "" "管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户," -#: assets/templates/assets/admin_user_list.html:8 +#: assets/templates/assets/admin_user_list.html:6 msgid "" "Jumpserver users of the system using the user to `push system user`, `get " "assets hardware information`, etc. " msgstr "Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。" -#: assets/templates/assets/admin_user_list.html:9 +#: assets/templates/assets/admin_user_list.html:7 msgid "You can set any one for Windows or other hardware." msgstr "Windows或其它硬件可以随意设置一个" -#: assets/templates/assets/admin_user_list.html:19 +#: assets/templates/assets/admin_user_list.html:17 #: assets/templates/assets/asset_list.html:68 #: assets/templates/assets/system_user_list.html:23 #: audits/templates/audits/login_log_list.html:85 @@ -1638,7 +1631,7 @@ msgstr "Windows或其它硬件可以随意设置一个" msgid "Export" msgstr "导出" -#: assets/templates/assets/admin_user_list.html:24 +#: assets/templates/assets/admin_user_list.html:22 #: assets/templates/assets/asset_list.html:73 #: assets/templates/assets/system_user_list.html:28 #: settings/templates/settings/_ldap_list_users_modal.html:100 @@ -1649,20 +1642,13 @@ msgstr "导出" msgid "Import" msgstr "导入" -#: assets/templates/assets/admin_user_list.html:39 +#: assets/templates/assets/admin_user_list.html:37 #: assets/views/admin_user.py:50 msgid "Create admin user" msgstr "创建管理用户" -#: assets/templates/assets/admin_user_list.html:52 -#: assets/templates/assets/system_user_list.html:58 -#: ops/templates/ops/adhoc_history.html:54 -#: ops/templates/ops/task_history.html:60 -msgid "Ratio" -msgstr "比例" - -#: assets/templates/assets/admin_user_list.html:165 -#: assets/templates/assets/admin_user_list.html:196 +#: assets/templates/assets/admin_user_list.html:163 +#: assets/templates/assets/admin_user_list.html:194 #: assets/templates/assets/asset_list.html:268 #: assets/templates/assets/asset_list.html:305 #: assets/templates/assets/system_user_list.html:225 @@ -1807,7 +1793,7 @@ msgstr "仅显示当前节点资产" msgid "Displays all child node assets" msgstr "显示所有子节点资产" -#: assets/templates/assets/asset_list.html:390 +#: assets/templates/assets/asset_list.html:389 #: assets/templates/assets/system_user_list.html:166 #: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:408 @@ -1818,11 +1804,11 @@ msgstr "显示所有子节点资产" msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:391 +#: assets/templates/assets/asset_list.html:390 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:394 +#: assets/templates/assets/asset_list.html:393 #: assets/templates/assets/system_user_list.html:170 #: settings/templates/settings/terminal_setting.html:166 #: users/templates/users/user_detail.html:386 @@ -1836,16 +1822,16 @@ msgstr "删除选择资产" msgid "Cancel" msgstr "取消" -#: assets/templates/assets/asset_list.html:407 +#: assets/templates/assets/asset_list.html:406 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:408 -#: assets/templates/assets/asset_list.html:412 +#: assets/templates/assets/asset_list.html:407 +#: assets/templates/assets/asset_list.html:411 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:411 +#: assets/templates/assets/asset_list.html:410 msgid "Asset Deleting failed." msgstr "删除失败" @@ -2650,7 +2636,7 @@ msgstr "不能包含特殊字符" msgid "This field must be unique." msgstr "字段必须唯一" -#: jumpserver/views.py:190 +#: jumpserver/views.py:191 msgid "" "<div>Luna is a separately deployed program, you need to deploy Luna, coco, " "configure nginx for url distribution,</div> </div>If you see this page, " @@ -2853,6 +2839,11 @@ msgstr "执行历史" msgid "F/S/T" msgstr "失败/成功/总" +#: ops/templates/ops/adhoc_history.html:54 +#: ops/templates/ops/task_history.html:60 +msgid "Ratio" +msgstr "比例" + #: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:142 msgid "Run history detail" msgstr "执行历史详情" @@ -3005,6 +2996,14 @@ msgstr "命令执行" msgid "Organization" msgstr "组织" +#: perms/api/user_permission.py:206 +msgid "ungrouped" +msgstr "未分组" + +#: perms/api/user_permission.py:211 +msgid "empty" +msgstr "空" + #: perms/forms/asset_permission.py:65 perms/forms/remote_app_permission.py:34 #: perms/models/asset_permission.py:102 perms/models/base.py:37 #: perms/templates/perms/asset_permission_list.html:47 @@ -3207,10 +3206,6 @@ msgstr "添加用户" msgid "Add user group to this permission" msgstr "添加用户组" -#: perms/utils/asset_permission.py:115 -msgid "Empty" -msgstr "空" - #: perms/views/asset_permission.py:33 perms/views/asset_permission.py:64 #: perms/views/asset_permission.py:81 perms/views/asset_permission.py:98 #: perms/views/asset_permission.py:135 perms/views/asset_permission.py:168 @@ -5313,25 +5308,28 @@ msgid "Password length" msgstr "密码长度" #: xpack/plugins/change_auth_plan/forms.py:45 -msgid "* For security, please do not change root user's password" -msgstr "* 为了安全,请不要更改root用户的密码" +#: xpack/plugins/change_auth_plan/models.py:213 +#, fuzzy +#| msgid "For security, do not change {} user's password" +msgid "* For security, do not change {} user's password" +msgstr "* 为了安全,禁止更改 {} 用户的密码" -#: xpack/plugins/change_auth_plan/forms.py:54 +#: xpack/plugins/change_auth_plan/forms.py:55 msgid "* Please enter custom password" msgstr "* 请输入自定义密码" -#: xpack/plugins/change_auth_plan/forms.py:63 +#: xpack/plugins/change_auth_plan/forms.py:64 msgid "* Please enter a valid crontab expression" msgstr "* 请输入有效的 crontab 表达式" -#: xpack/plugins/change_auth_plan/forms.py:116 +#: xpack/plugins/change_auth_plan/forms.py:117 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:60 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:81 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17 msgid "Periodic perform" msgstr "定时执行" -#: xpack/plugins/change_auth_plan/forms.py:120 +#: xpack/plugins/change_auth_plan/forms.py:121 msgid "" "Tips: The username of the user on the asset to be modified. if the user " "exists, change the password; If the user does not exist, create the user." @@ -5339,11 +5337,11 @@ msgstr "" "提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果" "用户不存在,则创建用户。" -#: xpack/plugins/change_auth_plan/forms.py:124 +#: xpack/plugins/change_auth_plan/forms.py:125 msgid "Tips: (Units: hour)" msgstr "提示:(单位: 时)" -#: xpack/plugins/change_auth_plan/forms.py:125 +#: xpack/plugins/change_auth_plan/forms.py:126 msgid "" "eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux " "crontab expressions <min hour day month week> (<a href='https://tool.lu/" @@ -5396,10 +5394,6 @@ msgstr "定期执行" msgid "Password rules" msgstr "密码规则" -#: xpack/plugins/change_auth_plan/models.py:213 -msgid "For security, do not change {} user's password" -msgstr "* 为了安全,禁止更改 {} 用户的密码" - #: xpack/plugins/change_auth_plan/models.py:217 msgid "Assets is empty, please add the asset" msgstr "资产为空,请添加资产" @@ -5768,7 +5762,7 @@ msgid "Interface settings" msgstr "界面设置" #: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:25 +#: xpack/plugins/interface/views.py:24 xpack/plugins/interface/views.py:25 msgid "Interface setting" msgstr "界面设置" @@ -5791,26 +5785,22 @@ msgstr "恢复默认成功!" msgid "Restore default failed." msgstr "恢复默认失败!" -#: xpack/plugins/interface/views.py:24 -msgid "Interface" -msgstr "界面" - #: xpack/plugins/interface/views.py:51 msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:98 +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 #: xpack/plugins/license/templates/license/license_detail.html:55 #: xpack/plugins/license/views.py:32 msgid "License" msgstr "许可证" -#: xpack/plugins/license/models.py:75 +#: xpack/plugins/license/models.py:74 msgid "Standard edition" msgstr "标准版" -#: xpack/plugins/license/models.py:77 +#: xpack/plugins/license/models.py:76 msgid "Enterprise edition" msgstr "企业版" @@ -5898,7 +5888,9 @@ msgstr "无效的许可证" msgid "Admin" msgstr "管理员" -#: xpack/plugins/orgs/meta.py:8 +#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26 +#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60 +#: xpack/plugins/orgs/views.py:77 msgid "Organizations" msgstr "组织管理" @@ -5915,11 +5907,6 @@ msgstr "添加管理员" msgid "Create organization " msgstr "创建组织" -#: xpack/plugins/orgs/views.py:26 xpack/plugins/orgs/views.py:43 -#: xpack/plugins/orgs/views.py:60 xpack/plugins/orgs/views.py:77 -msgid "Orgs" -msgstr "组织管理" - #: xpack/plugins/orgs/views.py:27 msgid "Org list" msgstr "组织列表" @@ -5949,6 +5936,15 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" +#~ msgid "* For security, please do not change root user's password" +#~ msgstr "* 为了安全,请不要更改root用户的密码" + +#~ msgid "Interface" +#~ msgstr "界面" + +#~ msgid "Orgs" +#~ msgstr "组织管理" + #~ msgid "Org" #~ msgstr "组织" diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py index d75e80b8f..2e3b851f7 100644 --- a/apps/perms/api/remote_app_permission.py +++ b/apps/perms/api/remote_app_permission.py @@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): else: return Response({"error": serializer.errors}) + diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 4d59c13a7..9e32055d4 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -7,148 +7,57 @@ from rest_framework.generics import ( ) from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser -from common.tree import TreeNodeSerializer -from ..utils import ( - AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, - RemoteAppPermissionUtil, -) -from ..hands import ( - UserGroup, Node, NodeSerializer, RemoteAppSerializer, -) +from ..hands import UserGroup from .. import serializers, const +from .user_permission import ( + UserGrantedAssetsApi, UserGrantedNodesApi, UserGrantedNodesWithAssetsApi, + UserGrantedNodesWithAssetsAsTreeApi, UserGrantedNodeAssetsApi, +) __all__ = [ 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodesWithAssetsAsTreeApi', - 'UserGroupGrantedRemoteAppsApi', ] -class UserGroupGrantedAssetsApi(ListAPIView): - permission_classes = (IsOrgAdmin,) - serializer_class = serializers.AssetGrantedSerializer - - def get_queryset(self): +class UserGroupGrantedAssetsApi(UserGrantedAssetsApi): + def get_object(self): user_group_id = self.kwargs.get('pk', '') - queryset = [] - - if not user_group_id: - return queryset - user_group = get_object_or_404(UserGroup, id=user_group_id) - util = AssetPermissionUtil(user_group) - assets = util.get_assets() - for k, v in assets.items(): - k.system_users_granted = v - queryset.append(k) - return queryset + return user_group -class UserGroupGrantedNodesApi(ListAPIView): - permission_classes = (IsOrgAdmin,) - serializer_class = NodeSerializer - - def get_queryset(self): - group_id = self.kwargs.get('pk', '') - queryset = [] - - if group_id: - group = get_object_or_404(UserGroup, id=group_id) - util = AssetPermissionUtil(group) - nodes = util.get_nodes_with_assets() - return nodes.keys() - return queryset +class UserGroupGrantedNodesApi(UserGrantedNodesApi): + def get_object(self): + user_group_id = self.kwargs.get('pk', '') + user_group = get_object_or_404(UserGroup, id=user_group_id) + return user_group -class UserGroupGrantedNodesWithAssetsApi(ListAPIView): +class UserGroupGrantedNodesWithAssetsApi(UserGrantedNodesWithAssetsApi): permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeGrantedSerializer - def get_queryset(self): + def get_object(self): user_group_id = self.kwargs.get('pk', '') - queryset = [] - - if not user_group_id: - return queryset - user_group = get_object_or_404(UserGroup, id=user_group_id) - util = AssetPermissionUtil(user_group) - nodes = util.get_nodes_with_assets() - for node, _assets in nodes.items(): - assets = _assets.keys() - for asset, system_users in _assets.items(): - asset.system_users_granted = system_users - node.assets_granted = assets - queryset.append(node) - return queryset + return user_group -class UserGroupGrantedNodesWithAssetsAsTreeApi(ListAPIView): - serializer_class = TreeNodeSerializer - permission_classes = (IsOrgAdminOrAppUser,) - show_assets = True - system_user_id = None - - def get(self, request, *args, **kwargs): - self.show_assets = request.query_params.get('show_assets', '1') == '1' - self.system_user_id = request.query_params.get('system_user') - return super().get(request, *args, **kwargs) - - def get_queryset(self): +class UserGroupGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsAsTreeApi): + def get_object(self): user_group_id = self.kwargs.get('pk', '') - queryset = [] - group = get_object_or_404(UserGroup, id=user_group_id) - util = AssetPermissionUtil(group) - if self.system_user_id: - util.filter_permissions(system_users=self.system_user_id) - nodes = util.get_nodes_with_assets() - for node, assets in nodes.items(): - data = parse_node_to_tree_node(node) - queryset.append(data) - if not self.show_assets: - continue - for asset, system_users in assets.items(): - data = parse_asset_to_tree_node(node, asset, system_users) - queryset.append(data) - queryset = sorted(queryset) - return queryset + user_group = get_object_or_404(UserGroup, id=user_group_id) + return user_group -class UserGroupGrantedNodeAssetsApi(ListAPIView): +class UserGroupGrantedNodeAssetsApi(UserGrantedNodeAssetsApi): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.AssetGrantedSerializer - def get_queryset(self): + def get_object(self): user_group_id = self.kwargs.get('pk', '') - node_id = self.kwargs.get('node_id') - user_group = get_object_or_404(UserGroup, id=user_group_id) - util = AssetPermissionUtil(user_group) - if str(node_id) == const.UNGROUPED_NODE_ID: - node = util.tree.ungrouped_node - else: - node = get_object_or_404(Node, id=node_id) - nodes = util.get_nodes_with_assets() - assets = nodes.get(node, []) - for asset, system_users in assets.items(): - asset.system_users_granted = system_users - return assets - - -# RemoteApp permission - -class UserGroupGrantedRemoteAppsApi(ListAPIView): - permission_classes = (IsOrgAdmin, ) - serializer_class = RemoteAppSerializer - - def get_queryset(self): - queryset = [] - user_group_id = self.kwargs.get('pk') - if not user_group_id: - return queryset - user_group = get_object_or_404(UserGroup, id=user_group_id) - util = RemoteAppPermissionUtil(user_group) - queryset = util.get_remote_apps() - return queryset + return user_group diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 3eafc28e0..8d55ee49c 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -2,40 +2,44 @@ # import time import traceback +import uuid from hashlib import md5 from django.core.cache import cache from django.conf import settings +from django.db.models import Q from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response from rest_framework.generics import ( ListAPIView, get_object_or_404, RetrieveAPIView ) +from django.utils.translation import ugettext as _ from rest_framework.pagination import LimitOffsetPagination from common.permissions import IsValidUser, IsOrgAdminOrAppUser from common.tree import TreeNodeSerializer -from common.utils import get_logger +from common.utils import get_logger, get_object_or_none from ..utils import ( - AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, + AssetPermissionUtil, ParserNode, ) +from .. import const from ..hands import User, Asset, Node, SystemUser, NodeSerializer -from .. import serializers, const -from ..mixins import AssetsFilterMixin +from .. import serializers from ..models import Action + logger = get_logger(__name__) __all__ = [ 'UserGrantedAssetsApi', 'UserGrantedNodesApi', 'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi', - 'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi', + 'ValidateUserAssetPermissionApi', 'UserGrantedNodesAsTreeApi', 'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi', ] class UserPermissionCacheMixin: cache_policy = '0' - RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_{}' + RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}' CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME _object = None @@ -130,12 +134,61 @@ class UserPermissionCacheMixin: return resp -class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView): +class GrantAssetsMixin: + serializer_class = serializers.AssetGrantedSerializer + + def get_serializer(self, queryset, many=True): + assets_ids = [] + system_users_ids = set() + for asset in queryset: + assets_ids.append(asset["id"]) + system_users_ids.update(set(asset["system_users"])) + assets = Asset.objects.filter(id__in=assets_ids).only( + *self.serializer_class.Meta.only_fields + ) + assets_map = {asset.id: asset for asset in assets} + system_users = SystemUser.objects.filter(id__in=system_users_ids).only( + *self.serializer_class.system_users_only_fields + ) + system_users_map = {s.id: s for s in system_users} + data = [] + for item in queryset: + i = item["id"] + asset = assets_map.get(i) + if not asset: + continue + + _system_users = item["system_users"] + system_users_granted = [] + for sid, action in _system_users.items(): + system_user = system_users_map.get(sid) + if not system_user: + continue + system_user.actions = action + system_users_granted.append(system_user) + asset.system_users_granted = system_users_granted + data.append(asset) + return super().get_serializer(data, many=True) + + def search_queryset(self, assets): + search = self.request.query_params.get("search") + if not search: + return assets + + assets_map = {asset['id']: asset for asset in assets} + assets_ids = set(assets_map.keys()) + assets_ids_search = Asset.objects.filter(id__in=assets_ids).filter( + Q(hostname__icontains=search) | Q(ip__icontains=search) + ).values_list('id', flat=True) + assets_ids &= set(assets_ids_search) + return [assets_map.get(asset_id) for asset_id in assets_ids] + + +class UserGrantedAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView): """ 用户授权的所有资产 """ permission_classes = (IsOrgAdminOrAppUser,) - serializer_class = serializers.AssetGrantedSerializer pagination_class = LimitOffsetPagination def get_object(self): @@ -147,17 +200,10 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV return user def get_queryset(self): - queryset = [] user = self.get_object() util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - assets = util.get_assets() - for asset, system_users in assets.items(): - system_users_granted = [] - for system_user, actions in system_users.items(): - system_user.actions = actions - system_users_granted.append(system_user) - asset.system_users_granted = system_users_granted - queryset.append(asset) + queryset = util.get_assets() + queryset = self.search_queryset(queryset) return queryset def get_permissions(self): @@ -166,12 +212,39 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV return super().get_permissions() -class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView): +class NodesWithUngroupMixin: + util = None + + @staticmethod + def get_ungrouped_node(ungroup_key): + return Node(key=ungroup_key, id=const.UNGROUPED_NODE_ID, + value=_("ungrouped")) + + @staticmethod + def get_empty_node(): + return Node(key=const.EMPTY_NODE_KEY, id=const.EMPTY_NODE_ID, + value=_("empty")) + + def add_ungrouped_nodes(self, node_map, node_keys): + ungroup_key = '1:-1' + for key in node_keys: + if key.endswith('-1'): + ungroup_key = key + break + ungroup_node = self.get_ungrouped_node(ungroup_key) + empty_node = self.get_empty_node() + node_map[ungroup_key] = ungroup_node + node_map[const.EMPTY_NODE_KEY] = empty_node + + +class UserGrantedNodesApi(UserPermissionCacheMixin, NodesWithUngroupMixin, ListAPIView): """ 查询用户授权的所有节点的API """ permission_classes = (IsOrgAdminOrAppUser,) serializer_class = NodeSerializer + pagination_class = LimitOffsetPagination + only_fields = NodeSerializer.Meta.only_fields def get_object(self): user_id = self.kwargs.get('pk', '') @@ -181,11 +254,31 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView): user = self.request.user return user + def get_nodes(self, nodes_with_assets): + node_keys = [n["key"] for n in nodes_with_assets] + nodes = Node.objects.filter(key__in=node_keys).only( + *self.only_fields + ) + nodes_map = {n.key: n for n in nodes} + self.add_ungrouped_nodes(nodes_map, node_keys) + + _nodes = [] + for n in nodes_with_assets: + key = n["key"] + node = nodes_map.get(key) + node._assets_amount = n["assets_amount"] + _nodes.append(node) + return _nodes + + def get_serializer(self, nodes_with_assets, many=True): + nodes = self.get_nodes(nodes_with_assets) + return super().get_serializer(nodes, many=True) + def get_queryset(self): user = self.get_object() - util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - nodes = util.get_nodes() - return nodes + self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy) + nodes_with_assets = self.util.get_nodes_with_assets() + return nodes_with_assets def get_permissions(self): if self.kwargs.get('pk') is None: @@ -193,12 +286,30 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView): return super().get_permissions() -class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView): +class UserGrantedNodesAsTreeApi(UserGrantedNodesApi): + serializer_class = TreeNodeSerializer + only_fields = ParserNode.nodes_only_fields + + def get_serializer(self, nodes_with_assets, many=True): + nodes = self.get_nodes(nodes_with_assets) + queryset = [] + for node in nodes: + data = ParserNode.parse_node_to_tree_node(node) + queryset.append(data) + return self.get_serializer_class()(queryset, many=many) + + +class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, NodesWithUngroupMixin, ListAPIView): """ 用户授权的节点并带着节点下资产的api """ permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.NodeGrantedSerializer + pagination_class = LimitOffsetPagination + + nodes_only_fields = serializers.NodeGrantedSerializer.Meta.only_fields + assets_only_fields = serializers.NodeGrantedSerializer.assets_only_fields + system_users_only_fields = serializers.NodeGrantedSerializer.system_users_only_fields def get_object(self): user_id = self.kwargs.get('pk', '') @@ -208,78 +319,127 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, user = get_object_or_404(User, id=user_id) return user - def get_queryset(self): + def get_maps(self, nodes_items): + """ + 查库,并加入构造的ungrouped节点 + :return: + ({asset.id: asset}, {node.key: node}, {system_user.id: system_user}) + """ + _nodes_keys = set() + _assets_ids = set() + _system_users_ids = set() + for item in nodes_items: + _nodes_keys.add(item["key"]) + _assets_ids.update(set(item["assets"].keys())) + for _system_users_id in item["assets"].values(): + _system_users_ids.update(_system_users_id.keys()) + + _nodes = Node.objects.filter(key__in=_nodes_keys).only( + *self.nodes_only_fields + ) + _assets = Asset.objects.filter(id__in=_assets_ids).only( + *self.assets_only_fields + ) + _system_users = SystemUser.objects.filter(id__in=_system_users_ids).only( + *self.system_users_only_fields + ) + _nodes_map = {n.key: n for n in _nodes} + self.add_ungrouped_nodes(_nodes_map, _nodes_keys) + _assets_map = {a.id: a for a in _assets} + _system_users_map = {s.id: s for s in _system_users} + return _nodes_map, _assets_map, _system_users_map + + def get_serializer_queryset(self, nodes_items): + """ + 将id转为object,同时构造queryset + :param nodes_items: + [ + { + 'key': node.key, + 'assets_amount': 10 + 'assets': { + asset.id: { + system_user.id: actions, + }, + }, + }, + ] + """ queryset = [] - user = self.get_object() - util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - nodes = util.get_nodes_with_assets() - for node, _assets in nodes.items(): - assets = _assets.keys() - for k, v in _assets.items(): - k.system_users_granted = v - node.assets_granted = assets + _node_map, _assets_map, _system_users_map = self.get_maps(nodes_items) + for item in nodes_items: + key = item["key"] + node = _node_map.get(key) + if not node: + continue + node._assets_amount = item["assets_amount"] + assets_granted = [] + for asset_id, system_users_ids_action in item["assets"].items(): + asset = _assets_map.get(asset_id) + if not asset: + continue + system_user_granted = [] + for system_user_id, action in system_users_ids_action.items(): + system_user = _system_users_map.get(system_user_id) + if not system_user: + continue + system_user.actions = action + system_user_granted.append(system_user) + asset.system_users_granted = system_user_granted + assets_granted.append(asset) + node.assets_granted = assets_granted queryset.append(node) return queryset - def sort_assets(self, queryset): - for node in queryset: - node.assets_granted = super().sort_assets(node.assets_granted) - return queryset - - def get_permissions(self): - if self.kwargs.get('pk') is None: - self.permission_classes = (IsValidUser,) - return super().get_permissions() - - -class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView): - serializer_class = TreeNodeSerializer - permission_classes = (IsOrgAdminOrAppUser,) - show_assets = True - system_user_id = None - - def get_permissions(self): - if self.kwargs.get('pk') is None: - self.permission_classes = (IsValidUser,) - return super().get_permissions() - - def get_object(self): - user_id = self.kwargs.get('pk', '') - if not user_id: - user = self.request.user - else: - user = get_object_or_404(User, id=user_id) - return user + def get_serializer(self, nodes_items, many=True): + queryset = self.get_serializer_queryset(nodes_items) + return super().get_serializer(queryset, many=many) def get_queryset(self): - queryset = [] - self.show_assets = self.request.query_params.get('show_assets', '1') == '1' - self.system_user_id = self.request.query_params.get('system_user') user = self.get_object() - util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - if self.system_user_id: - util.filter_permissions( - system_users=self.system_user_id + self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy) + system_user_id = self.request.query_params.get('system_user') + if system_user_id: + self.util.filter_permissions( + system_users=system_user_id ) - nodes = util.get_nodes_with_assets() - for node, assets in nodes.items(): - data = parse_node_to_tree_node(node) + nodes_items = self.util.get_nodes_with_assets() + return nodes_items + + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() + + +class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsApi): + serializer_class = TreeNodeSerializer + permission_classes = (IsOrgAdminOrAppUser,) + system_user_id = None + nodes_only_fields = ParserNode.nodes_only_fields + assets_only_fields = ParserNode.assets_only_fields + system_users_only_fields = ParserNode.system_users_only_fields + + def get_serializer(self, nodes_items, many=True): + _queryset = super().get_serializer_queryset(nodes_items) + queryset = [] + + for node in _queryset: + data = ParserNode.parse_node_to_tree_node(node) queryset.append(data) - if not self.show_assets: - continue - for asset, system_users in assets.items(): - data = parse_asset_to_tree_node(node, asset, system_users) + for asset in node.assets_granted: + system_users = asset.system_users_granted + data = ParserNode.parse_asset_to_tree_node(node, asset, system_users) queryset.append(data) queryset = sorted(queryset) - return queryset + return self.serializer_class(queryset, many=True) -class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView): +class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView): """ 查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产 """ permission_classes = (IsOrgAdminOrAppUser,) - serializer_class = serializers.AssetGrantedSerializer pagination_class = LimitOffsetPagination def get_object(self): @@ -291,25 +451,31 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List user = self.request.user return user - def get_queryset(self): - user = self.get_object() + def get_node_key(self): node_id = self.kwargs.get('node_id') - util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - nodes = util.get_nodes_with_assets() if str(node_id) == const.UNGROUPED_NODE_ID: - node = util.tree.ungrouped_node + key = self.util.tree.ungrouped_key elif str(node_id) == const.EMPTY_NODE_ID: - node = util.tree.empty_node + key = const.EMPTY_NODE_KEY else: node = get_object_or_404(Node, id=node_id) - if node == util.tree.root_node: - assets = util.get_assets() - else: - assets = nodes.get(node, {}) - for asset, system_users in assets.items(): - asset.system_users_granted = system_users + key = node.key + return key - assets = list(assets.keys()) + def get_queryset(self): + user = self.get_object() + self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy) + key = self.get_node_key() + nodes_items = self.util.get_nodes_with_assets() + assets_system_users = {} + for item in nodes_items: + if item["key"] == key: + assets_system_users = item["assets"] + break + assets = [] + for asset_id, system_users in assets_system_users.items(): + assets.append({"id": asset_id, "system_users": system_users}) + assets = self.search_queryset(assets) return assets def get_permissions(self): @@ -318,92 +484,6 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List return super().get_permissions() -class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView): - """ - 获取用户自己授权节点下子节点的api - """ - permission_classes = (IsValidUser,) - serializer_class = serializers.AssetPermissionNodeSerializer - - def get_object(self): - return self.request.user - - def get_children_queryset(self): - user = self.get_object() - util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - node_id = self.request.query_params.get('id') - nodes_granted = util.get_nodes_with_assets() - if not nodes_granted: - return [] - root_nodes = [node for node in nodes_granted.keys() if node.is_root()] - - queryset = [] - if node_id and node_id in [str(node.id) for node in nodes_granted]: - node = [node for node in nodes_granted if str(node.id) == node_id][0] - elif len(root_nodes) == 1: - node = root_nodes[0] - node.assets_amount = len(nodes_granted[node]) - queryset.append(node) - else: - for node in root_nodes: - node.assets_amount = len(nodes_granted[node]) - queryset.append(node) - return queryset - - children = [] - for child in node.get_children(): - if child in nodes_granted: - child.assets_amount = len(nodes_granted[node]) - children.append(child) - children = sorted(children, key=lambda x: x.value) - queryset.extend(children) - fake_nodes = [] - for asset, system_users in nodes_granted[node].items(): - fake_node = asset.as_node() - fake_node.assets_amount = 0 - system_users = [s for s in system_users if asset.has_protocol(s.protocol)] - fake_node.asset.system_users_granted = system_users - fake_node.key = node.key + ':0' - fake_nodes.append(fake_node) - fake_nodes = sorted(fake_nodes, key=lambda x: x.value) - queryset.extend(fake_nodes) - return queryset - - def get_search_queryset(self, keyword): - user = self.get_object() - util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - nodes_granted = util.get_nodes_with_assets() - queryset = [] - for node, assets in nodes_granted.items(): - matched_assets = [] - node_matched = node.value.lower().find(keyword.lower()) >= 0 - asset_has_matched = False - for asset, system_users in assets.items(): - asset_matched = (asset.hostname.lower().find(keyword.lower()) >= 0) \ - or (asset.ip.find(keyword.lower()) >= 0) - if node_matched or asset_matched: - asset_has_matched = True - fake_node = asset.as_node() - fake_node.assets_amount = 0 - system_users = [s for s in system_users if - asset.has_protocol(s.protocol)] - fake_node.asset.system_users_granted = system_users - fake_node.key = node.key + ':0' - matched_assets.append(fake_node) - if asset_has_matched: - node.assets_amount = len(matched_assets) - queryset.append(node) - queryset.extend(sorted(matched_assets, key=lambda x: x.value)) - return queryset - - def get_queryset(self): - keyword = self.request.query_params.get('search') - if keyword: - return self.get_search_queryset(keyword) - else: - return self.get_children_queryset() - - class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): permission_classes = (IsOrgAdminOrAppUser,) @@ -412,24 +492,24 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): asset_id = request.query_params.get('asset_id', '') system_id = request.query_params.get('system_user_id', '') action_name = request.query_params.get('action_name', '') + cache_policy = self.request.query_params.get("cache_policy", '0') + + try: + asset_id = uuid.UUID(asset_id) + system_id = uuid.UUID(system_id) + except ValueError: + return Response({'msg': False}, status=403) user = get_object_or_404(User, id=user_id) - asset = get_object_or_404(Asset, id=asset_id) - su = get_object_or_404(SystemUser, id=system_id) - - util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - granted_assets = util.get_assets() - granted_system_users = granted_assets.get(asset, {}) - - if su not in granted_system_users: - return Response({'msg': False}, status=403) - - action = granted_system_users[su] - choices = Action.value_to_choices(action) - if action_name not in choices: - return Response({'msg': False}, status=403) - - return Response({'msg': True}, status=200) + util = AssetPermissionUtil(user, cache_policy=cache_policy) + assets = util.get_assets() + for asset in assets: + if asset_id == asset["id"]: + action = asset["system_users"].get(system_id) + if action and action_name in Action.value_to_choices(action): + return Response({'msg': True}, status=200) + break + return Response({'msg': False}, status=403) class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView): diff --git a/apps/perms/api/user_remote_app_permission.py b/apps/perms/api/user_remote_app_permission.py index 4cbfcd9b4..5c816cffb 100644 --- a/apps/perms/api/user_remote_app_permission.py +++ b/apps/perms/api/user_remote_app_permission.py @@ -13,13 +13,13 @@ from ..utils import ( RemoteAppPermissionUtil, construct_remote_apps_tree_root, parse_remote_app_to_tree_node, ) -from ..hands import User, RemoteApp, RemoteAppSerializer +from ..hands import User, RemoteApp, RemoteAppSerializer, UserGroup from ..mixins import RemoteAppFilterMixin __all__ = [ 'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi', - 'UserGrantedRemoteAppsAsTreeApi', + 'UserGrantedRemoteAppsAsTreeApi', 'UserGroupGrantedRemoteAppsApi', ] @@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView): if remote_app not in remote_apps: return Response({'msg': False}, status=403) return Response({'msg': True}, status=200) + + +# RemoteApp permission + +class UserGroupGrantedRemoteAppsApi(ListAPIView): + permission_classes = (IsOrgAdminOrAppUser, ) + serializer_class = RemoteAppSerializer + + def get_queryset(self): + queryset = [] + user_group_id = self.kwargs.get('pk') + if not user_group_id: + return queryset + user_group = get_object_or_404(UserGroup, id=user_group_id) + util = RemoteAppPermissionUtil(user_group) + queryset = util.get_remote_apps() + return queryset diff --git a/apps/perms/const.py b/apps/perms/const.py index b18580747..4ccab5e38 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -3,3 +3,4 @@ UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002" EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003" +EMPTY_NODE_KEY = "1:-2" diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index d5fafc63a..cb3e37768 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import date_expired_default, set_or_append_attr_bulk from orgs.mixins import OrgModelMixin +from assets.models import Asset, SystemUser, Node from .base import BasePermission @@ -85,7 +86,11 @@ class AssetPermission(BasePermission): @classmethod def get_queryset_with_prefetch(cls): - return cls.objects.all().valid().prefetch_related('nodes', 'assets', 'system_users') + return cls.objects.all().valid().prefetch_related( + models.Prefetch('nodes', queryset=Node.objects.all().only('key')), + models.Prefetch('assets', queryset=Asset.objects.all().only('id')), + models.Prefetch('system_users', queryset=SystemUser.objects.all().only('id')) + ) def get_all_assets(self): assets = set(self.assets.all()) diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 639ba7140..bdf2e1fdc 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -2,16 +2,16 @@ # from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ -from assets.models import Node, SystemUser -from assets.serializers import AssetSerializer - +from assets.models import Node, SystemUser, Asset +from assets.serializers import ProtocolsField from .asset_permission import ActionsField __all__ = [ - 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', + 'GrantedNodeSerializer', 'NodeGrantedSerializer', 'AssetGrantedSerializer', - 'ActionsSerializer', + 'ActionsSerializer', 'AssetSystemUserSerializer', ] @@ -23,87 +23,56 @@ class AssetSystemUserSerializer(serializers.ModelSerializer): class Meta: model = SystemUser - fields = ( - 'id', 'name', 'username', 'priority', "actions", + only_fields = ( + 'id', 'name', 'username', 'priority', 'protocol', 'login_mode', ) + fields = list(only_fields) + ["actions"] + read_only_fields = fields -class AssetGrantedSerializer(AssetSerializer): +class AssetGrantedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 """ + protocols = ProtocolsField(label=_('Protocols'), required=False, read_only=True) system_users_granted = AssetSystemUserSerializer(many=True, read_only=True) system_users_join = serializers.SerializerMethodField() + system_users_only_fields = AssetSystemUserSerializer.Meta.only_fields + + class Meta: + model = Asset + only_fields = [ + "id", "hostname", "ip", "protocols", "os", 'domain', + "platform", "org_id", + ] + fields = only_fields + ['system_users_granted', 'system_users_join', "org_name"] + read_only_fields = fields @staticmethod def get_system_users_join(obj): system_users = [s.username for s in obj.system_users_granted] return ', '.join(system_users) - def get_field_names(self, declared_fields, info): - fields = ( - "id", "hostname", "ip", "protocols", - "system_users_granted", "is_active", "system_users_join", "os", - 'domain', "platform", "comment", "org_id", "org_name", - ) - return fields - - -class AssetPermissionNodeSerializer(serializers.ModelSerializer): - asset = AssetGrantedSerializer(required=False) - assets_amount = serializers.SerializerMethodField() - - tree_id = serializers.SerializerMethodField() - tree_parent = serializers.SerializerMethodField() - - class Meta: - model = Node - fields = [ - 'id', 'key', 'value', 'asset', 'is_node', 'org_id', - 'tree_id', 'tree_parent', 'assets_amount', - ] - - @staticmethod - def get_assets_amount(obj): - return obj.assets_amount - - @staticmethod - def get_tree_id(obj): - return obj.key - - @staticmethod - def get_tree_parent(obj): - return obj.parent_key - class NodeGrantedSerializer(serializers.ModelSerializer): """ 授权资产组 """ assets_granted = AssetGrantedSerializer(many=True, read_only=True) - assets_amount = serializers.SerializerMethodField() - parent = serializers.SerializerMethodField() - name = serializers.SerializerMethodField() + assets_amount = serializers.ReadOnlyField() + name = serializers.ReadOnlyField(source='value') + + assets_only_fields = AssetGrantedSerializer.Meta.only_fields + system_users_only_fields = AssetGrantedSerializer.system_users_only_fields class Meta: model = Node - fields = [ - 'id', 'key', 'name', 'value', 'parent', - 'assets_granted', 'assets_amount', 'org_id', + only_fields = ['id', 'key', 'value', "org_id"] + fields = only_fields + [ + 'name', 'assets_granted', 'assets_amount', ] - - @staticmethod - def get_assets_amount(obj): - return len(obj.assets_granted) - - @staticmethod - def get_name(obj): - return obj.name - - @staticmethod - def get_parent(obj): - return obj.parent.id + read_only_fields = fields class GrantedNodeSerializer(serializers.ModelSerializer): @@ -112,6 +81,7 @@ class GrantedNodeSerializer(serializers.ModelSerializer): fields = [ 'id', 'name', 'key', 'value', ] + read_only_fields = fields class ActionsSerializer(serializers.Serializer): diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 611661c5d..459426f06 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -1,7 +1,8 @@ # coding:utf-8 -from django.urls import path +from django.urls import path, re_path from rest_framework import routers +from common import api as capi from .. import api app_name = 'perms' @@ -12,26 +13,36 @@ router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remot asset_permission_urlpatterns = [ - # 查询某个用户授权的资产和资产组 - path('user/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view()), + # Assets path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'), - path('user/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'), - path('user/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), - path('user/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'), - path('user/nodes/children/', api.UserGrantedNodeChildrenApi.as_view(), name='my-node-children'), - path('user/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), - path('user/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'), - path('user/<uuid:pk>/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), - path('user/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), - path('user/<uuid:pk>/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-assets-as-tree'), - path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-assets-as-tree'), + path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'), + + # Node as tree + path('users/<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'), + path('users/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'), + + # Nodes + path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), + path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'), + + # Node assets + path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), + path('users/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'), + + # Node with assets + path('users/<uuid:pk>/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), + path('users/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), + + # Node assets as tree + path('users/<uuid:pk>/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-assets-as-tree'), + path('users/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-assets-as-tree'), # 查询某个用户组授权的资产和资产组 - path('user-group/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), - path('user-group/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), - path('user-group/<uuid:pk>/nodes-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'), - path('user-group/<uuid:pk>/nodes-assets/tree/', api.UserGroupGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-group-nodes-assets-as-tree'), - path('user-group/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), + path('user-groups/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), + path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), + path('user-groups/<uuid:pk>/nodes-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'), + path('user-groups/<uuid:pk>/nodes-assets/tree/', api.UserGroupGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-group-nodes-assets-as-tree'), + path('user-groups/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), # 用户和资产授权变更 path('asset-permissions/<uuid:pk>/user/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'), @@ -40,25 +51,25 @@ asset_permission_urlpatterns = [ path('asset-permissions/<uuid:pk>/asset/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'), # 验证用户是否有某个资产和系统用户的权限 - path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), - path('asset-permission/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), + path('asset-permissions/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), + path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), ] remote_app_permission_urlpatterns = [ # 查询用户授权的RemoteApp - path('user/<uuid:pk>/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'), - path('user/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'), + path('users/<uuid:pk>/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'), + path('users/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'), # 获取用户授权的RemoteApp树 - path('user/<uuid:pk>/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='user-remote-apps-as-tree'), - path('user/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='my-remote-apps-as-tree'), + path('users/<uuid:pk>/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='user-remote-apps-as-tree'), + path('users/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='my-remote-apps-as-tree'), # 查询用户组授权的RemoteApp - path('user-group/<uuid:pk>/remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'), + path('user-groups/<uuid:pk>/remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'), # 校验用户对RemoteApp的权限 - path('remote-app-permission/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'), + path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'), # 用户和RemoteApp变更 path('remote-app-permissions/<uuid:pk>/user/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'), @@ -67,7 +78,11 @@ remote_app_permission_urlpatterns = [ path('remote-app-permissions/<uuid:pk>/remote-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'), ] -urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns +old_version_urlpatterns = [ + re_path('(?P<resource>user|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api) +] + +urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns + old_version_urlpatterns urlpatterns += router.urls diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 3a8c2d387..c437753b6 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -1,80 +1,65 @@ # coding: utf-8 +import time import uuid from collections import defaultdict import json from hashlib import md5 -import time import itertools from django.utils import timezone from django.db.models import Q from django.core.cache import cache from django.conf import settings -from django.utils.translation import ugettext as _ from orgs.utils import set_to_root_org -from common.utils import get_logger +from common.utils import get_logger, timeit from common.tree import TreeNode +from assets.utils import NodeUtil from .. import const from ..models import AssetPermission, Action from ..hands import Node, Asset -from assets.utils import NodeUtil +from .stack import PermSystemUserNodeUtil, PermAssetsAmountUtil logger = get_logger(__file__) __all__ = [ 'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets', - 'parse_asset_to_tree_node', 'parse_node_to_tree_node', + 'ParserNode', ] -class TreeNodeCounter(NodeUtil): - def __init__(self, nodes): - self.__nodes = nodes - super().__init__(with_assets_amount=True) - - def get_queryset(self): - return self.__nodes - - -def timeit(func): - def wrapper(*args, **kwargs): - logger.debug("Start call: {}".format(func.__name__)) - now = time.time() - result = func(*args, **kwargs) - using = time.time() - now - logger.debug("Call {} end, using: {:.2}s".format(func.__name__, using)) - return result - return wrapper - - class GenerateTree: def __init__(self): """ nodes = { - "<node1>": { + node.key: { "system_users": { - "system_user": action, - "system_user2": action, + system_user.id: actions, }, - "assets": set([<asset_instance>]), - } + "assets": set([asset.id,]), + }, } assets = { - "<asset_instance2>": { - "system_user": action, - "system_user2": action, + asset.id: { + system_user.id: actions, }, } """ self._node_util = None - self.nodes = defaultdict(lambda: {"system_users": defaultdict(int), "assets": set(), "assets_amount": 0}) + self.nodes = defaultdict(lambda: { + "system_users": defaultdict(int), "assets": set(), + "assets_amount": 0, "all_assets": set(), + }) self.assets = defaultdict(lambda: defaultdict(int)) self._root_node = None self._ungroup_node = None self._nodes_with_assets = None + self._all_assets_nodes_key = None + self._asset_counter = 0 + self._system_user_counter = 0 + self._nodes_assets_counter = 0 @property def node_util(self): @@ -82,115 +67,160 @@ class GenerateTree: self._node_util = NodeUtil() return self._node_util + @staticmethod + def key_sort(key): + key_list = [int(i) for i in key.split(':')] + return len(key_list), key_list + @property - def root_node(self): + def root_key(self): if self._root_node: return self._root_node - all_nodes = self.nodes.keys() + all_keys = self.nodes.keys() # 如果没有授权节点,就放到默认的根节点下 - if not all_nodes: + if not all_keys: return None - root_node = min(all_nodes) - self._root_node = root_node - return root_node + root_key = min(all_keys, key=self.key_sort) + self._root_key = root_key + return root_key @property - def ungrouped_node(self): + def all_assets_nodes_keys(self): + if not self._all_assets_nodes_key: + self._all_assets_nodes_key = Asset.get_all_nodes_keys() + return self._all_assets_nodes_key + + @property + def ungrouped_key(self): if self._ungroup_node: return self._ungroup_node - node_id = const.UNGROUPED_NODE_ID - if self.root_node: - node_key = "{}:{}".format(self.root_node.key, self.root_node.child_mark) + if self.root_key: + node_key = "{}:{}".format(self.root_key, '-1') else: - node_key = '0:0' - node_value = _("Default") - node = Node(id=node_id, key=node_key, value=node_value) - self.add_node(node, {}) - self._ungroup_node = node - return node + node_key = '1:-1' + self._ungroup_node = node_key + return node_key - @property - def empty_node(self): - node_id = const.EMPTY_NODE_ID - value = _('Empty') - node = Node(id=node_id, value=value) - return node + @timeit + def add_assets_without_system_users(self, assets_ids): + for asset_id in assets_ids: + self.add_asset(asset_id, {}) - #@timeit - def add_assets_without_system_users(self, assets): - for asset in assets: - self.add_asset(asset, {}) + @timeit + def add_assets(self, assets_ids_with_system_users): + for asset_id, system_users_ids in assets_ids_with_system_users.items(): + self.add_asset(asset_id, system_users_ids) - #@timeit - def add_assets(self, assets): - for asset, system_users in assets.items(): - self.add_asset(asset, system_users) + # @timeit + def add_asset(self, asset_id, system_users_ids=None): + """ + :param asset_id: + :param system_users_ids: {system_user.id: actions, } + :return: + """ + if not system_users_ids: + system_users_ids = defaultdict(int) - # #@timeit - def add_asset(self, asset, system_users=None): - nodes = asset.nodes.all() - nodes = self.node_util.get_nodes_by_queryset(nodes) - if not system_users: - system_users = defaultdict(int) - else: - system_users = {k: v for k, v in system_users.items()} - _system_users = self.assets[asset] - for system_user, action in _system_users.items(): - system_users[system_user] |= action + # 获取已有资产的系统用户和actions,并更新到最新系统用户信息中 + old_system_users_ids = self.assets[asset_id] + for system_user_id, action in old_system_users_ids.items(): + system_users_ids[system_user_id] |= action - # 获取父节点们 - parents = self.node_util.get_nodes_parents(nodes, with_self=True) - for node in parents: - _system_users = self.nodes[node]["system_users"] - self.nodes[node]["assets_amount"] += 1 - for system_user, action in _system_users.items(): - system_users[system_user] |= action - - # 过滤系统用户的协议 - system_users = {s: v for s, v in system_users.items() if asset.has_protocol(s.protocol)} - self.assets[asset] = system_users - - in_nodes = set(self.nodes.keys()) & set(nodes) + asset_nodes_keys = self.all_assets_nodes_keys.get(asset_id, []) + # {asset.id: [node.key, ], } + # 获取用户在的节点 + in_nodes = set(self.nodes.keys()) & set(asset_nodes_keys) if not in_nodes: - self.nodes[self.ungrouped_node]["assets_amount"] += 1 - self.nodes[self.ungrouped_node]["assets"].add(system_users) + self.nodes[self.ungrouped_key]["assets"].add(asset_id) + self.assets[asset_id] = system_users_ids return - for node in in_nodes: - self.nodes[node]["assets"].add(asset) + # 遍历用户应该在的节点 + for key in in_nodes: + # 把自己加入到树上的节点中 + self.nodes[key]["assets"].add(asset_id) + # 获取自己所在节点的系统用户,并添加进去 + node_system_users_ids = self.nodes[key]["system_users"] + for system_user_id, action in node_system_users_ids.items(): + system_users_ids[system_user_id] |= action + self.assets[asset_id] = system_users_ids - def add_node(self, node, system_users=None): - if not system_users: - system_users = defaultdict(int) - self.nodes[node]["system_users"] = system_users + def add_node(self, node_key, system_users_ids=None): + """ + :param node_key: node.key + :param system_users_ids: {system_user.id: actions,} + :return: + """ + if not system_users_ids: + system_users_ids = defaultdict(int) + self.nodes[node_key]["system_users"] = system_users_ids # 添加树节点 - #@timeit - def add_nodes(self, nodes): - _nodes = nodes.keys() - family = self.node_util.get_family(_nodes, with_children=True) - for node in family: - self.add_node(node, nodes.get(node, {})) + @timeit + def add_nodes(self, nodes_keys_with_system_users_ids): + """ + :param nodes_keys_with_system_users_ids: + {node.key: {system_user.id: actions,}, } + :return: + """ + util = PermSystemUserNodeUtil() + family = util.get_nodes_family_and_system_users(nodes_keys_with_system_users_ids) + for key, system_users in family.items(): + self.add_node(key, system_users) def get_assets(self): - return dict(self.assets) + """ + :return: + [ + {"id": asset.id, "system_users": {system_user.id: actions, }}, + ] + """ + assets = [] + for asset_id, system_users in self.assets.items(): + assets.append({"id": asset_id, "system_users": system_users}) + return assets - #@timeit + @timeit def get_nodes_with_assets(self): + """ + :return: + [ + { + 'key': node.key, + 'assets_amount': 10 + 'assets': { + asset.id: { + system_user.id: actions, + }, + }, + }, + ] + """ if self._nodes_with_assets: return self._nodes_with_assets - nodes = {} - for node, values in self.nodes.items(): - node._assets_amount = values["assets_amount"] - nodes[node] = {asset: self.assets.get(asset, {}) for asset in values["assets"]} + util = PermAssetsAmountUtil() + nodes_with_assets_amount = util.compute_nodes_assets_amount(self.nodes) + nodes = [] + for key, values in nodes_with_assets_amount.items(): + assets = {asset_id: self.assets.get(asset_id) for asset_id in values["assets"]} + nodes.append({ + "key": key, "assets": assets, + "assets_amount": values["assets_amount"] + }) # 如果返回空节点,页面构造授权资产树报错 if not nodes: - nodes[self.empty_node] = {} + nodes.append({ + "key": const.EMPTY_NODE_KEY, "assets": {}, "assets_amount": 0 + }) + nodes.sort(key=lambda n: self.key_sort(n["key"])) self._nodes_with_assets = nodes - return dict(nodes) + return nodes def get_nodes(self): - return list(self.nodes.keys()) + nodes = list(self.nodes.keys()) + if not nodes: + nodes.append(const.EMPTY_NODE_KEY) + return list(nodes) def get_user_permissions(user, include_group=True): @@ -228,8 +258,8 @@ def get_system_user_permissions(system_user): class AssetPermissionCacheMixin: - CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_' - CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_' + CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_V2_' + CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_V2_' CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh')) cache_policy = '1' @@ -283,6 +313,7 @@ class AssetPermissionCacheMixin: return self.get_cache_key('SYSTEM_USER') def get_resource_from_cache(self, resource): + logger.debug("Try get resource from cache") key_map = { "assets": self.asset_key, "nodes": self.node_key, @@ -294,18 +325,22 @@ class AssetPermissionCacheMixin: raise ValueError("Not a valid resource: {}".format(resource)) cached = cache.get(key) if not cached: + logger.debug("Not found resource cache, update it") self.update_cache() cached = cache.get(key) return cached def get_resource(self, resource): if self._is_using_cache(): + logger.debug("Using cache to get resource") return self.get_resource_from_cache(resource) elif self._is_refresh_cache(): + logger.debug("Need refresh cache") self.expire_cache() data = self.get_resource_from_cache(resource) return data else: + logger.debug("Not using cache get source") return self.get_resource_without_cache(resource) def get_resource_without_cache(self, resource): @@ -430,88 +465,91 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): self._permissions = permissions return permissions - #@timeit + @timeit def filter_permissions(self, **filters): filters_json = json.dumps(filters, sort_keys=True) self._permissions = self.permissions.filter(**filters) self._filter_id = md5(filters_json.encode()).hexdigest() - #@timeit + @timeit def get_nodes_direct(self): """ - 返回用户/组授权规则直接关联的节点 - :return: {node1: {system_user1: {'actions': set()},}} + 返回直接授权的节点, + 并将节点添加到tree.nodes中,并将节点下的资产添加到tree.assets中 + :return: + {node.key: {system_user.id: actions,}, } """ if self._nodes_direct: return self._nodes_direct - nodes = defaultdict(lambda: defaultdict(int)) + nodes_keys = defaultdict(lambda: defaultdict(int)) for perm in self.permissions: actions = [perm.actions] - system_users = perm.system_users.all() - _nodes = perm.nodes.all() - for node, system_user, action in itertools.product(_nodes, system_users, actions): - nodes[node][system_user] |= action - self.tree.add_nodes(nodes) - self._nodes_direct = nodes - return nodes + system_users_ids = [s.id for s in perm.system_users.all()] + _nodes_keys = [n.key for n in perm.nodes.all()] + iterable = itertools.product(_nodes_keys, system_users_ids, actions) + for node_key, sys_id, action in iterable: + nodes_keys[node_key][sys_id] |= action + + self.tree.add_nodes(nodes_keys) + + pattern = set() + for key in nodes_keys: + pattern.add(r'^{0}$|^{0}:'.format(key)) + pattern = '|'.join(list(pattern)) + if pattern: + assets_ids = Asset.objects.filter( + nodes__key__regex=pattern + ).values_list("id", flat=True).distinct() + else: + assets_ids = [] + self.tree.add_assets_without_system_users(assets_ids) + self._nodes_direct = nodes_keys + return nodes_keys def get_nodes_without_cache(self): - self.get_assets_direct() + self.get_assets_without_cache() return self.tree.get_nodes() - #@timeit + @timeit def get_assets_direct(self): """ - 返回用户授权规则直接关联的资产 - :return: {asset1: {system_user1: 1,}} + 返回直接授权的资产, + 并添加到tree.assets中 + :return: + {asset.id: {system_user.id: actions, }, } """ if self._assets_direct: return self._assets_direct - assets = defaultdict(lambda: defaultdict(int)) + assets_ids = defaultdict(lambda: defaultdict(int)) for perm in self.permissions: actions = [perm.actions] - _assets = perm.assets.valid().only(*self.assets_only) - system_users = perm.system_users.all() - iterable = itertools.product(_assets, system_users, actions) - for asset, system_user, action in iterable: - assets[asset][system_user] |= action - self.tree.add_assets(assets) - self._assets_direct = assets - return assets + _assets_ids = [a.id for a in perm.assets.all()] + system_users_ids = [s.id for s in perm.system_users.all()] + iterable = itertools.product(_assets_ids, system_users_ids, actions) + for asset_id, sys_id, action in iterable: + assets_ids[asset_id][sys_id] |= action + self.tree.add_assets(assets_ids) + self._assets_direct = assets_ids + return assets_ids - #@timeit + @timeit def get_assets_without_cache(self): """ - :return: {asset1: set(system_user1,)} + :return: + [ + {"id": asset.id, "system_users": {system_user.id: actions, }}, + ] """ if self._assets: return self._assets + self.get_nodes_direct() self.get_assets_direct() - nodes = self.get_nodes_direct() - pattern = set() - for node in nodes: - pattern.add(r'^{0}$|^{0}:'.format(node.key)) - pattern = '|'.join(list(pattern)) - if pattern: - assets = Asset.objects.filter(nodes__key__regex=pattern).valid() \ - .prefetch_related('nodes')\ - .only(*self.assets_only)\ - .distinct() - else: - assets = [] - assets = list(assets) - self.tree.add_assets_without_system_users(assets) assets = self.tree.get_assets() self._assets = assets return assets - #@timeit + @timeit def get_nodes_with_assets_without_cache(self): - """ - 返回节点并且包含资产 - {"node": {"asset": {"system_user": 1})}} - :return: - """ self.get_assets_without_cache() nodes_assets = self.tree.get_nodes_with_assets() return nodes_assets @@ -545,67 +583,72 @@ def sort_assets(assets, order_by='hostname', reverse=False): return assets -def parse_node_to_tree_node(node): - name = '{} ({})'.format(node.value, node.assets_amount) - data = { - 'id': node.key, - 'name': name, - 'title': name, - 'pId': node.parent_key, - 'isParent': True, - 'open': node.is_root(), - 'meta': { - 'node': { - "id": node.id, - "key": node.key, - "value": node.value, - }, - 'type': 'node' - } - } - tree_node = TreeNode(**data) - return tree_node +class ParserNode: + nodes_only_fields = ("key", "value", "id") + assets_only_fields = ("platform", "hostname", "id", "ip", "protocols") + system_users_only_fields = ( + "id", "name", "username", "protocol", "priority", "login_mode", + ) - -def parse_asset_to_tree_node(node, asset, system_users): - icon_skin = 'file' - if asset.platform.lower() == 'windows': - icon_skin = 'windows' - elif asset.platform.lower() == 'linux': - icon_skin = 'linux' - _system_users = [] - for system_user, action in system_users.items(): - _system_users.append({ - 'id': system_user.id, - 'name': system_user.name, - 'username': system_user.username, - 'protocol': system_user.protocol, - 'priority': system_user.priority, - 'login_mode': system_user.login_mode, - 'actions': [Action.value_to_choices(action)], - }) - data = { - 'id': str(asset.id), - 'name': asset.hostname, - 'title': asset.ip, - 'pId': node.key, - 'isParent': False, - 'open': False, - 'iconSkin': icon_skin, - 'meta': { - 'system_users': _system_users, - 'type': 'asset', - 'asset': { - 'id': asset.id, - 'hostname': asset.hostname, - 'ip': asset.ip, - 'protocols': asset.protocols_as_list, - 'platform': asset.platform, - 'domain': None if not asset.domain else asset.domain.id, - 'is_active': asset.is_active, - 'comment': asset.comment - }, + @staticmethod + def parse_node_to_tree_node(node): + name = '{} ({})'.format(node.value, node.assets_amount) + data = { + 'id': node.key, + 'name': name, + 'title': name, + 'pId': node.parent_key, + 'isParent': True, + 'open': node.is_root(), + 'meta': { + 'node': { + "id": node.id, + "key": node.key, + "value": node.value, + }, + 'type': 'node' + } } - } - tree_node = TreeNode(**data) - return tree_node + tree_node = TreeNode(**data) + return tree_node + + @staticmethod + def parse_asset_to_tree_node(node, asset, system_users): + icon_skin = 'file' + if asset.platform.lower() == 'windows': + icon_skin = 'windows' + elif asset.platform.lower() == 'linux': + icon_skin = 'linux' + _system_users = [] + for system_user in system_users: + _system_users.append({ + 'id': system_user.id, + 'name': system_user.name, + 'username': system_user.username, + 'protocol': system_user.protocol, + 'priority': system_user.priority, + 'login_mode': system_user.login_mode, + 'actions': [Action.value_to_choices(system_user.actions)], + }) + data = { + 'id': str(asset.id), + 'name': asset.hostname, + 'title': asset.ip, + 'pId': node.key, + 'isParent': False, + 'open': False, + 'iconSkin': icon_skin, + 'meta': { + 'system_users': _system_users, + 'type': 'asset', + 'asset': { + 'id': asset.id, + 'hostname': asset.hostname, + 'ip': asset.ip, + 'protocols': asset.protocols_as_list, + 'platform': asset.platform, + }, + } + } + tree_node = TreeNode(**data) + return tree_node diff --git a/apps/perms/utils/stack.py b/apps/perms/utils/stack.py new file mode 100644 index 000000000..ac2e8b69f --- /dev/null +++ b/apps/perms/utils/stack.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# +from collections import defaultdict +from common.struct import Stack +from common.utils import timeit +from assets.utils import NodeUtil + + +class PermStackUtilMixin: + def __init__(self, debug=False): + self.stack = None + self._nodes = {} + self._debug = debug + + @staticmethod + def sorted_by(node_dict): + return [int(i) for i in node_dict['key'].split(':')] + + @staticmethod + def is_children(item1, item2): + key1 = item1["key"] + key2 = item2["key"] + return key2.startswith(key1 + ':') and ( + len(key2.split(':')) - len(key1.split(':')) + ) == 1 + + def debug(self, msg): + self._debug and print(msg) + + +class PermSystemUserNodeUtil(PermStackUtilMixin): + """ + self._nodes: {node.key: {system_user.id: actions,}} + """ + @timeit + def get_nodes_family_and_system_users(self, nodes_with_system_users): + """ + 返回所有nodes_with_system_users中的node的家族节点的信息, + 并子会继承祖先的系统用户和actions信息 + :param nodes_with_system_users: + {node.key: {system_user.id: actions,}, } + :return: + {node.key: {system_user.id: actions,}, } + """ + node_util = NodeUtil() + _nodes_keys = nodes_with_system_users.keys() + family_keys = node_util.get_some_nodes_family_keys_by_keys(_nodes_keys) + + nodes_items = [] + for i in family_keys: + system_users = nodes_with_system_users.get(i, defaultdict(int)) + item = {"key": i, "system_users": system_users} + nodes_items.append(item) + # 按照父子关系排序 + nodes_items.sort(key=self.sorted_by) + nodes_items.append({"key": "", "system_users": defaultdict(int)}) + + self.stack = Stack() + for item in nodes_items: + self.debug("准备: {} 栈顶: {}".format( + item['key'], self.stack.top["key"] if self.stack.top else None) + ) + # 入栈之前检查,该节点是不是栈顶节点的子节点 + # 如果不是,则栈顶出栈 + while self.stack.top and not self.is_children(self.stack.top, item): + # 出栈 + self.pop_from_stack_system_users() + # 入栈 + self.push_to_stack_system_users(item) + # 出栈最后一个 + self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack]))) + return self._nodes + + def push_to_stack_system_users(self, item): + """ + :param item: + {"key": node.key, "system_users": {system_user.id: actions,},} + """ + if not self.stack.is_empty(): + item_system_users = item["system_users"] + for system_user, action in self.stack.top["system_users"].items(): + # 更新栈顶的系统用户和action到将要入栈的item中 + item_system_users[system_user] |= action + item["system_users"] = item_system_users + self.debug("入栈: {}".format(item['key'])) + self.stack.push(item) + + # 出栈 + def pop_from_stack_system_users(self): + _node = self.stack.pop() + self._nodes[_node["key"]] = _node["system_users"] + self.debug("出栈: {} 栈顶: {}".format(_node['key'], self.stack.top['key'] if self.stack.top else None)) + + +class PermAssetsAmountUtil(PermStackUtilMixin): + def push_to_stack_nodes_amount(self, item): + self.debug("入栈: {}".format(item['key'])) + self.stack.push(item) + + def pop_from_stack_nodes_amount(self): + _node = self.stack.pop() + self.debug("出栈: {} 栈顶: {}".format( + _node['key'], self.stack.top['key'] if self.stack.top else None) + ) + _node["assets_amount"] = len(_node["all_assets"] | _node["assets"]) + self._nodes[_node.pop("key")] = _node + + if not self.stack.top: + return + self.stack.top["all_assets"]\ + .update(_node["all_assets"] | _node["assets"]) + + def compute_nodes_assets_amount(self, nodes_with_assets): + self.stack = Stack() + nodes_items = [] + for key, values in nodes_with_assets.items(): + nodes_items.append({ + "key": key, "assets": values["assets"], + "all_assets": values["all_assets"], "assets_amount": 0 + }) + + nodes_items.sort(key=self.sorted_by) + nodes_items.append({"key": "", "assets": set(), "all_assets": set(), "assets_amount": 0}) + self.stack = Stack() + for item in nodes_items: + self.debug("准备: {} 栈顶: {}".format( + item['key'], self.stack.top["key"] if self.stack.top else None) + ) + # 入栈之前检查,该节点是不是栈顶节点的子节点 + # 如果不是,则栈顶出栈 + while self.stack.top and not self.is_children(self.stack.top, item): + self.pop_from_stack_nodes_amount() + self.push_to_stack_nodes_amount(item) + # 出栈最后一个 + self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack]))) + return self._nodes \ No newline at end of file diff --git a/apps/users/models/user.py b/apps/users/models/user.py index dbc336443..7b8395859 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -27,97 +27,7 @@ signer = get_signer() logger = get_logger(__file__) -class User(AbstractUser): - ROLE_ADMIN = 'Admin' - ROLE_USER = 'User' - ROLE_APP = 'App' - ROLE_AUDITOR = 'Auditor' - - ROLE_CHOICES = ( - (ROLE_ADMIN, _('Administrator')), - (ROLE_USER, _('User')), - (ROLE_APP, _('Application')), - (ROLE_AUDITOR, _("Auditor")) - ) - OTP_LEVEL_CHOICES = ( - (0, _('Disable')), - (1, _('Enable')), - (2, _("Force enable")), - ) - SOURCE_LOCAL = 'local' - SOURCE_LDAP = 'ldap' - SOURCE_OPENID = 'openid' - SOURCE_RADIUS = 'radius' - SOURCE_CHOICES = ( - (SOURCE_LOCAL, 'Local'), - (SOURCE_LDAP, 'LDAP/AD'), - (SOURCE_OPENID, 'OpenID'), - (SOURCE_RADIUS, 'Radius'), - ) - - CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}" - - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - username = models.CharField( - max_length=128, unique=True, verbose_name=_('Username') - ) - name = models.CharField(max_length=128, verbose_name=_('Name')) - email = models.EmailField( - max_length=128, unique=True, verbose_name=_('Email') - ) - groups = models.ManyToManyField( - 'users.UserGroup', related_name='users', - blank=True, verbose_name=_('User group') - ) - role = models.CharField( - choices=ROLE_CHOICES, default='User', max_length=10, - blank=True, verbose_name=_('Role') - ) - avatar = models.ImageField( - upload_to="avatar", null=True, verbose_name=_('Avatar') - ) - wechat = models.CharField( - max_length=128, blank=True, verbose_name=_('Wechat') - ) - phone = models.CharField( - max_length=20, blank=True, null=True, verbose_name=_('Phone') - ) - otp_level = models.SmallIntegerField( - default=0, choices=OTP_LEVEL_CHOICES, verbose_name=_('MFA') - ) - otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True) - # Todo: Auto generate key, let user download - private_key = fields.EncryptTextField( - blank=True, null=True, verbose_name=_('Private key') - ) - public_key = fields.EncryptTextField( - blank=True, null=True, verbose_name=_('Public key') - ) - comment = models.TextField( - blank=True, null=True, verbose_name=_('Comment') - ) - is_first_login = models.BooleanField(default=True) - date_expired = models.DateTimeField( - default=date_expired_default, blank=True, null=True, - db_index=True, verbose_name=_('Date expired') - ) - created_by = models.CharField( - max_length=30, default='', verbose_name=_('Created by') - ) - source = models.CharField( - max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES, - verbose_name=_('Source') - ) - date_password_last_updated = models.DateTimeField( - auto_now_add=True, blank=True, null=True, - verbose_name=_('Date password last updated') - ) - - user_cache_key_prefix = '_User_{}' - - def __str__(self): - return '{0.name}({0.username})'.format(self) - +class AuthMixin: @property def password_raw(self): raise AttributeError('Password raw is not a readable attribute') @@ -134,9 +44,12 @@ class User(AbstractUser): def set_password(self, raw_password): self._set_password = True if self.can_update_password(): - return super().set_password(raw_password) + self.date_password_last_updated = timezone.now() + super().set_password(raw_password) + self.save() else: - error = _("User auth from {}, go there change password").format(self.source) + error = _("User auth from {}, go there change password").format( + self.source) raise PermissionError(error) def can_update_password(self): @@ -146,9 +59,6 @@ class User(AbstractUser): from ..utils import check_otp_code return check_otp_code(self.otp_secret_key, code) - def get_absolute_url(self): - return reverse('users:user-detail', args=(self.id,)) - def is_public_key_valid(self): """ Check if the user's ssh public key is valid. @@ -158,36 +68,12 @@ class User(AbstractUser): return True return False - @property - def groups_display(self): - return ' '.join([group.name for group in self.groups.all()]) - - @property - def role_display(self): - return self.get_role_display() - - @property - def source_display(self): - return self.get_source_display() - - @property - def is_expired(self): - if self.date_expired and self.date_expired < timezone.now(): - return True - else: - return False - - @property - def is_valid(self): - if self.is_active and not self.is_expired: - return True - return False - @property def public_key_obj(self): class PubKey(object): def __getattr__(self, item): return '' + if self.public_key: import sshpubkeys try: @@ -196,6 +82,52 @@ class User(AbstractUser): pass return PubKey() + def reset_password(self, new_password): + self.set_password(new_password) + + @property + def date_password_expired(self): + interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME + date_expired = self.date_password_last_updated + timezone.timedelta( + days=int(interval)) + return date_expired + + @property + def password_expired_remain_days(self): + date_remain = self.date_password_expired - timezone.now() + return date_remain.days + + @property + def password_has_expired(self): + if self.is_local and self.password_expired_remain_days < 0: + return True + return False + + @property + def password_will_expired(self): + if self.is_local and self.password_expired_remain_days < 5: + return True + return False + + +class RoleMixin: + ROLE_ADMIN = 'Admin' + ROLE_USER = 'User' + ROLE_APP = 'App' + ROLE_AUDITOR = 'Auditor' + + ROLE_CHOICES = ( + (ROLE_ADMIN, _('Administrator')), + (ROLE_USER, _('User')), + (ROLE_APP, _('Application')), + (ROLE_AUDITOR, _("Auditor")) + ) + role = ROLE_USER + + @property + def role_display(self): + return self.get_role_display() + @property def is_superuser(self): if self.role == 'Admin': @@ -251,41 +183,21 @@ class User(AbstractUser): def is_staff(self, value): pass - @property - def is_local(self): - return self.source == self.SOURCE_LOCAL - - @property - def date_password_expired(self): - interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME - date_expired = self.date_password_last_updated + timezone.timedelta( - days=int(interval)) - return date_expired + @classmethod + def create_app_user(cls, name, comment): + app = cls.objects.create( + username=name, name=name, email='{}@local.domain'.format(name), + is_active=False, role='App', comment=comment, + is_first_login=False, created_by='System' + ) + access_key = app.create_access_key() + return app, access_key - @property - def password_expired_remain_days(self): - date_remain = self.date_password_expired - timezone.now() - return date_remain.days - @property - def password_has_expired(self): - if self.is_local and self.password_expired_remain_days < 0: - return True - return False - - @property - def password_will_expired(self): - if self.is_local and self.password_expired_remain_days < 5: - return True - return False - - def save(self, *args, **kwargs): - if not self.name: - self.name = self.username - if self.username == 'admin': - self.role = 'Admin' - self.is_active = True - super().save(*args, **kwargs) +class TokenMixin: + CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}" + email = '' + id = None @property def private_token(self): @@ -333,31 +245,12 @@ class User(AbstractUser): def access_key(self): return self.access_keys.first() - def is_member_of(self, user_group): - if user_group in self.groups.all(): - return True - return False - - def avatar_url(self): - admin_default = settings.STATIC_URL + "img/avatar/admin.png" - user_default = settings.STATIC_URL + "img/avatar/user.png" - if self.avatar: - return self.avatar.url - if self.is_superuser: - return admin_default - else: - return user_default - def generate_reset_token(self): letter = string.ascii_letters + string.digits token = ''.join([random.choice(letter) for _ in range(50)]) self.set_cache(token) return token - def set_cache(self, token): - key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token) - cache.set(key, {'id': self.id, 'email': self.email}, 3600) - @classmethod def validate_reset_password_token(cls, token): try: @@ -371,11 +264,25 @@ class User(AbstractUser): user = None return user + def set_cache(self, token): + key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token) + cache.set(key, {'id': self.id, 'email': self.email}, 3600) + @classmethod def expired_reset_password_token(cls, token): key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token) cache.delete(key) + +class MFAMixin: + otp_level = 0 + otp_secret_key = '' + OTP_LEVEL_CHOICES = ( + (0, _('Disable')), + (1, _('Enable')), + (2, _("Force enable")), + ) + @property def otp_enabled(self): return self.otp_force_enabled or self.otp_level > 0 @@ -397,39 +304,130 @@ class User(AbstractUser): self.otp_level = 0 self.otp_secret_key = None - def to_json(self): - return OrderedDict({ - 'id': self.id, - 'username': self.username, - 'name': self.name, - 'email': self.email, - 'is_active': self.is_active, - 'is_superuser': self.is_superuser, - 'role': self.get_role_display(), - 'groups': [group.name for group in self.groups.all()], - 'source': self.get_source_display(), - 'wechat': self.wechat, - 'phone': self.phone, - 'otp_level': self.otp_level, - 'comment': self.comment, - 'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S') \ - if self.date_expired is not None else None - }) - @classmethod - def create_app_user(cls, name, comment): - app = cls.objects.create( - username=name, name=name, email='{}@local.domain'.format(name), - is_active=False, role='App', comment=comment, - is_first_login=False, created_by='System' - ) - access_key = app.create_access_key() - return app, access_key +class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): + SOURCE_LOCAL = 'local' + SOURCE_LDAP = 'ldap' + SOURCE_OPENID = 'openid' + SOURCE_RADIUS = 'radius' + SOURCE_CHOICES = ( + (SOURCE_LOCAL, 'Local'), + (SOURCE_LDAP, 'LDAP/AD'), + (SOURCE_OPENID, 'OpenID'), + (SOURCE_RADIUS, 'Radius'), + ) - def reset_password(self, new_password): - self.set_password(new_password) - self.date_password_last_updated = timezone.now() - self.save() + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + username = models.CharField( + max_length=128, unique=True, verbose_name=_('Username') + ) + name = models.CharField(max_length=128, verbose_name=_('Name')) + email = models.EmailField( + max_length=128, unique=True, verbose_name=_('Email') + ) + groups = models.ManyToManyField( + 'users.UserGroup', related_name='users', + blank=True, verbose_name=_('User group') + ) + role = models.CharField( + choices=RoleMixin.ROLE_CHOICES, default='User', max_length=10, + blank=True, verbose_name=_('Role') + ) + avatar = models.ImageField( + upload_to="avatar", null=True, verbose_name=_('Avatar') + ) + wechat = models.CharField( + max_length=128, blank=True, verbose_name=_('Wechat') + ) + phone = models.CharField( + max_length=20, blank=True, null=True, verbose_name=_('Phone') + ) + otp_level = models.SmallIntegerField( + default=0, choices=MFAMixin.OTP_LEVEL_CHOICES, verbose_name=_('MFA') + ) + otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True) + # Todo: Auto generate key, let user download + private_key = fields.EncryptTextField( + blank=True, null=True, verbose_name=_('Private key') + ) + public_key = fields.EncryptTextField( + blank=True, null=True, verbose_name=_('Public key') + ) + comment = models.TextField( + blank=True, null=True, verbose_name=_('Comment') + ) + is_first_login = models.BooleanField(default=True) + date_expired = models.DateTimeField( + default=date_expired_default, blank=True, null=True, + db_index=True, verbose_name=_('Date expired') + ) + created_by = models.CharField( + max_length=30, default='', verbose_name=_('Created by') + ) + source = models.CharField( + max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES, + verbose_name=_('Source') + ) + date_password_last_updated = models.DateTimeField( + auto_now_add=True, blank=True, null=True, + verbose_name=_('Date password last updated') + ) + + user_cache_key_prefix = '_User_{}' + + def __str__(self): + return '{0.name}({0.username})'.format(self) + + def get_absolute_url(self): + return reverse('users:user-detail', args=(self.id,)) + + @property + def groups_display(self): + return ' '.join([group.name for group in self.groups.all()]) + + @property + def source_display(self): + return self.get_source_display() + + @property + def is_expired(self): + if self.date_expired and self.date_expired < timezone.now(): + return True + else: + return False + + @property + def is_valid(self): + if self.is_active and not self.is_expired: + return True + return False + + @property + def is_local(self): + return self.source == self.SOURCE_LOCAL + + def save(self, *args, **kwargs): + if not self.name: + self.name = self.username + if self.username == 'admin': + self.role = 'Admin' + self.is_active = True + super().save(*args, **kwargs) + + def is_member_of(self, user_group): + if user_group in self.groups.all(): + return True + return False + + def avatar_url(self): + admin_default = settings.STATIC_URL + "img/avatar/admin.png" + user_default = settings.STATIC_URL + "img/avatar/user.png" + if self.avatar: + return self.avatar.url + if self.is_superuser: + return admin_default + else: + return user_default def delete(self, using=None, keep_parents=False): if self.pk == 1 or self.username == 'admin': diff --git a/apps/users/templates/users/user_granted_asset.html b/apps/users/templates/users/user_granted_asset.html index 41aea998c..225005ffd 100644 --- a/apps/users/templates/users/user_granted_asset.html +++ b/apps/users/templates/users/user_granted_asset.html @@ -29,7 +29,6 @@ <div class="file-manager "> <div id="assetTree" class="ztree"> </div> - <div class="clearfix"></div> </div> </div> @@ -43,7 +42,6 @@ <th class="text-center"><input type="checkbox" class="ipt_check_all"></th> <th class="text-center">{% trans 'Hostname' %}</th> <th class="text-center">{% trans 'IP' %}</th> - <th class="text-center">{% trans 'Active' %}</th> <th class="text-center">{% trans 'System users' %}</th> </tr> </thead> @@ -64,7 +62,7 @@ var zTree; var inited = false; var url; var asset_table; -var treeUrl = "{% url 'api-perms:user-nodes-assets-as-tree' pk=object.id %}?show_assets=0&cache_policy=1"; +var treeUrl = "{% url 'api-perms:user-nodes-as-tree' pk=object.id %}?&cache_policy=1"; function initTable() { if (inited){ @@ -83,13 +81,6 @@ function initTable() { $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, {targets: 3, createdCell: function (td, cellData) { - if (!cellData) { - $(td).html('<i class="fa fa-times text-danger"></i>') - } else { - $(td).html('<i class="fa fa-check text-navy"></i>') - } - }}, - {targets: 4, createdCell: function (td, cellData) { var users = []; $.each(cellData, function (id, data) { var name = htmlEscape(data.name); @@ -101,7 +92,6 @@ function initTable() { ajax_url: url, columns: [ {data: "id"}, {data: "hostname" }, {data: "ip" }, - {data: "is_active", orderable: false }, {data: "system_users_granted", orderable: false} ] };