mirror of https://github.com/jumpserver/jumpserver
Merge branch 'dev' of github.com:jumpserver/jumpserver into dev
commit
21ffa8b28a
|
@ -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))
|
||||
|
|
|
@ -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 = ""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
|
|
@ -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},
|
||||
}
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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"}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -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 %}
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
]
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
@ -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 "组织"
|
||||
|
||||
|
|
|
@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
|
|||
else:
|
||||
return Response({"error": serializer.errors})
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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':
|
||||
|
|
|
@ -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}
|
||||
]
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue