Merge pull request #2947 from jumpserver/dev

Dev
pull/3007/head
老广 2019-07-15 15:11:30 +08:00 committed by GitHub
commit c8623c3b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 2600 additions and 2069 deletions

View File

@ -107,6 +107,21 @@ function hiddenFields(){
});
$('.' + app_type + '-fields').removeClass('hidden');
}
function constructParams(data) {
var typeList = ['chrome', 'mysql_workbench', 'vmware_client', 'custom'];
var params = {};
for (var type in typeList){
if (data.type === type){
for (var k in data){
if (k.startsWith(data.type)){
params[k] = data[k]
}
}
break
}
}
return params;
}
$(document).ready(function () {
$('.select2').select2({
closeOnSelect: true
@ -118,6 +133,28 @@ $(document).ready(function () {
.on('change', app_type_id, function(){
hiddenFields();
setDefaultValue();
});
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url "api-applications:remote-app-list" %}';
var redirect_to = '{% url "applications:remote-app-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url "api-applications:remote-app-detail" object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
data["params"] = constructParams(data);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
;
</script>
{% endblock %}

View File

@ -46,6 +46,7 @@ class RemoteAppCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
context = {
'app': _('Applications'),
'action': _('Create RemoteApp'),
'type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -68,6 +69,7 @@ class RemoteAppUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
context = {
'app': _('Applications'),
'action': _('Update RemoteApp'),
'type': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -13,11 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from django.db.models import Count
from common.utils import get_logger
from orgs.mixins import OrgBulkModelViewSet
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
@ -27,7 +27,7 @@ logger = get_logger(__file__)
__all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet):
class LabelViewSet(OrgBulkModelViewSet):
filter_fields = ("name", "value")
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)

View File

@ -131,7 +131,7 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
if not include_assets:
return queryset
assets = self.node.get_assets().only(
"id", "hostname", "ip", 'platform', "os", "org_id",
"id", "hostname", "ip", 'platform', "os", "org_id", "protocols",
)
for asset in assets:
queryset.append(asset.as_tree_node(self.node))

View File

@ -29,9 +29,9 @@ class ProtocolForm(forms.Form):
class AssetCreateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.data:
nodes_field = self.fields['nodes']
nodes_field._queryset = Node.get_queryset()
nodes_field = self.fields['nodes']
nodes_field.choices = ((n.id, n.full_value) for n in
Node.get_queryset())
class Meta:
model = Asset

View File

@ -0,0 +1,34 @@
# Generated by Django 2.1.7 on 2019-07-11 12:18
import common.fields.model
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0034_auto_20190705_1348'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='authbook',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='gateway',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='systemuser',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
]

View File

@ -6,7 +6,8 @@ import uuid
import logging
import random
from functools import reduce
from collections import OrderedDict
from collections import OrderedDict, defaultdict
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
@ -96,7 +97,53 @@ class ProtocolsMixin:
return self.protocols_as_dict.get("ssh", 22)
class Asset(ProtocolsMixin, OrgModelMixin):
class NodesRelationMixin:
NODES_CACHE_KEY = 'ASSET_NODES_{}'
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
CACHE_TIME = 3600 * 24 * 7
id = ""
_all_nodes_keys = None
@classmethod
def get_all_nodes_keys(cls):
"""
:return: {asset.id: [node.key, ]}
"""
from .node import Node
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cached = cache.get(cache_key)
if cached:
return cached
assets = Asset.objects.all().only('id').prefetch_related(
models.Prefetch('nodes', queryset=Node.objects.all().only('key'))
)
assets_nodes_keys = {}
for asset in assets:
assets_nodes_keys[asset.id] = [n.key for n in asset.nodes.all()]
cache.set(cache_key, assets_nodes_keys, cls.CACHE_TIME)
return assets_nodes_keys
@classmethod
def expire_all_nodes_keys_cache(cls):
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cache.delete(cache_key)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
@ -182,20 +229,6 @@ class Asset(ProtocolsMixin, OrgModelMixin):
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
@property
def cpu_info(self):
info = ""

View File

@ -28,7 +28,7 @@ class AssetUser(OrgModelMixin):
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))

View File

@ -212,14 +212,12 @@ class AssetsAmountMixin:
if cached is not None:
return cached
assets_amount = self.get_all_assets().count()
self.assets_amount = assets_amount
cache.set(cache_key, assets_amount, self.cache_time)
return assets_amount
@assets_amount.setter
def assets_amount(self, value):
self._assets_amount = value
cache_key = self._assets_amount_cache_key.format(self.key)
cache.set(cache_key, value, self.cache_time)
def expire_assets_amount(self):
ancestor_keys = self.get_ancestor_keys(with_self=True)

View File

@ -117,16 +117,6 @@ class SystemUser(AssetUser):
def __str__(self):
return '{0.name}({0.username})'.format(self)
def to_json(self):
return {
'id': self.id,
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'priority': self.priority,
'auto_push': self.auto_push,
}
@property
def login_mode_display(self):
return self.get_login_mode_display()

View File

@ -21,19 +21,15 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
model = AdminUser
fields = [
'id', 'name', 'username', 'password', 'private_key', 'public_key',
'comment', 'connectivity_amount', 'assets_amount',
'date_created', 'date_updated', 'created_by',
'comment', 'assets_amount', 'date_created', 'date_updated', 'created_by',
]
read_only_fields = ['date_created', 'date_updated', 'created_by', 'assets_amount']
extra_kwargs = {
'password': {"write_only": True},
'private_key': {"write_only": True},
'public_key': {"write_only": True},
'date_created': {'read_only': True},
'date_updated': {'read_only': True},
'created_by': {'read_only': True},
'assets_amount': {'label': _('Asset')},
'connectivity_amount': {'label': _('Connectivity')},
}

View File

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

View File

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

View File

@ -13,7 +13,13 @@ class LabelSerializer(BulkOrgResourceModelSerializer):
class Meta:
model = Label
fields = '__all__'
fields = [
'id', 'name', 'value', 'category', 'is_active', 'comment',
'date_created', 'asset_count', 'assets', 'get_category_display'
]
read_only_fields = (
'category', 'date_created', 'asset_count', 'get_category_display'
)
list_serializer_class = AdaptedBulkListSerializer
@staticmethod

View File

@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
class Meta:
model = Node
fields = [
'id', 'key', 'value', 'assets_amount', 'org_id',
]
only_fields = ['id', 'key', 'value', 'org_id']
fields = only_fields + ['assets_amount']
read_only_fields = [
'key', 'assets_amount', 'org_id',
]

View File

@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
'assets_amount', 'connectivity_amount', 'auto_generate_key'
'assets_amount', 'auto_generate_key'
]
extra_kwargs = {
'password': {"write_only": True},
'public_key': {"write_only": True},
'private_key': {"write_only": True},
'assets_amount': {'label': _('Asset')},
'connectivity_amount': {'label': _('Connectivity')},
'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True},
}

View File

@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs):
logger.debug("Asset nodes change signal received")
Asset.expire_all_nodes_keys_cache()
if isinstance(instance, Asset):
if kwargs['action'] == 'pre_remove':
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])

View File

@ -70,7 +70,7 @@ function showAuth() {
var msg = "{% trans 'Get auth info error' %}";
toastr.error(msg)
};
APIUpdateAttr({
requestApi({
url: url,
method: "GET",
success: success,

View File

@ -141,7 +141,7 @@ $(document).ready(function(){
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'GET',
success: success,

View File

@ -235,7 +235,7 @@ function onRename(event, treeId, treeNode, isCancel){
if (isCancel){
return
}
APIUpdateAttr({
requestApi({
url: url,
body: JSON.stringify(data),
method: "PATCH",
@ -274,7 +274,7 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id);
var body = {nodes: treeNodesIds};
APIUpdateAttr({
requestApi({
url: the_url,
method: "PUT",
body: JSON.stringify(body)
@ -291,42 +291,6 @@ function defaultCallback(action) {
$(document).ready(function () {
})
.on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
if (!current_node_id) {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-show-current-asset', function(){
hideRMenu();
$(this).css('display', 'none');
@ -341,17 +305,5 @@ $(document).ready(function () {
setCookie('show_current_asset', '');
location.reload();
})
.on('click', '.btn-test-connective', function () {
hideRMenu();
})
.on('click', '#menu_refresh_assets_amount', function () {
hideRMenu();
var url = "{% url 'api-assets:refresh-assets-amount' %}";
APIUpdateAttr({
'url': url,
'method': 'GET'
});
window.location.reload();
})
</script>

View File

@ -228,6 +228,7 @@ $(document).ready(function () {
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['cmd_filters']);
objectAttrsIsBool(data, ["auto_generate_key", "auto_push"]);
data["private_key"] = $("#id_private_key_file").data('file');

View File

@ -88,7 +88,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'GET',
success: success,

View File

@ -131,7 +131,7 @@ function replaceNodeAssetsAdminUser(nodes) {
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success

View File

@ -2,8 +2,6 @@
{% load i18n static %}
{% block help_message %}
<div class="alert alert-info help-message">
{# 管理用户是资产被控服务器上的root或拥有 NOPASSWD: ALL sudo权限的用户Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
{# Windows或其它硬件可以随意设置一个#}
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
{% trans 'You can set any one for Windows or other hardware.' %}
@ -47,9 +45,9 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Ratio' %}</th>
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
{# <th class="text-center">{% trans 'Unreachable' %}</th>#}
{# <th class="text-center">{% trans 'Ratio' %}</th>#}
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
@ -73,44 +71,44 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = "";
var data = cellData.reachable;
if (data !== 0) {
innerHtml = "<span class='text-navy'>" + data + "</span>";
} else {
innerHtml = "<span>" + data + "</span>";
}
$(td).html(innerHtml)
}},
{targets: 5, createdCell: function (td, cellData) {
var data = cellData.unreachable;
var innerHtml = "";
if (data !== 0) {
innerHtml = "<span class='text-danger'>" + data + "</span>";
} else {
innerHtml = "<span>" + data + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
var reachable = cellData.reachable;
if (total !== 0) {
val = reachable/total * 100;
}
if (val === 100) {
innerHtml = "<span class='text-navy'>" + val + "% </span>";
} else {
var num = new Number(val);
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 8, createdCell: function (td, cellData, rowData) {
{#{targets: 4, createdCell: function (td, cellData) {#}
{# var innerHtml = "";#}
{# var data = cellData.reachable;#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html(innerHtml)#}
{#}},#}
{#{targets: 5, createdCell: function (td, cellData) {#}
{# var data = cellData.unreachable;#}
{# var innerHtml = "";#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
{#}},#}
{#{targets: 6, createdCell: function (td, cellData, rowData) {#}
{# var val = 0;#}
{# var innerHtml = "";#}
{# var total = rowData.assets_amount;#}
{# var reachable = cellData.reachable;#}
{# if (total !== 0) {#}
{# val = reachable/total * 100;#}
{# }#}
{##}
{# if (val === 100) {#}
{# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
{# } else {#}
{# var num = new Number(val);#}
{# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
{#}},#}
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
@ -118,7 +116,7 @@ function initTable() {
ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},
{#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#}
{data: "comment"}, {data: "id"}
]
};

View File

@ -84,7 +84,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'GET',
success: success,

View File

@ -137,7 +137,6 @@ $(document).ready(function () {
protocolRef.val(protocolShould);
protocolRef.trigger("change")
}
})
.on("click", ".btn-protocol.btn-del", function () {
$(this).parent().parent().remove();

View File

@ -70,7 +70,7 @@
</tr>
<tr>
<td>{% trans 'Protocol' %}</td>
<td>{{ asset.protocols }}</td>
<td><b>{{ asset.protocols }}</b></td>
</tr>
<tr>
<td>{% trans 'Admin user' %}:</td>
@ -267,7 +267,7 @@ function updateAssetNodes(nodes) {
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -282,7 +282,7 @@ function refreshAssetHardware() {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
};
APIUpdateAttr({
requestApi({
url: the_url,
success: success,
method: 'GET'
@ -306,7 +306,7 @@ $(document).ready(function () {
};
var success = '{% trans "Update successfully!" %}';
var status = $(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").text();
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
@ -360,7 +360,7 @@ $(document).ready(function () {
window.open(url, '', 'width=800,height=600')
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'GET',
success: success

View File

@ -167,7 +167,7 @@ function initTable() {
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
@ -325,8 +325,7 @@ $(document).ready(function(){
}
window.open(url, '_self');
})
.on('click', '.btn_asset_delete', function () {
.on('click', '.btn-asset-delete', function () {
var $this = $(this);
var $data_table = $("#asset_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
@ -361,7 +360,7 @@ $(document).ready(function(){
setTimeout( function () {
window.location.reload();}, 500);
}
APIUpdateAttr({
requestApi({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
@ -378,7 +377,7 @@ $(document).ready(function(){
setTimeout( function () {
window.location.reload();}, 300);
}
APIUpdateAttr({
requestApi({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
@ -398,7 +397,7 @@ $(document).ready(function(){
},function () {
function success(data) {
url = setUrlParam(the_url, 'spm', data.spm);
APIUpdateAttr({
requestApi({
url:url,
method:'DELETE',
success:refreshTag,
@ -411,7 +410,7 @@ $(document).ready(function(){
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
}
APIUpdateAttr({
requestApi({
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
@ -429,7 +428,7 @@ $(document).ready(function(){
var url = "{% url 'assets:asset-bulk-update' %}";
location.href= setUrlParam(url, 'spm', data.spm);
}
APIUpdateAttr({
requestApi({
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
@ -453,7 +452,7 @@ $(document).ready(function(){
asset_table.ajax.reload()
};
APIUpdateAttr({
requestApi({
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
@ -501,7 +500,7 @@ $(document).ready(function(){
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
}
APIUpdateAttr({
requestApi({
'url': url,
'method': 'PUT',
'body': JSON.stringify(data),
@ -513,6 +512,40 @@ $(document).ready(function(){
update_node_action = "add"
}).on('click', '#menu_asset_move', function () {
update_node_action = "move"
}).on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
if (!current_node_id) {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
requestApi({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
}).on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
requestApi({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
</script>

View File

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

View File

@ -136,7 +136,7 @@ function updateCMDFilterSystemUsers(system_users) {
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH',

View File

@ -70,5 +70,25 @@ $(document).ready(function(){
content_help_ref.html(content_origin_help_text);
}
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var the_url = '{% url "api-assets:cmd-filter-rule-list" filter_pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", data.filter);
var redirect_to = '{% url "assets:cmd-filter-rule-list" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", data.filter);
var method = "POST";
{% if request_type == "update" %}
the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=DEFAULT_PK pk=rule.id %}'.replace('{{ DEFAULT_PK }}', data.filter);
method = "PUT";
{% endif %}
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}

View File

@ -48,5 +48,26 @@ $(document).ready(function () {
$("#asset_list_modal").modal('hide');
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var method = "POST";
var the_url = '{% url "api-assets:domain-list" %}';
var redirect_to = '{% url "assets:domain-list" %}';
{% if type == "update" %}
the_url = '{% url 'api-assets:domain-detail' pk=object.id %}';
method = "PUT";
{% endif %}
objectAttrsIsList(data, ['assets']);
var props = {
url:the_url,
data:data,
method:method,
form:form,
redirect_to:redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}

View File

@ -134,7 +134,7 @@ $(document).ready(function(){
var data = $("#test_gateway_form").serializeObject();
var uid = data.gateway_id;
var the_url = '{% url "api-assets:test-gateway-connective" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
APIUpdateAttr({
requestApi({
url: the_url,
method: "POST",
body: JSON.stringify({'port': parseInt(data.port)}),

View File

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

View File

@ -51,5 +51,26 @@ $(document).ready(function () {
$('#id_assets').val(assets).trigger('change');
$("#asset_list_modal").modal('hide');
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-assets:label-list' %}';
var redirect_to = '{% url "assets:label-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url 'api-assets:label-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['assets']);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}

View File

@ -146,7 +146,7 @@ function updateSystemUserNode(nodes) {
// clear jumpserver.nodes_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -206,7 +206,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
requestApi({
url: the_url,
error: error,
method: 'GET',
@ -226,7 +226,7 @@ $(document).ready(function () {
var error = function (data) {
alert(data)
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'GET',
success: success,
@ -243,7 +243,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
requestApi({
url: the_url,
error: error,
method: 'GET',

View File

@ -212,7 +212,7 @@ function updateCommandFilters(command_filters) {
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -235,7 +235,7 @@ $(document).ready(function () {
var body = {
'auto_push': checked
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body)
});
@ -254,7 +254,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'GET',
success: success,
@ -268,7 +268,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'GET',
success: success,

View File

@ -53,9 +53,9 @@
<th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Login mode' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Ratio' %}</th>
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
{# <th class="text-center">{% trans 'Unreachable' %}</th>#}
{# <th class="text-center">{% trans 'Ratio' %}</th>#}
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
@ -78,44 +78,44 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 6, createdCell: function (td, cellData) {
var innerHtml = "";
var data = cellData.reachable;
if (data !== 0) {
innerHtml = "<span class='text-navy'>" + data + "</span>";
} else {
innerHtml = "<span>" + data + "</span>";
}
$(td).html(innerHtml)
}},
{targets: 7, createdCell: function (td, cellData) {
var data = cellData.unreachable;
var innerHtml = "";
if (data !== 0) {
innerHtml = "<span class='text-danger'>" + data + "</span>";
} else {
innerHtml = "<span>" + data + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');
}},
{targets: 8, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
var reachable = cellData.reachable;
if (total && total !== 0) {
val = reachable/total * 100;
}
if (val === 100) {
innerHtml = "<span class='text-navy'>" + val + "% </span>";
} else {
var num = new Number(val);
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 10, createdCell: function (td, cellData, rowData) {
{#{targets: 6, createdCell: function (td, cellData) {#}
{# var innerHtml = "";#}
{# var data = cellData.reachable;#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html(innerHtml)#}
{#}},#}
{#{targets: 7, createdCell: function (td, cellData) {#}
{# var data = cellData.unreachable;#}
{# var innerHtml = "";#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
{#}},#}
{#{targets: 8, createdCell: function (td, cellData, rowData) {#}
{# var val = 0;#}
{# var innerHtml = "";#}
{# var total = rowData.assets_amount;#}
{# var reachable = cellData.reachable;#}
{# if (total && total !== 0) {#}
{# val = reachable/total * 100;#}
{# }#}
{##}
{# if (val === 100) {#}
{# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
{# } else {#}
{# var num = new Number(val);#}
{# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
{#}},#}
{targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
@ -124,7 +124,7 @@ function initTable() {
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" },
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "comment" }, {data: "id" }
{data: "comment" }, {data: "id" }
],
op_html: $('#actions').html()
};
@ -182,7 +182,7 @@ $(document).ready(function(){
swal("{% trans 'System Users Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});

View File

@ -11,48 +11,7 @@
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="user_assets_table" style="width: 100%">
<thead>
<tr>
<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>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
{% include 'users/_granted_assets.html' %}
</div>
</div>
@ -62,130 +21,52 @@
{% block custom_foot_js %}
<script>
var treeUrl = "{% url 'api-perms:my-nodes-assets-as-tree' %}?show_assets=0&cache_policy=1";
var zTree, asset_table, show=0;
var inited = false;
var url;
function initTable() {
if (inited){
return
} else {
inited = true;
}
url = "{% url 'api-perms:my-assets' %}?cache_policy=1";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
$(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) {
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)
}}
],
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}
]
};
asset_table = jumpserver.initServerSideDataTable(options);
return asset_table
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1';
var node_id = treeNode.meta.node.id;
url = url.replace("{{ DEFAULT_PK }}", node_id);
setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true,
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
var zNodes = [];
$.get(treeUrl, function(data, status){
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rootNodeAddDom(zTree, function () {
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
initTree();
});
});
}
var treeUrl = "{% url 'api-perms:my-nodes-as-tree' %}?&cache_policy=1";
var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1";
var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1';
var showAssetHref = false; // Need input default true
var actions = {
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>';
$(td).html(conn_btn)
}};
$(document).ready(function () {
initTree();
initTable();
}).on('click', '.labels li', function () {
var val = $(this).text();
$("#user_assets_table_filter input").val(val);
asset_table.search(val).draw();
assetTable.search(val).draw();
})
.on('click', '.asset_detail', function() {
var data = asset_table.ajax.json();
var asset_id = this.getAttribute("asset-id");
.on('click', '.asset-detail', function(e) {
e.preventDefault();
var data = assetTable.ajax.json();
var assetId = $(this).data("asset");
var trs = '';
var desc = {
'hostname': "{% trans 'Hostname' %}",
'ip': "{% trans 'IP' %}",
'port': "{% trans 'Port' %}",
'protocol': "{% trans 'Protocol' %}",
'protocols': "{% trans 'Protocols' %}",
'platform': "{% trans 'Platform' %}",
'os': "{% trans 'OS' %}",
'system_users_join': "{% trans 'System user' %}",
'domain': "{% trans 'Domain' %}",
'is_active': "{% trans 'Is active' %}",
'comment': "{% trans 'Comment' %}"
{#'date_joined': "{% trans 'Date joined' %}",#}
};
$.each(data.results, function(index, value){
if(value.id === asset_id){
var value;
for (var i = 0; i < data.results.length; i++) {
value = data.results[i];
if(value.id === assetId){
for(var i in desc){
trs += "<tr class='no-borders-tr'>\n" +
"<td>"+ desc[i] + ":</td>"+
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
"</tr>";
}
break
}
});
};
$('#asset_detail_tbody').html(trs)
$('#user_asset_detail_modal').modal();
});
function toggle() {
@ -204,5 +85,4 @@ function toggle() {
}
</script>
{% endblock %}

View File

@ -1,7 +1,8 @@
# ~*~ coding: utf-8 ~*~
#
import time
from django.db.models import Prefetch
from functools import reduce
from django.db.models import Prefetch, Q
from common.utils import get_object_or_none, get_logger
from common.struct import Stack
@ -21,24 +22,34 @@ def get_system_user_by_id(id):
return system_user
class LabelFilter:
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
query_keys = self.request.query_params.keys()
class LabelFilterMixin:
def get_filter_labels_ids(self):
query_params = self.request.query_params
query_keys = query_params.keys()
all_label_keys = Label.objects.values_list('name', flat=True)
valid_keys = set(all_label_keys) & set(query_keys)
labels_query = {}
for key in valid_keys:
labels_query[key] = self.request.query_params.get(key)
conditions = []
for k, v in labels_query.items():
query = {'labels__name': k, 'labels__value': v}
conditions.append(query)
if not valid_keys:
return []
if conditions:
for kwargs in conditions:
queryset = queryset.filter(**kwargs)
labels_query = [
{"name": key, "value": query_params[key]}
for key in valid_keys
]
args = [Q(**kwargs) for kwargs in labels_query]
args = reduce(lambda x, y: x | y, args)
labels_id = Label.objects.filter(args).values_list('id', flat=True)
return labels_id
class LabelFilter(LabelFilterMixin):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
labels_ids = self.get_filter_labels_ids()
if not labels_ids:
return queryset
for labels_id in labels_ids:
queryset = queryset.filter(labels=labels_id)
return queryset
@ -104,7 +115,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 +156,69 @@ class NodeUtil:
def nodes(self):
return list(self._nodes.values())
def get_family_by_key(self, key):
tree_nodes = set()
node = self.get_node_by_key(key)
if not node:
return []
tree_nodes.update(node._parents)
tree_nodes.add(node)
tree_nodes.update(node._all_children)
return list(tree_nodes)
# 使用给定节点生成一颗树
# 找到他们的祖先节点
# 可选找到他们的子孙节点
def get_family(self, nodes, with_children=False):
tree_nodes = set()
for n in nodes:
node = self.get_node_by_key(n.key)
if not node:
continue
tree_nodes.update(node._parents)
tree_nodes.add(node)
if with_children:
tree_nodes.update(node._children)
return list(tree_nodes)
def get_family(self, node):
return self.get_family_by_key(node.key)
def get_nodes_parents(self, nodes, with_self=True):
def get_family_keys_by_key(self, key):
nodes = self.get_family_by_key(key)
return [n.key for n in nodes]
def get_some_nodes_family_by_keys(self, keys):
family = set()
for key in keys:
family.update(self.get_family_by_key(key))
return family
def get_some_nodes_family_keys_by_keys(self, keys):
family = self.get_some_nodes_family_by_keys(keys)
return [n.key for n in family]
def get_nodes_parents_by_key(self, key, with_self=True):
parents = set()
for n in nodes:
node = self.get_node_by_key(n.key)
parents.update(set(node._parents))
if with_self:
parents.add(node)
return parents
node = self.get_node_by_key(key)
if not node:
return []
parents.update(set(node._parents))
if with_self:
parents.add(node)
return list(parents)
def get_node_parents(self, node, with_self=True):
return self.get_nodes_parents_by_key(node.key, with_self=with_self)
def get_nodes_parents_keys_by_key(self, key, with_self=True):
nodes = self.get_nodes_parents_by_key(key, with_self=with_self)
return [n.key for n in nodes]
def get_all_children_by_key(self, key, with_self=True):
children = set()
node = self.get_node_by_key(key)
if not node:
return []
children.update(set(node._all_children))
if with_self:
children.add(node)
return list(children)
def get_children(self, node, with_self=True):
return self.get_all_children_by_key(node.key, with_self=with_self)
def get_children_keys_by_key(self, key, with_self=True):
nodes = self.get_all_children_by_key(key, with_self=with_self)
return [n.key for n in nodes]
def test_node_tree():

View File

@ -69,7 +69,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
context = {
'action': _('My assets'),
'labels': Label.objects.all().order_by('name'),
'system_users': SystemUser.objects.all(),
'show_actions': True
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -47,6 +47,7 @@ class CommandFilterCreateView(PermissionsMixin, CreateView):
context = {
'app': _('Assets'),
'action': _('Create command filter'),
'type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -64,6 +65,7 @@ class CommandFilterUpdateView(PermissionsMixin, UpdateView):
context = {
'app': _('Assets'),
'action': _('Update command filter'),
'type': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -136,6 +138,7 @@ class CommandFilterRuleCreateView(PermissionsMixin, CreateView):
'app': _('Assets'),
'action': _('Create command filter rule'),
'object': self.cmd_filter,
'request_type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -170,6 +173,8 @@ class CommandFilterRuleUpdateView(PermissionsMixin, UpdateView):
'app': _('Assets'),
'action': _('Update command filter rule'),
'object': self.cmd_filter,
'rule': self.get_object(),
'request_type': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

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

View File

@ -44,6 +44,7 @@ class LabelCreateView(PermissionsMixin, CreateView):
context = {
'app': _('Assets'),
'action': _('Create label'),
'type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -71,6 +72,7 @@ class LabelUpdateView(PermissionsMixin, UpdateView):
context = {
'app': _('Assets'),
'action': _('Update label'),
'type': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -38,7 +38,7 @@ $(document).ready(function () {
var error = function () {
$("#mfa_error").addClass("text-danger").html(codeError);
};
APIUpdateAttr({
requestApi({
url: url,
method: "POST",
body: JSON.stringify(data),

View File

@ -4,11 +4,13 @@ import os
import uuid
from django.core.cache import cache
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import generics, serializers
from .http import HttpResponseTemporaryRedirect
from .const import KEY_CACHE_RESOURCES_ID
__all__ = [
@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView):
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
cache.set(cache_key, resources_id, 300)
return Response({'spm': spm})
@csrf_exempt
def redirect_plural_name_api(request, *args, **kwargs):
resource = kwargs.get("resource", "")
full_path = request.get_full_path()
full_path = full_path.replace(resource, resource+"s", 1)
return HttpResponseTemporaryRedirect(full_path)

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

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
#
from django.http import HttpResponse
from django.utils.encoding import iri_to_uri
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)

View File

@ -145,13 +145,13 @@ class NeedMFAVerify(permissions.BasePermission):
return False
class CanUpdateSuperUser(permissions.BasePermission):
class CanUpdateDeleteSuperUser(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in ['GET', 'OPTIONS']:
return True
if str(request.user.id) == str(obj.id):
elif request.method == 'DELETE' and str(request.user.id) == str(obj.id):
return False
if request.user.is_superuser:
elif request.user.is_superuser:
return True
if hasattr(obj, 'is_superuser') and obj.is_superuser:
return False

View File

@ -46,12 +46,17 @@ class TreeNode:
def __gt__(self, other):
if self.isParent and not other.isParent:
return False
result = False
elif not self.isParent and other.isParent:
return True
if self.pId != other.pId:
return self.pId > other.pId
return self.name > other.name
result = True
elif self.pId != other.pId:
result = self.pId > other.pId
else:
result = self.name > other.name
return result
def __le__(self, other):
return not self.__gt__(other)
def __eq__(self, other):
return self.id == other.id
@ -74,7 +79,7 @@ class Tree:
raise ValueError("Parent must not be node parent")
node.pId = parent.id
parent.isParent = True
self.nodes[node.id] = node
self.nodes[node.key] = node
def get_nodes(self):
return sorted(self.nodes.values())

View File

@ -7,6 +7,7 @@ import logging
import datetime
import uuid
from functools import wraps
import time
import copy
import ipaddress
@ -179,3 +180,18 @@ def random_string(length):
charset = string.ascii_letters + string.digits
s = [random.choice(charset) for i in range(length)]
return ''.join(s)
logger = get_logger(__name__)
def timeit(func):
def wrapper(*args, **kwargs):
logger.debug("Start call: {}".format(func.__name__))
now = time.time()
result = func(*args, **kwargs)
using = (time.time() - now) * 1000
msg = "Call {} end, using: {:.1f}ms".format(func.__name__, using)
logger.debug(msg)
return result
return wrapper

View File

@ -359,6 +359,7 @@ defaults = {
'TERMINAL_TELNET_REGEX': '',
'TERMINAL_COMMAND_STORAGE': {},
'SECURITY_MFA_AUTH': False,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30,

View File

@ -568,7 +568,7 @@ SECURITY_PASSWORD_RULES = [
'SECURITY_PASSWORD_SPECIAL_CHAR'
]
SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL
SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH
TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH
TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL

View File

@ -2,7 +2,7 @@ import datetime
import re
import time
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseRedirect
from django.conf import settings
from django.views.generic import TemplateView, View
from django.utils import timezone
@ -13,13 +13,14 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.utils.encoding import iri_to_uri
from users.models import User
from assets.models import Asset
from terminal.models import Session
from orgs.utils import current_org
from common.permissions import PermissionsMixin, IsValidUser
from common.http import HttpResponseTemporaryRedirect
class IndexView(PermissionsMixin, TemplateView):
@ -203,14 +204,6 @@ class I18NView(View):
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
@csrf_exempt
def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode()

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -255,7 +255,7 @@ function execute() {
}
}
APIUpdateAttr({
requestApi({
url: url,
body: JSON.stringify(data),
method: 'POST',

View File

@ -109,7 +109,7 @@ $(document).ready(function() {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
requestApi({
url: the_url,
error: error,
method: 'GET',

View File

@ -4,7 +4,8 @@
from django.utils import timezone
from django.db.models import Q
from rest_framework.views import Response
from rest_framework.generics import RetrieveUpdateAPIView
from django.shortcuts import get_object_or_404
from rest_framework.generics import RetrieveUpdateAPIView, ListAPIView
from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination
@ -20,7 +21,7 @@ from .. import serializers
__all__ = [
'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi',
'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi',
'AssetPermissionAddAssetApi',
'AssetPermissionAddAssetApi', 'AssetPermissionAssetsApi',
]
@ -232,3 +233,22 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAssetsApi(ListAPIView):
permission_classes = (IsOrgAdmin,)
pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetPermissionAssetsSerializer
filter_fields = ("hostname", "ip")
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(AssetPermission, pk=pk)
def get_queryset(self):
perm = self.get_object()
assets = perm.get_all_assets().only(
*self.serializer_class.Meta.only_fields
)
return assets

225
apps/perms/api/mixin.py Normal file
View File

@ -0,0 +1,225 @@
# -*- coding: utf-8 -*-
#
from functools import reduce
from hashlib import md5
from django.core.cache import cache
from django.db.models import Q
from django.conf import settings
from rest_framework.views import Response
from django.utils.translation import ugettext as _
from common.utils import get_logger
from assets.utils import LabelFilterMixin
from ..utils import (
AssetPermissionUtil
)
from .. import const
from ..hands import Asset, Node, SystemUser, Label
from .. import serializers
logger = get_logger(__name__)
__all__ = ['UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin']
class UserPermissionCacheMixin:
cache_policy = '0'
RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
_object = None
def get_object(self):
return None
# 内部使用可控制缓存
def _get_object(self):
if not self._object:
self._object = self.get_object()
return self._object
def get_object_id(self):
obj = self._get_object()
if obj:
return str(obj.id)
return None
def get_request_md5(self):
path = self.request.path
query = {k: v for k, v in self.request.GET.items()}
query.pop("_", None)
query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
full_path = "{}?{}".format(path, query)
return md5(full_path.encode()).hexdigest()
def get_meta_cache_id(self):
obj = self._get_object()
util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
meta_cache_id = util.cache_meta.get('id')
return meta_cache_id
def get_response_cache_id(self):
obj_id = self.get_object_id()
request_md5 = self.get_request_md5()
meta_cache_id = self.get_meta_cache_id()
resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
return resp_cache_id
def get_response_from_cache(self):
# 没有数据缓冲
meta_cache_id = self.get_meta_cache_id()
if not meta_cache_id:
logger.debug("Not get meta id: {}".format(meta_cache_id))
return None
# 从响应缓冲里获取响应
key = self.get_response_key()
data = cache.get(key)
if not data:
logger.debug("Not get response from cache: {}".format(key))
return None
logger.debug("Get user permission from cache: {}".format(self.get_object()))
response = Response(data)
return response
def expire_response_cache(self):
obj_id = self.get_object_id()
expire_cache_id = '{}_{}'.format(obj_id, '*')
key = self.RESP_CACHE_KEY.format(expire_cache_id)
cache.delete_pattern(key)
def get_response_key(self):
resp_cache_id = self.get_response_cache_id()
key = self.RESP_CACHE_KEY.format(resp_cache_id)
return key
def set_response_to_cache(self, response):
key = self.get_response_key()
cache.set(key, response.data, self.CACHE_TIME)
logger.debug("Set response to cache: {}".format(key))
def get(self, request, *args, **kwargs):
self.cache_policy = request.GET.get('cache_policy', '0')
obj = self._get_object()
if obj is None:
logger.debug("Not get response from cache: obj is none")
return super().get(request, *args, **kwargs)
if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
return super().get(request, *args, **kwargs)
elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
self.expire_response_cache()
logger.debug("Try get response from cache")
resp = self.get_response_from_cache()
if not resp:
resp = super().get(request, *args, **kwargs)
self.set_response_to_cache(resp)
return resp
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 GrantAssetsMixin(LabelFilterMixin):
serializer_class = serializers.AssetGrantedSerializer
def get_serializer_queryset(self, queryset):
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 data
def get_serializer(self, queryset_list, many=True):
data = self.get_serializer_queryset(queryset_list)
return super().get_serializer(data, many=True)
def search_queryset(self, assets_items):
search = self.request.query_params.get("search")
if not search:
return assets_items
assets_map = {asset['id']: asset for asset in assets_items}
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)
return [assets_map.get(asset_id) for asset_id in assets_ids_search]
def filter_queryset_by_label(self, assets_items):
labels_id = self.get_filter_labels_ids()
if not labels_id:
return assets_items
assets_map = {asset['id']: asset for asset in assets_items}
assets_matched = Asset.objects.filter(id__in=assets_map.keys())
for label_id in labels_id:
assets_matched = assets_matched.filter(labels=label_id)
assets_ids_matched = assets_matched.values_list('id', flat=True)
return [assets_map.get(asset_id) for asset_id in assets_ids_matched]
def sort_queryset(self, assets_items):
order_by = self.request.query_params.get('order', 'hostname')
if order_by not in ['hostname', '-hostname', 'ip', '-ip']:
order_by = 'hostname'
assets_map = {asset['id']: asset for asset in assets_items}
assets_ids_search = Asset.objects.filter(id__in=assets_map.keys())\
.order_by(order_by)\
.values_list('id', flat=True)
return [assets_map.get(asset_id) for asset_id in assets_ids_search]
def filter_queryset(self, assets_items):
assets_items = self.search_queryset(assets_items)
assets_items = self.filter_queryset_by_label(assets_items)
assets_items = self.sort_queryset(assets_items)
return assets_items

View File

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

View File

@ -2,153 +2,67 @@
#
from django.shortcuts import get_object_or_404
from rest_framework.generics import (
ListAPIView, get_object_or_404,
)
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 .. import serializers, const
from ..hands import UserGroup
from .. import serializers
from .user_permission import (
UserGrantedAssetsApi, UserGrantedNodesApi, UserGrantedNodesWithAssetsApi,
UserGrantedNodesWithAssetsAsTreeApi, UserGrantedNodeAssetsApi,
UserGrantedNodesAsTreeApi,
)
__all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
'UserGroupGrantedNodesWithAssetsAsTreeApi',
'UserGroupGrantedRemoteAppsApi',
'UserGroupGrantedNodesWithAssetsAsTreeApi', 'UserGroupGrantedNodesAsTreeApi',
]
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 UserGroupGrantedNodesAsTreeApi(UserGrantedNodesAsTreeApi):
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(UserGrantedNodesWithAssetsApi):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeGrantedSerializer
def get_queryset(self):
def get_object(self):
user_group_id = self.kwargs.get('pk', '')
queryset = []
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(user_group)
nodes = util.get_nodes_with_assets()
for node, _assets in nodes.items():
assets = _assets.keys()
for asset, system_users in _assets.items():
asset.system_users_granted = system_users
node.assets_granted = assets
queryset.append(node)
return queryset
return user_group
class UserGroupGrantedNodesWithAssetsAsTreeApi(ListAPIView):
serializer_class = TreeNodeSerializer
permission_classes = (IsOrgAdminOrAppUser,)
show_assets = True
system_user_id = None
def get(self, request, *args, **kwargs):
self.show_assets = request.query_params.get('show_assets', '1') == '1'
self.system_user_id = request.query_params.get('system_user')
return super().get(request, *args, **kwargs)
def get_queryset(self):
class UserGroupGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsAsTreeApi):
def get_object(self):
user_group_id = self.kwargs.get('pk', '')
queryset = []
group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(group)
if self.system_user_id:
util.filter_permissions(system_users=self.system_user_id)
nodes = util.get_nodes_with_assets()
for node, assets in nodes.items():
data = parse_node_to_tree_node(node)
queryset.append(data)
if not self.show_assets:
continue
for asset, system_users in assets.items():
data = parse_asset_to_tree_node(node, asset, system_users)
queryset.append(data)
queryset = sorted(queryset)
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id)
return user_group
class UserGroupGrantedNodeAssetsApi(ListAPIView):
class UserGroupGrantedNodeAssetsApi(UserGrantedNodeAssetsApi):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer
def get_queryset(self):
def get_object(self):
user_group_id = self.kwargs.get('pk', '')
node_id = self.kwargs.get('node_id')
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(user_group)
if str(node_id) == const.UNGROUPED_NODE_ID:
node = util.tree.ungrouped_node
else:
node = get_object_or_404(Node, id=node_id)
nodes = util.get_nodes_with_assets()
assets = nodes.get(node, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
return assets
# RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView):
permission_classes = (IsOrgAdmin, )
serializer_class = RemoteAppSerializer
def get_queryset(self):
queryset = []
user_group_id = self.kwargs.get('pk')
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = RemoteAppPermissionUtil(user_group)
queryset = util.get_remote_apps()
return queryset
return user_group

View File

@ -2,9 +2,9 @@
#
import time
import traceback
from hashlib import md5
from django.core.cache import cache
from django.conf import settings
from functools import reduce
import uuid
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 (
@ -16,126 +16,30 @@ from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
AssetPermissionUtil, ParserNode,
)
from .mixin import UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
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_{}'
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
_object = None
def get_object(self):
return None
# 内部使用可控制缓存
def _get_object(self):
if not self._object:
self._object = self.get_object()
return self._object
def get_object_id(self):
obj = self._get_object()
if obj:
return str(obj.id)
return None
def get_request_md5(self):
path = self.request.path
query = {k: v for k, v in self.request.GET.items()}
query.pop("_", None)
query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
full_path = "{}?{}".format(path, query)
return md5(full_path.encode()).hexdigest()
def get_meta_cache_id(self):
obj = self._get_object()
util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
meta_cache_id = util.cache_meta.get('id')
return meta_cache_id
def get_response_cache_id(self):
obj_id = self.get_object_id()
request_md5 = self.get_request_md5()
meta_cache_id = self.get_meta_cache_id()
resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
return resp_cache_id
def get_response_from_cache(self):
# 没有数据缓冲
meta_cache_id = self.get_meta_cache_id()
if not meta_cache_id:
logger.debug("Not get meta id: {}".format(meta_cache_id))
return None
# 从响应缓冲里获取响应
key = self.get_response_key()
data = cache.get(key)
if not data:
logger.debug("Not get response from cache: {}".format(key))
return None
logger.debug("Get user permission from cache: {}".format(self.get_object()))
response = Response(data)
return response
def expire_response_cache(self):
obj_id = self.get_object_id()
expire_cache_id = '{}_{}'.format(obj_id, '*')
key = self.RESP_CACHE_KEY.format(expire_cache_id)
cache.delete_pattern(key)
def get_response_key(self):
resp_cache_id = self.get_response_cache_id()
key = self.RESP_CACHE_KEY.format(resp_cache_id)
return key
def set_response_to_cache(self, response):
key = self.get_response_key()
cache.set(key, response.data, self.CACHE_TIME)
logger.debug("Set response to cache: {}".format(key))
def get(self, request, *args, **kwargs):
self.cache_policy = request.GET.get('cache_policy', '0')
obj = self._get_object()
if obj is None:
logger.debug("Not get response from cache: obj is none")
return super().get(request, *args, **kwargs)
if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
return super().get(request, *args, **kwargs)
elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
self.expire_response_cache()
logger.debug("Try get response from cache")
resp = self.get_response_from_cache()
if not resp:
resp = super().get(request, *args, **kwargs)
self.set_response_to_cache(resp)
return resp
class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView):
class UserGrantedAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
"""
用户授权的所有资产
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer
pagination_class = LimitOffsetPagination
def get_object(self):
@ -147,17 +51,9 @@ 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()
return queryset
def get_permissions(self):
@ -166,120 +62,11 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
return super().get_permissions()
class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView):
"""
查询用户授权的所有节点的API
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = NodeSerializer
def get_object(self):
user_id = self.kwargs.get('pk', '')
if user_id:
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
return user
def get_queryset(self):
user = self.get_object()
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
nodes = util.get_nodes()
return nodes
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView):
"""
用户授权的节点并带着节点下资产的api
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.NodeGrantedSerializer
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_queryset(self):
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
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_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
)
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
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 +78,30 @@ 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})
return assets
def get_permissions(self):
@ -318,90 +110,202 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List
return super().get_permissions()
class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
class UserGrantedNodesApi(UserPermissionCacheMixin, NodesWithUngroupMixin, ListAPIView):
"""
获取用户自己授权节点下子节点的api
查询用户授权的所有节点的API
"""
permission_classes = (IsValidUser,)
serializer_class = serializers.AssetPermissionNodeSerializer
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = NodeSerializer
pagination_class = LimitOffsetPagination
only_fields = NodeSerializer.Meta.only_fields
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)
user_id = self.kwargs.get('pk', '')
if user_id:
user = get_object_or_404(User, id=user_id)
else:
for node in root_nodes:
node.assets_amount = len(nodes_granted[node])
queryset.append(node)
return queryset
user = self.request.user
return user
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_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)
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
_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):
keyword = self.request.query_params.get('search')
if keyword:
return self.get_search_queryset(keyword)
user = self.get_object()
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:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
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', '')
if not user_id:
user = self.request.user
else:
return self.get_children_queryset()
user = get_object_or_404(User, id=user_id)
return user
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 = []
_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 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):
user = self.get_object()
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_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)
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 self.serializer_class(queryset, many=True)
class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
@ -412,24 +316,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):
@ -442,16 +346,12 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView
system_id = self.request.query_params.get('system_user_id', '')
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, {})
_object = {}
if su not in granted_system_users:
_object['actions'] = 0
else:
_object['actions'] = granted_system_users[su]
return _object
assets = util.get_assets()
actions = 0
for asset in assets:
if asset_id == asset["id"]:
actions = asset["system_users"].get(system_id, 0)
break
return {"actions": actions}

View File

@ -13,13 +13,13 @@ from ..utils import (
RemoteAppPermissionUtil, construct_remote_apps_tree_root,
parse_remote_app_to_tree_node,
)
from ..hands import User, RemoteApp, RemoteAppSerializer
from ..hands import User, RemoteApp, RemoteAppSerializer, UserGroup
from ..mixins import RemoteAppFilterMixin
__all__ = [
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
'UserGrantedRemoteAppsAsTreeApi',
'UserGrantedRemoteAppsAsTreeApi', 'UserGroupGrantedRemoteAppsApi',
]
@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView):
if remote_app not in remote_apps:
return Response({'msg': False}, status=403)
return Response({'msg': True}, status=200)
# RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser, )
serializer_class = RemoteAppSerializer
def get_queryset(self):
queryset = []
user_group_id = self.kwargs.get('pk')
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = RemoteAppPermissionUtil(user_group)
queryset = util.get_remote_apps()
return queryset

View File

@ -3,3 +3,4 @@
UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002"
EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003"
EMPTY_NODE_KEY = "1:-2"

View File

@ -41,6 +41,9 @@ class AssetPermissionForm(OrgModelForm):
users_field = self.fields.get('users')
users_field.queryset = current_org.get_org_users()
nodes_field = self.fields['nodes']
nodes_field.choices = ((n.id, n.full_value) for n in Node.get_queryset())
# 前端渲染优化, 防止过多资产
if not self.data:
instance = kwargs.get('instance')
@ -49,8 +52,6 @@ class AssetPermissionForm(OrgModelForm):
assets_field.queryset = instance.assets.all()
else:
assets_field.queryset = Asset.objects.none()
nodes_field = self.fields['nodes']
nodes_field._queryset = Node.get_queryset()
class Meta:
model = AssetPermission

View File

@ -2,7 +2,7 @@
#
from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node
from assets.models import Asset, SystemUser, Node, Label
from assets.serializers import NodeSerializer
from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp

View File

@ -2,10 +2,12 @@ import uuid
from functools import reduce
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin
from assets.models import Asset, SystemUser, Node
from .base import BasePermission
@ -85,14 +87,23 @@ 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())
for node in self.nodes.all():
_assets = node.get_all_assets()
set_or_append_attr_bulk(_assets, 'inherit', node.value)
assets.update(set(_assets))
args = [Q(granted_by_permissions=self)]
pattern = set()
nodes_keys = self.nodes.all().values_list('key', flat=True)
for key in nodes_keys:
pattern.add(r'^{0}$|^{0}:'.format(key))
pattern = '|'.join(list(pattern))
if pattern:
args.append(Q(nodes__key__regex=pattern))
args = reduce(lambda x, y: x | y, args)
assets = Asset.objects.filter(args)
return assets

View File

@ -6,11 +6,12 @@ from rest_framework import serializers
from common.fields import StringManyToManyField
from orgs.mixins import BulkOrgResourceModelSerializer
from perms.models import AssetPermission, Action
from assets.models import Asset
__all__ = [
'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer',
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'ActionsField',
'ActionsField', 'AssetPermissionAssetsSerializer',
]
@ -70,3 +71,11 @@ class AssetPermissionUpdateAssetSerializer(serializers.ModelSerializer):
class Meta:
model = AssetPermission
fields = ['id', 'assets']
class AssetPermissionAssetsSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
only_fields = ['id', 'hostname', 'ip']
fields = tuple(only_fields)

View File

@ -2,16 +2,16 @@
#
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from assets.models import Node, SystemUser
from assets.serializers import AssetSerializer
from assets.models import Node, SystemUser, Asset
from assets.serializers import ProtocolsField
from .asset_permission import ActionsField
__all__ = [
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer',
'GrantedNodeSerializer',
'NodeGrantedSerializer', 'AssetGrantedSerializer',
'ActionsSerializer',
'ActionsSerializer', 'AssetSystemUserSerializer',
]
@ -23,87 +23,56 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = (
'id', 'name', 'username', 'priority', "actions",
only_fields = (
'id', 'name', 'username', 'priority',
'protocol', 'login_mode',
)
fields = list(only_fields) + ["actions"]
read_only_fields = fields
class AssetGrantedSerializer(AssetSerializer):
class AssetGrantedSerializer(serializers.ModelSerializer):
"""
被授权资产的数据结构
"""
protocols = ProtocolsField(label=_('Protocols'), required=False, read_only=True)
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField()
system_users_only_fields = AssetSystemUserSerializer.Meta.only_fields
class Meta:
model = Asset
only_fields = [
"id", "hostname", "ip", "protocols", "os", 'domain',
"platform", "org_id",
]
fields = only_fields + ['system_users_granted', 'system_users_join', "org_name"]
read_only_fields = fields
@staticmethod
def get_system_users_join(obj):
system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
def get_field_names(self, declared_fields, info):
fields = (
"id", "hostname", "ip", "protocols",
"system_users_granted", "is_active", "system_users_join", "os",
'domain', "platform", "comment", "org_id", "org_name",
)
return fields
class AssetPermissionNodeSerializer(serializers.ModelSerializer):
asset = AssetGrantedSerializer(required=False)
assets_amount = serializers.SerializerMethodField()
tree_id = serializers.SerializerMethodField()
tree_parent = serializers.SerializerMethodField()
class Meta:
model = Node
fields = [
'id', 'key', 'value', 'asset', 'is_node', 'org_id',
'tree_id', 'tree_parent', 'assets_amount',
]
@staticmethod
def get_assets_amount(obj):
return obj.assets_amount
@staticmethod
def get_tree_id(obj):
return obj.key
@staticmethod
def get_tree_parent(obj):
return obj.parent_key
class NodeGrantedSerializer(serializers.ModelSerializer):
"""
授权资产组
"""
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
assets_amount = serializers.SerializerMethodField()
parent = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
assets_amount = serializers.ReadOnlyField()
name = serializers.ReadOnlyField(source='value')
assets_only_fields = AssetGrantedSerializer.Meta.only_fields
system_users_only_fields = AssetGrantedSerializer.system_users_only_fields
class Meta:
model = Node
fields = [
'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount', 'org_id',
only_fields = ['id', 'key', 'value', "org_id"]
fields = only_fields + [
'name', 'assets_granted', 'assets_amount',
]
@staticmethod
def get_assets_amount(obj):
return len(obj.assets_granted)
@staticmethod
def get_name(obj):
return obj.name
@staticmethod
def get_parent(obj):
return obj.parent.id
read_only_fields = fields
class GrantedNodeSerializer(serializers.ModelSerializer):
@ -112,6 +81,7 @@ class GrantedNodeSerializer(serializers.ModelSerializer):
fields = [
'id', 'name', 'key', 'value',
]
read_only_fields = fields
class ActionsSerializer(serializers.Serializer):

View File

@ -48,29 +48,19 @@
</div>
</div>
<div class="ibox-content">
<table class="table table-hover">
<table class="table table-striped table-bordered table-hover" id="asset_list_table" style="width: 100%">
<thead>
<tr>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th></th>
</tr>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
</tr>
</thead>
<tbody>
{% for asset in object_list %}
<tr>
<td>{{ asset.hostname }}</td>
<td>{{ asset.ip }}</td>
<td>
<button title="{{ asset.inherit }}" data-gid="{{ asset.id }}" class="btn btn-danger btn-xs btn-remove-asset {% if asset.inherit %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row">
{% include '_pagination.html' %}
</div>
</div>
</div>
</div>
@ -86,9 +76,6 @@
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select assets' %}" class="select2" id="asset_select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}">{{ asset }}</option>
{% endfor %}
</select>
</td>
</tr>
@ -146,6 +133,7 @@
</div>
</div>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
@ -157,12 +145,12 @@ function addAssets(assets) {
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
}
function removeAssets(assets) {
var the_url = "{% url 'api-perms:asset-permission-remove-asset' pk=asset_permission.id %}";
@ -172,7 +160,7 @@ function removeAssets(assets) {
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -184,16 +172,64 @@ function updateNodes(nodes, success) {
var body = {
nodes: nodes
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
var table;
function initAssetTable() {
var options = {
ele: $('#asset_list_table'),
toggle: true,
columnDefs: [
{
targets: 0, createdCell: function (td, cellData, rowData) {
var html = '<input type="checkbox" class="text-center ipt_check" id="id_' + cellData + '">';
$(td).html(html);
}
},
],
ajax_url: "{% url 'api-perms:asset-permission-assets' pk=object.id %}",
columns: [
{data: "id"}, {data: "hostname"}, {data: "ip"}
],
op_html: $('#actions').html()
};
table = jumpserver.initServerSideDataTable(options);
return table
}
$(document).ready(function () {
$('.select2').select2();
$('.select2').select2();
table = initAssetTable();
$("#asset_select2").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
initSelectedAssets2Table('#asset_select2');
}
})
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
var options = [];
$('#asset_select2 option').each(function (i, v) {
options.push(v.value)
});
asset_table2.selected_rows.forEach(function (i) {
var name = i.hostname + '(' + i.ip + ')';
var option = new Option(name, i.id, false, true);
if (options.indexOf(i.id) === -1) {
$('#asset_select2').append(option).trigger('change');
}
});
$('#asset_select2').val(assets).trigger('change');
$("#asset_list_modal").modal('hide');
})
.on('click', '.btn-add-assets', function () {
var assets_selected = $("#asset_select2 option:selected").map(function () {
@ -237,7 +273,7 @@ $(document).ready(function () {
});
};
updateNodes(nodes, success);
})
})
.on('click', '.btn-remove-node', function () {
var $this = $(this);
var $tr = $this.closest('tr');

View File

@ -68,7 +68,7 @@
</tr>
<tr>
<td>{% trans 'User group count' %}:</td>
<td><b>{{ object.users.count }}</b></td>
<td><b>{{ object.user_groups.count }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset count' %}:</td>
@ -187,7 +187,7 @@ function updateSystemUser(system_users) {
var body = {
system_users: Object.assign([], system_users)
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body)
});
@ -247,7 +247,7 @@ $(document).ready(function () {
var body = {
'is_active': checked
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
});

View File

@ -160,7 +160,7 @@ function addUsers(users) {
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -175,7 +175,7 @@ function removeUser(users) {
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -187,7 +187,7 @@ function updateGroup(groups) {
var body = {
user_groups: groups
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body)
});

View File

@ -116,5 +116,28 @@ $(document).ready(function () {
$('#date_start').daterangepicker(dateOptions);
$('#date_expired').daterangepicker(dateOptions);
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var method = "POST";
var the_url = '{% url "api-perms:remote-app-permission-list" %}';
var redirect_to = '{% url "perms:remote-app-permission-list" %}';
{% if type == "update" %}
the_url = '{% url "api-perms:remote-app-permission-detail" pk=object.id %}';
method = "PUT";
{% endif %}
objectAttrsIsList(data, ['users', 'user_groups', 'remote_apps']);
objectAttrsIsDatetime(data, ['date_expired', 'date_start']);
objectAttrsIsBool(data, ['is_active']);
var props = {
url:the_url,
data:data,
method:method,
form:form,
redirect_to:redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}

View File

@ -160,7 +160,7 @@ $(document).ready(function () {
var body = {
'is_active': checked
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body)
});

View File

@ -120,7 +120,7 @@
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -134,7 +134,7 @@
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success

View File

@ -158,7 +158,7 @@
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -172,7 +172,7 @@
var success = function(data) {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -183,7 +183,7 @@
var body = {
user_groups: groups
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body)
});

View File

@ -1,7 +1,8 @@
# coding:utf-8
from django.urls import path
from django.urls import path, re_path
from rest_framework import routers
from common import api as capi
from .. import api
app_name = 'perms'
@ -12,26 +13,37 @@ 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/tree/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesAsTreeApi.as_view(), name='user-group-nodes-as-tree'),
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-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'),
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'),
@ -39,26 +51,29 @@ asset_permission_urlpatterns = [
path('asset-permissions/<uuid:pk>/asset/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'),
path('asset-permissions/<uuid:pk>/asset/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'),
# 授权规则中授权的资产
path('asset-permissions/<uuid:pk>/assets/', api.AssetPermissionAssetsApi.as_view(), name='asset-permission-assets'),
# 验证用户是否有某个资产和系统用户的权限
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 +82,11 @@ remote_app_permission_urlpatterns = [
path('remote-app-permissions/<uuid:pk>/remote-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
]
urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns
old_version_urlpatterns = [
re_path('(?P<resource>user|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api)
]
urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns + old_version_urlpatterns
urlpatterns += router.urls

View File

@ -1,80 +1,65 @@
# coding: utf-8
import time
import uuid
from collections import defaultdict
import json
from hashlib import md5
import time
import itertools
from django.utils import timezone
from django.db.models import Q
from django.core.cache import cache
from django.conf import settings
from django.utils.translation import ugettext as _
from orgs.utils import set_to_root_org
from common.utils import get_logger
from common.utils import get_logger, timeit
from common.tree import TreeNode
from assets.utils import NodeUtil
from .. import const
from ..models import AssetPermission, Action
from ..hands import Node, Asset
from assets.utils import NodeUtil
from .stack import PermSystemUserNodeUtil, PermAssetsAmountUtil
logger = get_logger(__file__)
__all__ = [
'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets',
'parse_asset_to_tree_node', 'parse_node_to_tree_node',
'ParserNode',
]
class TreeNodeCounter(NodeUtil):
def __init__(self, nodes):
self.__nodes = nodes
super().__init__(with_assets_amount=True)
def get_queryset(self):
return self.__nodes
def timeit(func):
def wrapper(*args, **kwargs):
logger.debug("Start call: {}".format(func.__name__))
now = time.time()
result = func(*args, **kwargs)
using = time.time() - now
logger.debug("Call {} end, using: {:.2}s".format(func.__name__, using))
return result
return wrapper
class GenerateTree:
def __init__(self):
"""
nodes = {
"<node1>": {
node.key: {
"system_users": {
"system_user": action,
"system_user2": action,
system_user.id: actions,
},
"assets": set([<asset_instance>]),
}
"assets": set([asset.id,]),
},
}
assets = {
"<asset_instance2>": {
"system_user": action,
"system_user2": action,
asset.id: {
system_user.id: actions,
},
}
"""
self._node_util = None
self.nodes = defaultdict(lambda: {"system_users": defaultdict(int), "assets": set(), "assets_amount": 0})
self.nodes = defaultdict(lambda: {
"system_users": defaultdict(int), "assets": set(),
"assets_amount": 0, "all_assets": set(),
})
self.assets = defaultdict(lambda: defaultdict(int))
self._root_node = None
self._ungroup_node = None
self._nodes_with_assets = None
self._all_assets_nodes_key = None
self._asset_counter = 0
self._system_user_counter = 0
self._nodes_assets_counter = 0
@property
def node_util(self):
@ -82,115 +67,160 @@ class GenerateTree:
self._node_util = NodeUtil()
return self._node_util
@staticmethod
def key_sort(key):
key_list = [int(i) for i in key.split(':')]
return len(key_list), key_list
@property
def root_node(self):
def root_key(self):
if self._root_node:
return self._root_node
all_nodes = self.nodes.keys()
all_keys = self.nodes.keys()
# 如果没有授权节点,就放到默认的根节点下
if not all_nodes:
if not all_keys:
return None
root_node = min(all_nodes)
self._root_node = root_node
return root_node
root_key = min(all_keys, key=self.key_sort)
self._root_key = root_key
return root_key
@property
def ungrouped_node(self):
def all_assets_nodes_keys(self):
if not self._all_assets_nodes_key:
self._all_assets_nodes_key = Asset.get_all_nodes_keys()
return self._all_assets_nodes_key
@property
def ungrouped_key(self):
if self._ungroup_node:
return self._ungroup_node
node_id = const.UNGROUPED_NODE_ID
if self.root_node:
node_key = "{}:{}".format(self.root_node.key, self.root_node.child_mark)
if self.root_key:
node_key = "{}:{}".format(self.root_key, '-1')
else:
node_key = '0:0'
node_value = _("Default")
node = Node(id=node_id, key=node_key, value=node_value)
self.add_node(node, {})
self._ungroup_node = node
return node
node_key = '1:-1'
self._ungroup_node = node_key
return node_key
@property
def empty_node(self):
node_id = const.EMPTY_NODE_ID
value = _('Empty')
node = Node(id=node_id, value=value)
return node
@timeit
def add_assets_without_system_users(self, assets_ids):
for asset_id in assets_ids:
self.add_asset(asset_id, {})
#@timeit
def add_assets_without_system_users(self, assets):
for asset in assets:
self.add_asset(asset, {})
@timeit
def add_assets(self, assets_ids_with_system_users):
for asset_id, system_users_ids in assets_ids_with_system_users.items():
self.add_asset(asset_id, system_users_ids)
#@timeit
def add_assets(self, assets):
for asset, system_users in assets.items():
self.add_asset(asset, system_users)
# @timeit
def add_asset(self, asset_id, system_users_ids=None):
"""
:param asset_id:
:param system_users_ids: {system_user.id: actions, }
:return:
"""
if not system_users_ids:
system_users_ids = defaultdict(int)
# #@timeit
def add_asset(self, asset, system_users=None):
nodes = asset.nodes.all()
nodes = self.node_util.get_nodes_by_queryset(nodes)
if not system_users:
system_users = defaultdict(int)
else:
system_users = {k: v for k, v in system_users.items()}
_system_users = self.assets[asset]
for system_user, action in _system_users.items():
system_users[system_user] |= action
# 获取已有资产的系统用户和actions并更新到最新系统用户信息中
old_system_users_ids = self.assets[asset_id]
for system_user_id, action in old_system_users_ids.items():
system_users_ids[system_user_id] |= action
# 获取父节点们
parents = self.node_util.get_nodes_parents(nodes, with_self=True)
for node in parents:
_system_users = self.nodes[node]["system_users"]
self.nodes[node]["assets_amount"] += 1
for system_user, action in _system_users.items():
system_users[system_user] |= action
# 过滤系统用户的协议
system_users = {s: v for s, v in system_users.items() if asset.has_protocol(s.protocol)}
self.assets[asset] = system_users
in_nodes = set(self.nodes.keys()) & set(nodes)
asset_nodes_keys = self.all_assets_nodes_keys.get(asset_id, [])
# {asset.id: [node.key, ], }
# 获取用户在的节点
in_nodes = set(self.nodes.keys()) & set(asset_nodes_keys)
if not in_nodes:
self.nodes[self.ungrouped_node]["assets_amount"] += 1
self.nodes[self.ungrouped_node]["assets"].add(system_users)
self.nodes[self.ungrouped_key]["assets"].add(asset_id)
self.assets[asset_id] = system_users_ids
return
for node in in_nodes:
self.nodes[node]["assets"].add(asset)
# 遍历用户应该在的节点
for key in in_nodes:
# 把自己加入到树上的节点中
self.nodes[key]["assets"].add(asset_id)
# 获取自己所在节点的系统用户,并添加进去
node_system_users_ids = self.nodes[key]["system_users"]
for system_user_id, action in node_system_users_ids.items():
system_users_ids[system_user_id] |= action
self.assets[asset_id] = system_users_ids
def add_node(self, node, system_users=None):
if not system_users:
system_users = defaultdict(int)
self.nodes[node]["system_users"] = system_users
def add_node(self, node_key, system_users_ids=None):
"""
:param node_key: node.key
:param system_users_ids: {system_user.id: actions,}
:return:
"""
if not system_users_ids:
system_users_ids = defaultdict(int)
self.nodes[node_key]["system_users"] = system_users_ids
# 添加树节点
#@timeit
def add_nodes(self, nodes):
_nodes = nodes.keys()
family = self.node_util.get_family(_nodes, with_children=True)
for node in family:
self.add_node(node, nodes.get(node, {}))
@timeit
def add_nodes(self, nodes_keys_with_system_users_ids):
"""
:param nodes_keys_with_system_users_ids:
{node.key: {system_user.id: actions,}, }
:return:
"""
util = PermSystemUserNodeUtil()
family = util.get_nodes_family_and_system_users(nodes_keys_with_system_users_ids)
for key, system_users in family.items():
self.add_node(key, system_users)
def get_assets(self):
return dict(self.assets)
"""
:return:
[
{"id": asset.id, "system_users": {system_user.id: actions, }},
]
"""
assets = []
for asset_id, system_users in self.assets.items():
assets.append({"id": asset_id, "system_users": system_users})
return assets
#@timeit
@timeit
def get_nodes_with_assets(self):
"""
:return:
[
{
'key': node.key,
'assets_amount': 10
'assets': {
asset.id: {
system_user.id: actions,
},
},
},
]
"""
if self._nodes_with_assets:
return self._nodes_with_assets
nodes = {}
for node, values in self.nodes.items():
node._assets_amount = values["assets_amount"]
nodes[node] = {asset: self.assets.get(asset, {}) for asset in values["assets"]}
util = PermAssetsAmountUtil()
nodes_with_assets_amount = util.compute_nodes_assets_amount(self.nodes)
nodes = []
for key, values in nodes_with_assets_amount.items():
assets = {asset_id: self.assets.get(asset_id) for asset_id in values["assets"]}
nodes.append({
"key": key, "assets": assets,
"assets_amount": values["assets_amount"]
})
# 如果返回空节点,页面构造授权资产树报错
if not nodes:
nodes[self.empty_node] = {}
nodes.append({
"key": const.EMPTY_NODE_KEY, "assets": {}, "assets_amount": 0
})
nodes.sort(key=lambda n: self.key_sort(n["key"]))
self._nodes_with_assets = nodes
return dict(nodes)
return nodes
def get_nodes(self):
return list(self.nodes.keys())
nodes = list(self.nodes.keys())
if not nodes:
nodes.append(const.EMPTY_NODE_KEY)
return list(nodes)
def get_user_permissions(user, include_group=True):
@ -228,8 +258,8 @@ def get_system_user_permissions(system_user):
class AssetPermissionCacheMixin:
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_'
CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_'
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_V2_'
CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_V2_'
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh'))
cache_policy = '1'
@ -283,6 +313,7 @@ class AssetPermissionCacheMixin:
return self.get_cache_key('SYSTEM_USER')
def get_resource_from_cache(self, resource):
logger.debug("Try get resource from cache")
key_map = {
"assets": self.asset_key,
"nodes": self.node_key,
@ -294,18 +325,22 @@ class AssetPermissionCacheMixin:
raise ValueError("Not a valid resource: {}".format(resource))
cached = cache.get(key)
if not cached:
logger.debug("Not found resource cache, update it")
self.update_cache()
cached = cache.get(key)
return cached
def get_resource(self, resource):
if self._is_using_cache():
logger.debug("Using cache to get resource")
return self.get_resource_from_cache(resource)
elif self._is_refresh_cache():
logger.debug("Need refresh cache")
self.expire_cache()
data = self.get_resource_from_cache(resource)
return data
else:
logger.debug("Not using cache get source")
return self.get_resource_without_cache(resource)
def get_resource_without_cache(self, resource):
@ -430,88 +465,91 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
self._permissions = permissions
return permissions
#@timeit
@timeit
def filter_permissions(self, **filters):
filters_json = json.dumps(filters, sort_keys=True)
self._permissions = self.permissions.filter(**filters)
self._filter_id = md5(filters_json.encode()).hexdigest()
#@timeit
@timeit
def get_nodes_direct(self):
"""
返回用户/组授权规则直接关联的节点
:return: {node1: {system_user1: {'actions': set()},}}
返回直接授权的节点
并将节点添加到tree.nodes中并将节点下的资产添加到tree.assets中
:return:
{node.key: {system_user.id: actions,}, }
"""
if self._nodes_direct:
return self._nodes_direct
nodes = defaultdict(lambda: defaultdict(int))
nodes_keys = defaultdict(lambda: defaultdict(int))
for perm in self.permissions:
actions = [perm.actions]
system_users = perm.system_users.all()
_nodes = perm.nodes.all()
for node, system_user, action in itertools.product(_nodes, system_users, actions):
nodes[node][system_user] |= action
self.tree.add_nodes(nodes)
self._nodes_direct = nodes
return nodes
system_users_ids = [s.id for s in perm.system_users.all()]
_nodes_keys = [n.key for n in perm.nodes.all()]
iterable = itertools.product(_nodes_keys, system_users_ids, actions)
for node_key, sys_id, action in iterable:
nodes_keys[node_key][sys_id] |= action
self.tree.add_nodes(nodes_keys)
pattern = set()
for key in nodes_keys:
pattern.add(r'^{0}$|^{0}:'.format(key))
pattern = '|'.join(list(pattern))
if pattern:
assets_ids = Asset.objects.filter(
nodes__key__regex=pattern
).values_list("id", flat=True).distinct()
else:
assets_ids = []
self.tree.add_assets_without_system_users(assets_ids)
self._nodes_direct = nodes_keys
return nodes_keys
def get_nodes_without_cache(self):
self.get_assets_direct()
self.get_assets_without_cache()
return self.tree.get_nodes()
#@timeit
@timeit
def get_assets_direct(self):
"""
返回用户授权规则直接关联的资产
:return: {asset1: {system_user1: 1,}}
返回直接授权的资产
并添加到tree.assets中
:return:
{asset.id: {system_user.id: actions, }, }
"""
if self._assets_direct:
return self._assets_direct
assets = defaultdict(lambda: defaultdict(int))
assets_ids = defaultdict(lambda: defaultdict(int))
for perm in self.permissions:
actions = [perm.actions]
_assets = perm.assets.valid().only(*self.assets_only)
system_users = perm.system_users.all()
iterable = itertools.product(_assets, system_users, actions)
for asset, system_user, action in iterable:
assets[asset][system_user] |= action
self.tree.add_assets(assets)
self._assets_direct = assets
return assets
_assets_ids = [a.id for a in perm.assets.all()]
system_users_ids = [s.id for s in perm.system_users.all()]
iterable = itertools.product(_assets_ids, system_users_ids, actions)
for asset_id, sys_id, action in iterable:
assets_ids[asset_id][sys_id] |= action
self.tree.add_assets(assets_ids)
self._assets_direct = assets_ids
return assets_ids
#@timeit
@timeit
def get_assets_without_cache(self):
"""
:return: {asset1: set(system_user1,)}
:return:
[
{"id": asset.id, "system_users": {system_user.id: actions, }},
]
"""
if self._assets:
return self._assets
self.get_nodes_direct()
self.get_assets_direct()
nodes = self.get_nodes_direct()
pattern = set()
for node in nodes:
pattern.add(r'^{0}$|^{0}:'.format(node.key))
pattern = '|'.join(list(pattern))
if pattern:
assets = Asset.objects.filter(nodes__key__regex=pattern).valid() \
.prefetch_related('nodes')\
.only(*self.assets_only)\
.distinct()
else:
assets = []
assets = list(assets)
self.tree.add_assets_without_system_users(assets)
assets = self.tree.get_assets()
self._assets = assets
return assets
#@timeit
@timeit
def get_nodes_with_assets_without_cache(self):
"""
返回节点并且包含资产
{"node": {"asset": {"system_user": 1})}}
:return:
"""
self.get_assets_without_cache()
nodes_assets = self.tree.get_nodes_with_assets()
return nodes_assets
@ -545,67 +583,72 @@ def sort_assets(assets, order_by='hostname', reverse=False):
return assets
def parse_node_to_tree_node(node):
name = '{} ({})'.format(node.value, node.assets_amount)
data = {
'id': node.key,
'name': name,
'title': name,
'pId': node.parent_key,
'isParent': True,
'open': node.is_root(),
'meta': {
'node': {
"id": node.id,
"key": node.key,
"value": node.value,
},
'type': 'node'
}
}
tree_node = TreeNode(**data)
return tree_node
class ParserNode:
nodes_only_fields = ("key", "value", "id")
assets_only_fields = ("platform", "hostname", "id", "ip", "protocols")
system_users_only_fields = (
"id", "name", "username", "protocol", "priority", "login_mode",
)
def parse_asset_to_tree_node(node, asset, system_users):
icon_skin = 'file'
if asset.platform.lower() == 'windows':
icon_skin = 'windows'
elif asset.platform.lower() == 'linux':
icon_skin = 'linux'
_system_users = []
for system_user, action in system_users.items():
_system_users.append({
'id': system_user.id,
'name': system_user.name,
'username': system_user.username,
'protocol': system_user.protocol,
'priority': system_user.priority,
'login_mode': system_user.login_mode,
'actions': [Action.value_to_choices(action)],
})
data = {
'id': str(asset.id),
'name': asset.hostname,
'title': asset.ip,
'pId': node.key,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'system_users': _system_users,
'type': 'asset',
'asset': {
'id': asset.id,
'hostname': asset.hostname,
'ip': asset.ip,
'protocols': asset.protocols_as_list,
'platform': asset.platform,
'domain': None if not asset.domain else asset.domain.id,
'is_active': asset.is_active,
'comment': asset.comment
},
@staticmethod
def parse_node_to_tree_node(node):
name = '{} ({})'.format(node.value, node.assets_amount)
data = {
'id': node.key,
'name': name,
'title': name,
'pId': node.parent_key,
'isParent': True,
'open': node.is_root(),
'meta': {
'node': {
"id": node.id,
"key": node.key,
"value": node.value,
},
'type': 'node'
}
}
}
tree_node = TreeNode(**data)
return tree_node
tree_node = TreeNode(**data)
return tree_node
@staticmethod
def parse_asset_to_tree_node(node, asset, system_users):
icon_skin = 'file'
if asset.platform.lower() == 'windows':
icon_skin = 'windows'
elif asset.platform.lower() == 'linux':
icon_skin = 'linux'
_system_users = []
for system_user in system_users:
_system_users.append({
'id': system_user.id,
'name': system_user.name,
'username': system_user.username,
'protocol': system_user.protocol,
'priority': system_user.priority,
'login_mode': system_user.login_mode,
'actions': [Action.value_to_choices(system_user.actions)],
})
data = {
'id': str(asset.id),
'name': asset.hostname,
'title': asset.ip,
'pId': node.key,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'system_users': _system_users,
'type': 'asset',
'asset': {
'id': asset.id,
'hostname': asset.hostname,
'ip': asset.ip,
'protocols': asset.protocols_as_list,
'platform': asset.platform,
},
}
}
tree_node = TreeNode(**data)
return tree_node

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

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from common.struct import Stack
from common.utils import timeit
from assets.utils import NodeUtil
class PermStackUtilMixin:
def __init__(self, debug=False):
self.stack = None
self._nodes = {}
self._debug = debug
@staticmethod
def sorted_by(node_dict):
return [int(i) for i in node_dict['key'].split(':')]
@staticmethod
def is_children(item1, item2):
key1 = item1["key"]
key2 = item2["key"]
return key2.startswith(key1 + ':') and (
len(key2.split(':')) - len(key1.split(':'))
) == 1
def debug(self, msg):
self._debug and print(msg)
class PermSystemUserNodeUtil(PermStackUtilMixin):
"""
self._nodes: {node.key: {system_user.id: actions,}}
"""
@timeit
def get_nodes_family_and_system_users(self, nodes_with_system_users):
"""
返回所有nodes_with_system_users中的node的家族节点的信息
并子会继承祖先的系统用户和actions信息
:param nodes_with_system_users:
{node.key: {system_user.id: actions,}, }
:return:
{node.key: {system_user.id: actions,}, }
"""
node_util = NodeUtil()
_nodes_keys = nodes_with_system_users.keys()
family_keys = node_util.get_some_nodes_family_keys_by_keys(_nodes_keys)
nodes_items = []
for i in family_keys:
system_users = nodes_with_system_users.get(i, defaultdict(int))
item = {"key": i, "system_users": system_users}
nodes_items.append(item)
# 按照父子关系排序
nodes_items.sort(key=self.sorted_by)
nodes_items.append({"key": "", "system_users": defaultdict(int)})
self.stack = Stack()
for item in nodes_items:
self.debug("准备: {} 栈顶: {}".format(
item['key'], self.stack.top["key"] if self.stack.top else None)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while self.stack.top and not self.is_children(self.stack.top, item):
# 出栈
self.pop_from_stack_system_users()
# 入栈
self.push_to_stack_system_users(item)
# 出栈最后一个
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
return self._nodes
def push_to_stack_system_users(self, item):
"""
:param item:
{"key": node.key, "system_users": {system_user.id: actions,},}
"""
if not self.stack.is_empty():
item_system_users = item["system_users"]
for system_user, action in self.stack.top["system_users"].items():
# 更新栈顶的系统用户和action到将要入栈的item中
item_system_users[system_user] |= action
item["system_users"] = item_system_users
self.debug("入栈: {}".format(item['key']))
self.stack.push(item)
# 出栈
def pop_from_stack_system_users(self):
_node = self.stack.pop()
self._nodes[_node["key"]] = _node["system_users"]
self.debug("出栈: {} 栈顶: {}".format(_node['key'], self.stack.top['key'] if self.stack.top else None))
class PermAssetsAmountUtil(PermStackUtilMixin):
def push_to_stack_nodes_amount(self, item):
self.debug("入栈: {}".format(item['key']))
self.stack.push(item)
def pop_from_stack_nodes_amount(self):
_node = self.stack.pop()
self.debug("出栈: {} 栈顶: {}".format(
_node['key'], self.stack.top['key'] if self.stack.top else None)
)
_node["assets_amount"] = len(_node["all_assets"] | _node["assets"])
self._nodes[_node.pop("key")] = _node
if not self.stack.top:
return
self.stack.top["all_assets"]\
.update(_node["all_assets"] | _node["assets"])
def compute_nodes_assets_amount(self, nodes_with_assets):
self.stack = Stack()
nodes_items = []
for key, values in nodes_with_assets.items():
nodes_items.append({
"key": key, "assets": values["assets"],
"all_assets": values["all_assets"], "assets_amount": 0
})
nodes_items.sort(key=self.sorted_by)
nodes_items.append({"key": "", "assets": set(), "all_assets": set(), "assets_amount": 0})
self.stack = Stack()
for item in nodes_items:
self.debug("准备: {} 栈顶: {}".format(
item['key'], self.stack.top["key"] if self.stack.top else None)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while self.stack.top and not self.is_children(self.stack.top, item):
self.pop_from_stack_nodes_amount()
self.push_to_stack_nodes_amount(item)
# 出栈最后一个
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
return self._nodes

View File

@ -163,12 +163,12 @@ class AssetPermissionAssetView(PermissionsMixin,
return queryset
def get_context_data(self, **kwargs):
assets_granted = self.get_queryset()
granted_nodes = self.object.nodes.all()
nodes_remain = [n for n in Node.get_queryset() if n not in granted_nodes]
context = {
'app': _('Perms'),
'action': _('Asset permission asset list'),
'assets_remain': Asset.objects.exclude(id__in=[a.id for a in assets_granted]),
'nodes_remain': Node.objects.exclude(granted_by_permissions=self.object),
'nodes_remain': nodes_remain,
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -48,6 +48,7 @@ class RemoteAppPermissionCreateView(PermissionsMixin, CreateView):
context = {
'app': _('Perms'),
'action': _('Create RemoteApp permission'),
'type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -63,7 +64,8 @@ class RemoteAppPermissionUpdateView(PermissionsMixin, UpdateView):
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Update RemoteApp permission')
'action': _('Update RemoteApp permission'),
'type': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -192,6 +192,11 @@ class SecuritySettingForm(BaseForm):
required=False, label=_("Batch execute commands"),
help_text=_("Allow user batch execute commands")
)
SECURITY_SERVICE_ACCOUNT_REGISTRATION = forms.BooleanField(
required=False, label=_("Service account registration"),
help_text=_("Allow using bootstrap token register service account, "
"when terminal setup, can disable it")
)
# limit login count
SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField(
min_value=3, max_value=99999,

View File

@ -96,7 +96,7 @@ $(document).ready(function () {
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(data),
method: "POST",

View File

@ -100,7 +100,7 @@ $(document).ready(function () {
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(data),
method: "POST",
@ -127,7 +127,7 @@ $(document).ready(function () {
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify({'username_list':username_list}),
method: "POST",

View File

@ -256,7 +256,7 @@ function formSubmit(props) {
})
}
function APIUpdateAttr(props) {
function requestApi(props) {
// props = {url: .., body: , success: , error: , method: ,}
props = props || {};
var user_success_message = props.success_message;
@ -328,7 +328,7 @@ function objectDelete(obj, name, url, redirectTo) {
// swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
swal(gettext('Error'), "[ "+name+" ]" + gettext("Being used by the asset, please unbind the asset first."), "error");
};
APIUpdateAttr({
requestApi({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
@ -369,7 +369,7 @@ function orgDelete(obj, name, url, redirectTo){
swal(gettext("Error"), " [ "+ name + " ] " + gettext("Do not perform this operation under this organization. Try again after switching to another organization"), "error");
}
};
APIUpdateAttr({
requestApi({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
@ -1109,9 +1109,22 @@ function objectAttrsIsBool(obj, attrs) {
})
}
function cleanDate(d) {
for (var i=0; i<2; i++) {
if (isNaN(Date.parse(d))) {
d = d.split('+')[0].trimRight();
} else {
return d
}
}
return ''
}
function formatDateAsCN(d) {
d = cleanDate(d);
var date = new Date(d);
return date.toISOString().replace("T", " ").replace(/\..*/, "");
var date_s = date.toLocaleString(navigator.language, {hour12: false});
return date_s.split("/").join('-')
}
function getUrlParams(url) {
@ -1137,6 +1150,8 @@ function getTimeUnits(u) {
}
function timeOffset(a, b) {
a = cleanDate(a);
b = cleanDate(b);
var start = new Date(a);
var end = new Date(b);
var offset = (end - start)/1000;

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
from django.conf import settings
from rest_framework import serializers
from common.utils import get_request_ip
@ -27,6 +28,9 @@ class TerminalSerializer(serializers.ModelSerializer):
valid = super().is_valid(raise_exception=raise_exception)
if not valid:
return valid
if not settings.SECURITY_SERVICE_ACCOUNT_REGISTRATION:
error = {"error": "service account registration disabled"}
raise serializers.ValidationError(error)
data = {'name': self.validated_data.get('name')}
kwargs = {'data': data}
if self.instance and self.instance.user:

View File

@ -132,7 +132,7 @@
}, 300)
}
var the_url = "{% url 'api-terminal:tasks-list' %}";
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
requestApi({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
}
$(document).ready(function () {
$('.footable').footable();

View File

@ -90,7 +90,7 @@ function terminateSession(data) {
}
var success_message = '{% trans "Terminate task send, waiting ..." %}';
var the_url = "{% url 'api-terminal:kill-session' %}";
APIUpdateAttr({
requestApi({
url: the_url,
method: 'POST',
body: JSON.stringify(data),
@ -174,7 +174,7 @@ function finishedSession(data) {
var success = function() {
location.reload();
};
APIUpdateAttr({
requestApi({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),

View File

@ -14,7 +14,7 @@ from rest_framework.pagination import LimitOffsetPagination
from common.permissions import (
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser,
CanUpdateSuperUser,
CanUpdateDeleteSuperUser,
)
from common.mixins import IDInCacheFilterMixin
from common.utils import get_logger
@ -38,7 +38,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
search_fields = filter_fields
queryset = User.objects.exclude(role=User.ROLE_APP)
serializer_class = UserSerializer
permission_classes = (IsOrgAdmin, CanUpdateSuperUser)
permission_classes = (IsOrgAdmin, CanUpdateDeleteSuperUser)
pagination_class = LimitOffsetPagination
def send_created_signal(self, users):

View File

@ -27,97 +27,7 @@ signer = get_signer()
logger = get_logger(__file__)
class User(AbstractUser):
ROLE_ADMIN = 'Admin'
ROLE_USER = 'User'
ROLE_APP = 'App'
ROLE_AUDITOR = 'Auditor'
ROLE_CHOICES = (
(ROLE_ADMIN, _('Administrator')),
(ROLE_USER, _('User')),
(ROLE_APP, _('Application')),
(ROLE_AUDITOR, _("Auditor"))
)
OTP_LEVEL_CHOICES = (
(0, _('Disable')),
(1, _('Enable')),
(2, _("Force enable")),
)
SOURCE_LOCAL = 'local'
SOURCE_LDAP = 'ldap'
SOURCE_OPENID = 'openid'
SOURCE_RADIUS = 'radius'
SOURCE_CHOICES = (
(SOURCE_LOCAL, 'Local'),
(SOURCE_LDAP, 'LDAP/AD'),
(SOURCE_OPENID, 'OpenID'),
(SOURCE_RADIUS, 'Radius'),
)
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username')
)
name = models.CharField(max_length=128, verbose_name=_('Name'))
email = models.EmailField(
max_length=128, unique=True, verbose_name=_('Email')
)
groups = models.ManyToManyField(
'users.UserGroup', related_name='users',
blank=True, verbose_name=_('User group')
)
role = models.CharField(
choices=ROLE_CHOICES, default='User', max_length=10,
blank=True, verbose_name=_('Role')
)
avatar = models.ImageField(
upload_to="avatar", null=True, verbose_name=_('Avatar')
)
wechat = models.CharField(
max_length=128, blank=True, verbose_name=_('Wechat')
)
phone = models.CharField(
max_length=20, blank=True, null=True, verbose_name=_('Phone')
)
otp_level = models.SmallIntegerField(
default=0, choices=OTP_LEVEL_CHOICES, verbose_name=_('MFA')
)
otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True)
# Todo: Auto generate key, let user download
private_key = fields.EncryptTextField(
blank=True, null=True, verbose_name=_('Private key')
)
public_key = fields.EncryptTextField(
blank=True, null=True, verbose_name=_('Public key')
)
comment = models.TextField(
blank=True, null=True, verbose_name=_('Comment')
)
is_first_login = models.BooleanField(default=True)
date_expired = models.DateTimeField(
default=date_expired_default, blank=True, null=True,
db_index=True, verbose_name=_('Date expired')
)
created_by = models.CharField(
max_length=30, default='', verbose_name=_('Created by')
)
source = models.CharField(
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
verbose_name=_('Source')
)
date_password_last_updated = models.DateTimeField(
auto_now_add=True, blank=True, null=True,
verbose_name=_('Date password last updated')
)
user_cache_key_prefix = '_User_{}'
def __str__(self):
return '{0.name}({0.username})'.format(self)
class AuthMixin:
@property
def password_raw(self):
raise AttributeError('Password raw is not a readable attribute')
@ -134,9 +44,11 @@ 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)
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 +58,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 +67,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 +81,53 @@ class User(AbstractUser):
pass
return PubKey()
def reset_password(self, new_password):
self.set_password(new_password)
self.save()
@property
def date_password_expired(self):
interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME
date_expired = self.date_password_last_updated + timezone.timedelta(
days=int(interval))
return date_expired
@property
def password_expired_remain_days(self):
date_remain = self.date_password_expired - timezone.now()
return date_remain.days
@property
def password_has_expired(self):
if self.is_local and self.password_expired_remain_days < 0:
return True
return False
@property
def password_will_expired(self):
if self.is_local and self.password_expired_remain_days < 5:
return True
return False
class RoleMixin:
ROLE_ADMIN = 'Admin'
ROLE_USER = 'User'
ROLE_APP = 'App'
ROLE_AUDITOR = 'Auditor'
ROLE_CHOICES = (
(ROLE_ADMIN, _('Administrator')),
(ROLE_USER, _('User')),
(ROLE_APP, _('Application')),
(ROLE_AUDITOR, _("Auditor"))
)
role = ROLE_USER
@property
def role_display(self):
return self.get_role_display()
@property
def is_superuser(self):
if self.role == 'Admin':
@ -251,41 +183,21 @@ class User(AbstractUser):
def is_staff(self, value):
pass
@property
def is_local(self):
return self.source == self.SOURCE_LOCAL
@property
def date_password_expired(self):
interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME
date_expired = self.date_password_last_updated + timezone.timedelta(
days=int(interval))
return date_expired
@classmethod
def create_app_user(cls, name, comment):
app = cls.objects.create(
username=name, name=name, email='{}@local.domain'.format(name),
is_active=False, role='App', comment=comment,
is_first_login=False, created_by='System'
)
access_key = app.create_access_key()
return app, access_key
@property
def password_expired_remain_days(self):
date_remain = self.date_password_expired - timezone.now()
return date_remain.days
@property
def password_has_expired(self):
if self.is_local and self.password_expired_remain_days < 0:
return True
return False
@property
def password_will_expired(self):
if self.is_local and self.password_expired_remain_days < 5:
return True
return False
def save(self, *args, **kwargs):
if not self.name:
self.name = self.username
if self.username == 'admin':
self.role = 'Admin'
self.is_active = True
super().save(*args, **kwargs)
class TokenMixin:
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
email = ''
id = None
@property
def private_token(self):
@ -333,31 +245,12 @@ class User(AbstractUser):
def access_key(self):
return self.access_keys.first()
def is_member_of(self, user_group):
if user_group in self.groups.all():
return True
return False
def avatar_url(self):
admin_default = settings.STATIC_URL + "img/avatar/admin.png"
user_default = settings.STATIC_URL + "img/avatar/user.png"
if self.avatar:
return self.avatar.url
if self.is_superuser:
return admin_default
else:
return user_default
def generate_reset_token(self):
letter = string.ascii_letters + string.digits
token = ''.join([random.choice(letter) for _ in range(50)])
self.set_cache(token)
return token
def set_cache(self, token):
key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.set(key, {'id': self.id, 'email': self.email}, 3600)
@classmethod
def validate_reset_password_token(cls, token):
try:
@ -371,11 +264,25 @@ class User(AbstractUser):
user = None
return user
def set_cache(self, token):
key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.set(key, {'id': self.id, 'email': self.email}, 3600)
@classmethod
def expired_reset_password_token(cls, token):
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.delete(key)
class MFAMixin:
otp_level = 0
otp_secret_key = ''
OTP_LEVEL_CHOICES = (
(0, _('Disable')),
(1, _('Enable')),
(2, _("Force enable")),
)
@property
def otp_enabled(self):
return self.otp_force_enabled or self.otp_level > 0
@ -397,39 +304,130 @@ class User(AbstractUser):
self.otp_level = 0
self.otp_secret_key = None
def to_json(self):
return OrderedDict({
'id': self.id,
'username': self.username,
'name': self.name,
'email': self.email,
'is_active': self.is_active,
'is_superuser': self.is_superuser,
'role': self.get_role_display(),
'groups': [group.name for group in self.groups.all()],
'source': self.get_source_display(),
'wechat': self.wechat,
'phone': self.phone,
'otp_level': self.otp_level,
'comment': self.comment,
'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S') \
if self.date_expired is not None else None
})
@classmethod
def create_app_user(cls, name, comment):
app = cls.objects.create(
username=name, name=name, email='{}@local.domain'.format(name),
is_active=False, role='App', comment=comment,
is_first_login=False, created_by='System'
)
access_key = app.create_access_key()
return app, access_key
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
SOURCE_LOCAL = 'local'
SOURCE_LDAP = 'ldap'
SOURCE_OPENID = 'openid'
SOURCE_RADIUS = 'radius'
SOURCE_CHOICES = (
(SOURCE_LOCAL, 'Local'),
(SOURCE_LDAP, 'LDAP/AD'),
(SOURCE_OPENID, 'OpenID'),
(SOURCE_RADIUS, 'Radius'),
)
def reset_password(self, new_password):
self.set_password(new_password)
self.date_password_last_updated = timezone.now()
self.save()
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username')
)
name = models.CharField(max_length=128, verbose_name=_('Name'))
email = models.EmailField(
max_length=128, unique=True, verbose_name=_('Email')
)
groups = models.ManyToManyField(
'users.UserGroup', related_name='users',
blank=True, verbose_name=_('User group')
)
role = models.CharField(
choices=RoleMixin.ROLE_CHOICES, default='User', max_length=10,
blank=True, verbose_name=_('Role')
)
avatar = models.ImageField(
upload_to="avatar", null=True, verbose_name=_('Avatar')
)
wechat = models.CharField(
max_length=128, blank=True, verbose_name=_('Wechat')
)
phone = models.CharField(
max_length=20, blank=True, null=True, verbose_name=_('Phone')
)
otp_level = models.SmallIntegerField(
default=0, choices=MFAMixin.OTP_LEVEL_CHOICES, verbose_name=_('MFA')
)
otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True)
# Todo: Auto generate key, let user download
private_key = fields.EncryptTextField(
blank=True, null=True, verbose_name=_('Private key')
)
public_key = fields.EncryptTextField(
blank=True, null=True, verbose_name=_('Public key')
)
comment = models.TextField(
blank=True, null=True, verbose_name=_('Comment')
)
is_first_login = models.BooleanField(default=True)
date_expired = models.DateTimeField(
default=date_expired_default, blank=True, null=True,
db_index=True, verbose_name=_('Date expired')
)
created_by = models.CharField(
max_length=30, default='', verbose_name=_('Created by')
)
source = models.CharField(
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
verbose_name=_('Source')
)
date_password_last_updated = models.DateTimeField(
auto_now_add=True, blank=True, null=True,
verbose_name=_('Date password last updated')
)
user_cache_key_prefix = '_User_{}'
def __str__(self):
return '{0.name}({0.username})'.format(self)
def get_absolute_url(self):
return reverse('users:user-detail', args=(self.id,))
@property
def groups_display(self):
return ' '.join([group.name for group in self.groups.all()])
@property
def source_display(self):
return self.get_source_display()
@property
def is_expired(self):
if self.date_expired and self.date_expired < timezone.now():
return True
else:
return False
@property
def is_valid(self):
if self.is_active and not self.is_expired:
return True
return False
@property
def is_local(self):
return self.source == self.SOURCE_LOCAL
def save(self, *args, **kwargs):
if not self.name:
self.name = self.username
if self.username == 'admin':
self.role = 'Admin'
self.is_active = True
super().save(*args, **kwargs)
def is_member_of(self, user_group):
if user_group in self.groups.all():
return True
return False
def avatar_url(self):
admin_default = settings.STATIC_URL + "img/avatar/admin.png"
user_default = settings.STATIC_URL + "img/avatar/user.png"
if self.avatar:
return self.avatar.url
if self.is_superuser:
return admin_default
else:
return user_default
def delete(self, using=None, keep_parents=False):
if self.pk == 1 or self.username == 'admin':

View File

@ -36,7 +36,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
'date_password_last_updated', 'date_expired', 'avatar_url',
]
extra_kwargs = {
'password': {'write_only': True, 'required': False},
'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True},
'public_key': {'write_only': True},
'groups_display': {'label': _('Groups name')},
'source_display': {'label': _('Source name')},
@ -56,13 +56,17 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
raise serializers.ValidationError(msg)
return value
@staticmethod
def validate_password(value):
def validate_password(self, password):
from ..utils import check_password_rules
if not check_password_rules(value):
password_strategy = self.initial_data.get('password_strategy')
if password_strategy == '0':
return
if password_strategy is None and not password:
return
if not check_password_rules(password):
msg = _('Password does not match security rules')
raise serializers.ValidationError(msg)
return value
return password
@staticmethod
def change_password_to_raw(validated_data):

View File

@ -0,0 +1,165 @@
{% load i18n %}
<div class="col-lg-3" style="padding-left: 0px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight">
<div class="mail-box-header">
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm labels dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels-menu">
</ul>
</div>
<table class="table table-striped table-bordered table-hover" id="user_assets_table" >
<thead>
<tr>
<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 'System user' %}</th>
{% if show_actions %}
<th class="text-center">{% trans 'Action' %}</th>
{% endif %}
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<script>
var zTree;
var inited = false;
var url;
var assetTable;
var treeUrl = "NeedInput";
var assetTableUrl = 'NeedInput';
var selectUrl = 'NeedInput';
var showAssetHref = true; // Need input default true
var actions = {};
var labels = '';
var requesting = false;
function initTable() {
if (inited){
return assetTable
} else {
inited = true;
}
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var assetDetailUrl = '{% url 'assets:asset-detail' pk=DEFAULT_PK %}'
.replace("{{ DEFAULT_PK }}", rowData.id);
var detailBtn = '<a href="assetDetailUrl" class="asset-detail" data-asset="assetId">' + cellData + '</a>';
if (showAssetHref) {
cellData = detailBtn.replace("assetDetailUrl", assetDetailUrl);
} else {
detailBtn = detailBtn.replace("assetId", rowData.id);
cellData = detailBtn.replace("assetDetailUrl", "");
}
$(td).html(cellData);
}},
{targets: 3, createdCell: function (td, cellData) {
var users = [];
$.each(cellData, function (id, data) {
var name = htmlEscape(data.name);
users.push(name);
});
$(td).html(users.join(', '))
}},
],
ajax_url: assetTableUrl,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "system_users_granted", orderable: false},
{% if show_actions %}
{data: "id", orderable: false}
{% endif %}
]
};
{% if show_actions %}
options.columnDefs.push(actions);
{% endif %}
assetTable = jumpserver.initServerSideDataTable(options);
return assetTable
}
function onSelected(event, treeNode) {
var node_id = treeNode.meta.node.id;
url = selectUrl.replace("{{ DEFAULT_PK }}", node_id);
assetTable.ajax.url(url);
assetTable.ajax.reload();
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
$.get(treeUrl, function(data, status) {
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rootNodeAddDom(zTree, function () {
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
initTree();
});
});
}
function loadLabels() {
var labelListUrl = '{% url "api-assets:label-list" %}';
var label = '<li><a style="font-weight: bolder">labelName:labelValue</a></li>';
if (requesting) {
return
}
if (!labels) {
var data = {
url: labelListUrl,
method: "GET",
success: function (data) {
data.forEach(function (value) {
labels += label.replace("labelName", value.name).replace("labelValue", value.value)
});
$(".labels-menu").append(labels);
requesting = false;
},
error: function() {
requesting = false;
},
flash_message: false
};
requesting = true;
requestApi(data)
}
}
$(document).ready(function () {
loadLabels()
}).on('click', '.labels-menu li', function () {
var val = $(this).text();
$("#user_assets_table_filter input").val(val);
assetTable.search(val).draw();
})
</script>

View File

@ -4,9 +4,7 @@
{% block user_template_title %}{% trans "Create user" %}{% endblock %}
{% block password %}
{% bootstrap_field form.password_strategy layout="horizontal" %}
<div class="form-group" id="custom_password">
{% bootstrap_field form.password layout="horizontal" %}
</div>
{% bootstrap_field form.password layout="horizontal" %}
{# 密码popover #}
<div id="container">
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
@ -29,7 +27,7 @@ function passwordCheck() {
progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }},
minLength = 6,
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) -77 + 34,
left = 377,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
@ -67,9 +65,9 @@ var password_strategy_radio_input = 'input[type=radio][name=password_strategy]';
function passwordStrategyFieldsDisplay(){
var val = $('input:radio[name="password_strategy"]:checked').val();
if(val === '0'){
$('#custom_password').addClass('hidden')
$('#id_password').parents('.form-group').addClass('hidden')
}else {
$('#custom_password').removeClass('hidden')
$('#id_password').parents('.form-group').removeClass('hidden')
}
}
$(document).ready(function () {
@ -78,7 +76,25 @@ $(document).ready(function () {
}).on('change', password_strategy_radio_input, function(){
passwordStrategyFieldsDisplay()
})
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-users:user-list' %}';
var redirect_to = '{% url "users:user-list" %}';
var method = "POST";
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['groups']);
objectAttrsIsDatetime(data,['date_expired']);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}

View File

@ -280,7 +280,7 @@ function updateUserGroups(groups) {
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -305,7 +305,7 @@ $(document).ready(function() {
'is_active': checked
};
var success = '{% trans "Update successfully!" %}';
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
@ -332,7 +332,7 @@ $(document).ready(function() {
'otp_secret_key': otp_secret_key
};
var success = '{% trans "Update successfully!" %}';
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
@ -372,7 +372,7 @@ $(document).ready(function() {
var msg = "{% trans "An e-mail has been sent to the user`s mailbox." %}";
swal("{% trans 'Reset password' %}", msg, "success");
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -398,7 +398,7 @@ $(document).ready(function() {
var msg = "{% trans 'The reset-ssh-public-key E-mail has been sent successfully. Please inform the user to update his new ssh public key.' %}";
swal("{% trans 'Reset SSH public key' %}", msg, "success");
};
APIUpdateAttr({
requestApi({
url: the_url,
body: body,
success: success
@ -441,7 +441,7 @@ $(document).ready(function() {
}
);
};
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
requestApi({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
}).on('click', '.btn-delete-user', function () {
var $this = $(this);
var name = "{{ user_object.name }}";
@ -466,7 +466,7 @@ $(document).ready(function() {
}
);
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@ -485,7 +485,7 @@ $(document).ready(function() {
doReset();
});
}).on('click', '#btn-reset-mfa', function () {
APIUpdateAttr({
requestApi({
url: "{% url 'api-users:user-reset-otp' pk=user_object.id %}",
method: "GET",
success_message: "{% trans 'Reset user MFA success' %}"

View File

@ -23,35 +23,7 @@
</ul>
</div>
<div class="tab-content">
<div class="col-lg-3" style="padding-left: 0px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover" id="user_assets_table" >
<thead>
<tr>
<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>
<tbody>
</tbody>
</table>
</div>
</div>
{% include 'users/_granted_assets.html' %}
</div>
</div>
</div>
@ -60,88 +32,9 @@
{% endblock %}
{% block custom_foot_js %}
<script>
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";
function initTable() {
if (inited){
return asset_table
} else {
inited = true;
}
url = "{% url 'api-perms:user-assets' pk=object.id %}?cache_policy=1";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(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);
users.push(name);
});
$(td).html(users.join(', '))
}}
],
ajax_url: url,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false}
]
};
asset_table = jumpserver.initServerSideDataTable(options)
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1';
var node_id = treeNode.meta.node.id;
url = url.replace("{{ DEFAULT_PK }}", node_id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
$.get(treeUrl, function(data, status) {
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rootNodeAddDom(zTree, function () {
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
initTree();
});
});
}
var assetTableUrl = "{% url 'api-perms:user-assets' pk=object.id %}?cache_policy=1";
var selectUrl = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1';
var treeUrl = "{% url 'api-perms:user-nodes-as-tree' pk=object.id %}?&cache_policy=1";
$(document).ready(function () {
initTree();

View File

@ -47,5 +47,26 @@ $(document).ready(function () {
closeOnSelect: false
});
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-users:user-group-list' %}';
var redirect_to = '{% url "users:user-group-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url 'api-users:user-group-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['users']);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}

View File

@ -142,7 +142,7 @@ function updateGroupMember(users) {
// clear jumpserver.selected_groups
jumpserver.users_selected = {};
};
APIUpdateAttr({
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success

View File

@ -23,142 +23,21 @@
</ul>
</div>
<div class="tab-content">
<div class="col-lg-3" style="padding-left: 0px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover" id="user_assets_table" >
<thead>
<tr>
<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 'Reachable' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
{% include 'users/_granted_assets.html' %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var zTree;
var inited = false;
var url;
var asset_table;
var treeUrl = "{% url 'api-perms:user-group-nodes-as-tree' pk=object.id %}?cache_policy=1";
var assetTableUrl = "{% url 'api-perms:user-group-assets' pk=object.id %}?cache_policy=1";
var selectUrl = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1';
var showAssetHref = true; // Need input default true
function initTable() {
if (inited){
return asset_table
} else {
inited = true;
}
url = "{% url 'api-perms:user-group-assets' pk=object.id %}";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(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);
users.push(name);
});
$(td).html(users.join(', '))
}}
],
ajax_url: url,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false}
]
};
asset_table = jumpserver.initDataTable(options);
return asset_table
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}';
var node_id = treeNode.meta.node.id;
url = url.replace("{{ DEFAULT_PK }}", node_id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function selectQueryNode() {
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
var node_id;
if (query_node_id !== null) {
node_id = query_node_id
} else if (cookie_node_id !== null) {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
$.get("{% url 'api-perms:user-group-nodes-assets-as-tree' pk=object.id %}?show_assets=0", function(data, status) {
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
});
}
$(document).ready(function () {
initTree();

View File

@ -129,7 +129,7 @@ $(document).ready(function() {
swal("{% trans 'UserGroups Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
jumpserver.checked = false;
});
}

Some files were not shown because too many files have changed in this diff Show More