Merge branch 'dev' of github.com:jumpserver/jumpserver into dev

pull/2938/head
ibuler 2019-07-11 18:17:34 +08:00
commit 21ffa8b28a
37 changed files with 1444 additions and 1136 deletions

View File

@ -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))

View File

@ -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 = ""

View File

@ -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)

View File

@ -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()

View File

@ -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')},
}

View File

@ -10,13 +10,19 @@ from orgs.mixins import BulkOrgResourceModelSerializer
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
class Meta:
model = CommandFilter
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
fields = [
'id', 'name', 'org_id', 'org_name', 'is_active', 'comment',
'created_by', 'date_created', 'date_updated', 'rules', 'system_users'
]
extra_kwargs = {
'rules': {'read_only': True},
'system_users': {'read_only': True}
}
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):

View File

@ -6,6 +6,7 @@ from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import Domain, Gateway
from .base import AuthSerializerMixin
class DomainSerializer(BulkOrgResourceModelSerializer):
@ -26,14 +27,14 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
return obj.gateway_set.all().count()
class GatewaySerializer(BulkOrgResourceModelSerializer):
class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
class Meta:
model = Gateway
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'ip', 'port', 'protocol', 'username',
'domain', 'is_active', 'date_created', 'date_updated',
'created_by', 'comment',
'id', 'name', 'ip', 'port', 'protocol', 'username', 'password',
'private_key', 'public_key', 'domain', 'is_active', 'date_created',
'date_updated', 'created_by', 'comment',
]

View File

@ -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',
]

View File

@ -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},
}

View File

@ -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'])

View File

@ -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"}
]
};

View File

@ -18,3 +18,29 @@
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-assets:cmd-filter-list' %}';
var redirect_to = '{% url "assets:cmd-filter-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url 'api-assets:cmd-filter-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}

View File

@ -95,6 +95,32 @@ function protocolChange() {
$(document).ready(function(){
protocolChange();
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
data["private_key"] = $("#id_private_key_file").data('file');
var method = "POST";
var the_url = '{% url "api-assets:gateway-list" %}';
var redirect_to = '{% url "assets:domain-gateway-list" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", data.domain);
{% if type == "update" %}
the_url = '{% url 'api-assets:gateway-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var props = {
url:the_url,
data:data,
method:method,
form:form,
redirect_to:redirect_to
};
formSubmit(props);
})
.on('change', '#id_private_key_file', function () {
readFile($(this)).on("onload", function (evt, data) {
$(this).attr("data-file", data)
})
})
.on('change', protocol_id, function(){
protocolChange();
});

View File

@ -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()
};

View File

@ -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}
]

View File

@ -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():

View File

@ -47,6 +47,7 @@ class CommandFilterCreateView(PermissionsMixin, CreateView):
context = {
'app': _('Assets'),
'action': _('Create command filter'),
'type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -64,6 +65,7 @@ class CommandFilterUpdateView(PermissionsMixin, UpdateView):
context = {
'app': _('Assets'),
'action': _('Update command filter'),
'type': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -132,6 +132,7 @@ class DomainGatewayCreateView(PermissionsMixin, CreateView):
context = {
'app': _('Assets'),
'action': _('Create gateway'),
'type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -152,6 +153,7 @@ class DomainGatewayUpdateView(PermissionsMixin, UpdateView):
context = {
'app': _('Assets'),
'action': _('Update gateway'),
"type": "update"
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -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)

12
apps/common/http.py Normal file
View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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()

Binary file not shown.

View File

@ -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 "组织"

View File

@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
else:
return Response({"error": serializer.errors})

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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"

View File

@ -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())

View File

@ -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):

View File

@ -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

View File

@ -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

136
apps/perms/utils/stack.py Normal file
View File

@ -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

View File

@ -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':

View File

@ -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}
]
};