mirror of https://github.com/jumpserver/jumpserver
commit
c8623c3b8c
|
@ -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 %}
|
|
@ -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)
|
||||
|
|
|
@ -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,)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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 = ""
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -212,14 +212,12 @@ class AssetsAmountMixin:
|
|||
if cached is not None:
|
||||
return cached
|
||||
assets_amount = self.get_all_assets().count()
|
||||
self.assets_amount = assets_amount
|
||||
cache.set(cache_key, assets_amount, self.cache_time)
|
||||
return assets_amount
|
||||
|
||||
@assets_amount.setter
|
||||
def assets_amount(self, value):
|
||||
self._assets_amount = value
|
||||
cache_key = self._assets_amount_cache_key.format(self.key)
|
||||
cache.set(cache_key, value, self.cache_time)
|
||||
|
||||
def expire_assets_amount(self):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=True)
|
||||
|
|
|
@ -117,16 +117,6 @@ class SystemUser(AssetUser):
|
|||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'protocol': self.protocol,
|
||||
'priority': self.priority,
|
||||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
@property
|
||||
def login_mode_display(self):
|
||||
return self.get_login_mode_display()
|
||||
|
|
|
@ -21,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')},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -10,13 +10,19 @@ from orgs.mixins import BulkOrgResourceModelSerializer
|
|||
|
||||
|
||||
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
||||
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
|
||||
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
|
||||
|
||||
class Meta:
|
||||
model = CommandFilter
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = '__all__'
|
||||
fields = [
|
||||
'id', 'name', 'org_id', 'org_name', 'is_active', 'comment',
|
||||
'created_by', 'date_created', 'date_updated', 'rules', 'system_users'
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
'rules': {'read_only': True},
|
||||
'system_users': {'read_only': True}
|
||||
}
|
||||
|
||||
|
||||
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
||||
|
|
|
@ -6,6 +6,7 @@ from common.serializers import AdaptedBulkListSerializer
|
|||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from ..models import Domain, Gateway
|
||||
from .base import AuthSerializerMixin
|
||||
|
||||
|
||||
class DomainSerializer(BulkOrgResourceModelSerializer):
|
||||
|
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'value', 'assets_amount', 'org_id',
|
||||
]
|
||||
only_fields = ['id', 'key', 'value', 'org_id']
|
||||
fields = only_fields + ['assets_amount']
|
||||
read_only_fields = [
|
||||
'key', 'assets_amount', 'org_id',
|
||||
]
|
||||
|
|
|
@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
'id', 'name', 'username', 'password', 'public_key', 'private_key',
|
||||
'login_mode', 'login_mode_display', 'priority', 'protocol',
|
||||
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
|
||||
'assets_amount', 'connectivity_amount', 'auto_generate_key'
|
||||
'assets_amount', 'auto_generate_key'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {"write_only": True},
|
||||
'public_key': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
'assets_amount': {'label': _('Asset')},
|
||||
'connectivity_amount': {'label': _('Connectivity')},
|
||||
'login_mode_display': {'label': _('Login mode display')},
|
||||
'created_by': {'read_only': True},
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
|
|||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||
logger.debug("Asset nodes change signal received")
|
||||
Asset.expire_all_nodes_keys_cache()
|
||||
if isinstance(instance, Asset):
|
||||
if kwargs['action'] == 'pre_remove':
|
||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
|
|
|
@ -70,7 +70,7 @@ function showAuth() {
|
|||
var msg = "{% trans 'Get auth info error' %}";
|
||||
toastr.error(msg)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
requestApi({
|
||||
url: url,
|
||||
method: "GET",
|
||||
success: success,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -137,7 +137,6 @@ $(document).ready(function () {
|
|||
protocolRef.val(protocolShould);
|
||||
protocolRef.trigger("change")
|
||||
}
|
||||
|
||||
})
|
||||
.on("click", ".btn-protocol.btn-del", function () {
|
||||
$(this).parent().parent().remove();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
|
@ -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',
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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)}),
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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 %}
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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 %}
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -4,11 +4,13 @@ import os
|
|||
import uuid
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import generics, serializers
|
||||
|
||||
from .http import HttpResponseTemporaryRedirect
|
||||
from .const import KEY_CACHE_RESOURCES_ID
|
||||
|
||||
__all__ = [
|
||||
|
@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView):
|
|||
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
|
||||
cache.set(cache_key, resources_id, 300)
|
||||
return Response({'spm': spm})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def redirect_plural_name_api(request, *args, **kwargs):
|
||||
resource = kwargs.get("resource", "")
|
||||
full_path = request.get_full_path()
|
||||
full_path = full_path.replace(resource, resource+"s", 1)
|
||||
return HttpResponseTemporaryRedirect(full_path)
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.http import HttpResponse
|
||||
from django.utils.encoding import iri_to_uri
|
||||
|
||||
|
||||
class HttpResponseTemporaryRedirect(HttpResponse):
|
||||
status_code = 307
|
||||
|
||||
def __init__(self, redirect_to):
|
||||
HttpResponse.__init__(self)
|
||||
self['Location'] = iri_to_uri(redirect_to)
|
|
@ -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
|
||||
|
|
|
@ -46,12 +46,17 @@ class TreeNode:
|
|||
|
||||
def __gt__(self, other):
|
||||
if self.isParent and not other.isParent:
|
||||
return False
|
||||
result = False
|
||||
elif not self.isParent and other.isParent:
|
||||
return True
|
||||
if self.pId != other.pId:
|
||||
return self.pId > other.pId
|
||||
return self.name > other.name
|
||||
result = True
|
||||
elif self.pId != other.pId:
|
||||
result = self.pId > other.pId
|
||||
else:
|
||||
result = self.name > other.name
|
||||
return result
|
||||
|
||||
def __le__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == other.id
|
||||
|
@ -74,7 +79,7 @@ class Tree:
|
|||
raise ValueError("Parent must not be node parent")
|
||||
node.pId = parent.id
|
||||
parent.isParent = True
|
||||
self.nodes[node.id] = node
|
||||
self.nodes[node.key] = node
|
||||
|
||||
def get_nodes(self):
|
||||
return sorted(self.nodes.values())
|
||||
|
|
|
@ -7,6 +7,7 @@ import logging
|
|||
import datetime
|
||||
import uuid
|
||||
from functools import wraps
|
||||
import time
|
||||
import copy
|
||||
import ipaddress
|
||||
|
||||
|
@ -179,3 +180,18 @@ def random_string(length):
|
|||
charset = string.ascii_letters + string.digits
|
||||
s = [random.choice(charset) for i in range(length)]
|
||||
return ''.join(s)
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def timeit(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
logger.debug("Start call: {}".format(func.__name__))
|
||||
now = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
using = (time.time() - now) * 1000
|
||||
msg = "Call {} end, using: {:.1f}ms".format(func.__name__, using)
|
||||
logger.debug(msg)
|
||||
return result
|
||||
return wrapper
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -255,7 +255,7 @@ function execute() {
|
|||
}
|
||||
}
|
||||
|
||||
APIUpdateAttr({
|
||||
requestApi({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
|
|||
else:
|
||||
return Response({"error": serializer.errors})
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -13,13 +13,13 @@ from ..utils import (
|
|||
RemoteAppPermissionUtil, construct_remote_apps_tree_root,
|
||||
parse_remote_app_to_tree_node,
|
||||
)
|
||||
from ..hands import User, RemoteApp, RemoteAppSerializer
|
||||
from ..hands import User, RemoteApp, RemoteAppSerializer, UserGroup
|
||||
from ..mixins import RemoteAppFilterMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
|
||||
'UserGrantedRemoteAppsAsTreeApi',
|
||||
'UserGrantedRemoteAppsAsTreeApi', 'UserGroupGrantedRemoteAppsApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView):
|
|||
if remote_app not in remote_apps:
|
||||
return Response({'msg': False}, status=403)
|
||||
return Response({'msg': True}, status=200)
|
||||
|
||||
|
||||
# RemoteApp permission
|
||||
|
||||
class UserGroupGrantedRemoteAppsApi(ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser, )
|
||||
serializer_class = RemoteAppSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
user_group_id = self.kwargs.get('pk')
|
||||
if not user_group_id:
|
||||
return queryset
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
util = RemoteAppPermissionUtil(user_group)
|
||||
queryset = util.get_remote_apps()
|
||||
return queryset
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
|
||||
UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002"
|
||||
EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003"
|
||||
EMPTY_NODE_KEY = "1:-2"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
|
|
|
@ -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 %}
|
|
@ -160,7 +160,7 @@ $(document).ready(function () {
|
|||
var body = {
|
||||
'is_active': checked
|
||||
};
|
||||
APIUpdateAttr({
|
||||
requestApi({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,80 +1,65 @@
|
|||
# coding: utf-8
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
import json
|
||||
from hashlib import md5
|
||||
import time
|
||||
import itertools
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db.models import Q
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from orgs.utils import set_to_root_org
|
||||
from common.utils import get_logger
|
||||
from common.utils import get_logger, timeit
|
||||
from common.tree import TreeNode
|
||||
from assets.utils import NodeUtil
|
||||
from .. import const
|
||||
from ..models import AssetPermission, Action
|
||||
from ..hands import Node, Asset
|
||||
from assets.utils import NodeUtil
|
||||
from .stack import PermSystemUserNodeUtil, PermAssetsAmountUtil
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets',
|
||||
'parse_asset_to_tree_node', 'parse_node_to_tree_node',
|
||||
'ParserNode',
|
||||
]
|
||||
|
||||
|
||||
class TreeNodeCounter(NodeUtil):
|
||||
def __init__(self, nodes):
|
||||
self.__nodes = nodes
|
||||
super().__init__(with_assets_amount=True)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.__nodes
|
||||
|
||||
|
||||
def timeit(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
logger.debug("Start call: {}".format(func.__name__))
|
||||
now = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
using = time.time() - now
|
||||
logger.debug("Call {} end, using: {:.2}s".format(func.__name__, using))
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
class GenerateTree:
|
||||
def __init__(self):
|
||||
"""
|
||||
nodes = {
|
||||
"<node1>": {
|
||||
node.key: {
|
||||
"system_users": {
|
||||
"system_user": action,
|
||||
"system_user2": action,
|
||||
system_user.id: actions,
|
||||
},
|
||||
"assets": set([<asset_instance>]),
|
||||
}
|
||||
"assets": set([asset.id,]),
|
||||
},
|
||||
}
|
||||
assets = {
|
||||
"<asset_instance2>": {
|
||||
"system_user": action,
|
||||
"system_user2": action,
|
||||
asset.id: {
|
||||
system_user.id: actions,
|
||||
},
|
||||
}
|
||||
"""
|
||||
self._node_util = None
|
||||
self.nodes = defaultdict(lambda: {"system_users": defaultdict(int), "assets": set(), "assets_amount": 0})
|
||||
self.nodes = defaultdict(lambda: {
|
||||
"system_users": defaultdict(int), "assets": set(),
|
||||
"assets_amount": 0, "all_assets": set(),
|
||||
})
|
||||
self.assets = defaultdict(lambda: defaultdict(int))
|
||||
self._root_node = None
|
||||
self._ungroup_node = None
|
||||
self._nodes_with_assets = None
|
||||
self._all_assets_nodes_key = None
|
||||
self._asset_counter = 0
|
||||
self._system_user_counter = 0
|
||||
self._nodes_assets_counter = 0
|
||||
|
||||
@property
|
||||
def node_util(self):
|
||||
|
@ -82,115 +67,160 @@ class GenerateTree:
|
|||
self._node_util = NodeUtil()
|
||||
return self._node_util
|
||||
|
||||
@staticmethod
|
||||
def key_sort(key):
|
||||
key_list = [int(i) for i in key.split(':')]
|
||||
return len(key_list), key_list
|
||||
|
||||
@property
|
||||
def root_node(self):
|
||||
def root_key(self):
|
||||
if self._root_node:
|
||||
return self._root_node
|
||||
all_nodes = self.nodes.keys()
|
||||
all_keys = self.nodes.keys()
|
||||
# 如果没有授权节点,就放到默认的根节点下
|
||||
if not all_nodes:
|
||||
if not all_keys:
|
||||
return None
|
||||
root_node = min(all_nodes)
|
||||
self._root_node = root_node
|
||||
return root_node
|
||||
root_key = min(all_keys, key=self.key_sort)
|
||||
self._root_key = root_key
|
||||
return root_key
|
||||
|
||||
@property
|
||||
def ungrouped_node(self):
|
||||
def all_assets_nodes_keys(self):
|
||||
if not self._all_assets_nodes_key:
|
||||
self._all_assets_nodes_key = Asset.get_all_nodes_keys()
|
||||
return self._all_assets_nodes_key
|
||||
|
||||
@property
|
||||
def ungrouped_key(self):
|
||||
if self._ungroup_node:
|
||||
return self._ungroup_node
|
||||
node_id = const.UNGROUPED_NODE_ID
|
||||
if self.root_node:
|
||||
node_key = "{}:{}".format(self.root_node.key, self.root_node.child_mark)
|
||||
if self.root_key:
|
||||
node_key = "{}:{}".format(self.root_key, '-1')
|
||||
else:
|
||||
node_key = '0:0'
|
||||
node_value = _("Default")
|
||||
node = Node(id=node_id, key=node_key, value=node_value)
|
||||
self.add_node(node, {})
|
||||
self._ungroup_node = node
|
||||
return node
|
||||
node_key = '1:-1'
|
||||
self._ungroup_node = node_key
|
||||
return node_key
|
||||
|
||||
@property
|
||||
def empty_node(self):
|
||||
node_id = const.EMPTY_NODE_ID
|
||||
value = _('Empty')
|
||||
node = Node(id=node_id, value=value)
|
||||
return node
|
||||
@timeit
|
||||
def add_assets_without_system_users(self, assets_ids):
|
||||
for asset_id in assets_ids:
|
||||
self.add_asset(asset_id, {})
|
||||
|
||||
#@timeit
|
||||
def add_assets_without_system_users(self, assets):
|
||||
for asset in assets:
|
||||
self.add_asset(asset, {})
|
||||
@timeit
|
||||
def add_assets(self, assets_ids_with_system_users):
|
||||
for asset_id, system_users_ids in assets_ids_with_system_users.items():
|
||||
self.add_asset(asset_id, system_users_ids)
|
||||
|
||||
#@timeit
|
||||
def add_assets(self, assets):
|
||||
for asset, system_users in assets.items():
|
||||
self.add_asset(asset, system_users)
|
||||
# @timeit
|
||||
def add_asset(self, asset_id, system_users_ids=None):
|
||||
"""
|
||||
:param asset_id:
|
||||
:param system_users_ids: {system_user.id: actions, }
|
||||
:return:
|
||||
"""
|
||||
if not system_users_ids:
|
||||
system_users_ids = defaultdict(int)
|
||||
|
||||
# #@timeit
|
||||
def add_asset(self, asset, system_users=None):
|
||||
nodes = asset.nodes.all()
|
||||
nodes = self.node_util.get_nodes_by_queryset(nodes)
|
||||
if not system_users:
|
||||
system_users = defaultdict(int)
|
||||
else:
|
||||
system_users = {k: v for k, v in system_users.items()}
|
||||
_system_users = self.assets[asset]
|
||||
for system_user, action in _system_users.items():
|
||||
system_users[system_user] |= action
|
||||
# 获取已有资产的系统用户和actions,并更新到最新系统用户信息中
|
||||
old_system_users_ids = self.assets[asset_id]
|
||||
for system_user_id, action in old_system_users_ids.items():
|
||||
system_users_ids[system_user_id] |= action
|
||||
|
||||
# 获取父节点们
|
||||
parents = self.node_util.get_nodes_parents(nodes, with_self=True)
|
||||
for node in parents:
|
||||
_system_users = self.nodes[node]["system_users"]
|
||||
self.nodes[node]["assets_amount"] += 1
|
||||
for system_user, action in _system_users.items():
|
||||
system_users[system_user] |= action
|
||||
|
||||
# 过滤系统用户的协议
|
||||
system_users = {s: v for s, v in system_users.items() if asset.has_protocol(s.protocol)}
|
||||
self.assets[asset] = system_users
|
||||
|
||||
in_nodes = set(self.nodes.keys()) & set(nodes)
|
||||
asset_nodes_keys = self.all_assets_nodes_keys.get(asset_id, [])
|
||||
# {asset.id: [node.key, ], }
|
||||
# 获取用户在的节点
|
||||
in_nodes = set(self.nodes.keys()) & set(asset_nodes_keys)
|
||||
if not in_nodes:
|
||||
self.nodes[self.ungrouped_node]["assets_amount"] += 1
|
||||
self.nodes[self.ungrouped_node]["assets"].add(system_users)
|
||||
self.nodes[self.ungrouped_key]["assets"].add(asset_id)
|
||||
self.assets[asset_id] = system_users_ids
|
||||
return
|
||||
|
||||
for node in in_nodes:
|
||||
self.nodes[node]["assets"].add(asset)
|
||||
# 遍历用户应该在的节点
|
||||
for key in in_nodes:
|
||||
# 把自己加入到树上的节点中
|
||||
self.nodes[key]["assets"].add(asset_id)
|
||||
# 获取自己所在节点的系统用户,并添加进去
|
||||
node_system_users_ids = self.nodes[key]["system_users"]
|
||||
for system_user_id, action in node_system_users_ids.items():
|
||||
system_users_ids[system_user_id] |= action
|
||||
self.assets[asset_id] = system_users_ids
|
||||
|
||||
def add_node(self, node, system_users=None):
|
||||
if not system_users:
|
||||
system_users = defaultdict(int)
|
||||
self.nodes[node]["system_users"] = system_users
|
||||
def add_node(self, node_key, system_users_ids=None):
|
||||
"""
|
||||
:param node_key: node.key
|
||||
:param system_users_ids: {system_user.id: actions,}
|
||||
:return:
|
||||
"""
|
||||
if not system_users_ids:
|
||||
system_users_ids = defaultdict(int)
|
||||
self.nodes[node_key]["system_users"] = system_users_ids
|
||||
|
||||
# 添加树节点
|
||||
#@timeit
|
||||
def add_nodes(self, nodes):
|
||||
_nodes = nodes.keys()
|
||||
family = self.node_util.get_family(_nodes, with_children=True)
|
||||
for node in family:
|
||||
self.add_node(node, nodes.get(node, {}))
|
||||
@timeit
|
||||
def add_nodes(self, nodes_keys_with_system_users_ids):
|
||||
"""
|
||||
:param nodes_keys_with_system_users_ids:
|
||||
{node.key: {system_user.id: actions,}, }
|
||||
:return:
|
||||
"""
|
||||
util = PermSystemUserNodeUtil()
|
||||
family = util.get_nodes_family_and_system_users(nodes_keys_with_system_users_ids)
|
||||
for key, system_users in family.items():
|
||||
self.add_node(key, system_users)
|
||||
|
||||
def get_assets(self):
|
||||
return dict(self.assets)
|
||||
"""
|
||||
:return:
|
||||
[
|
||||
{"id": asset.id, "system_users": {system_user.id: actions, }},
|
||||
]
|
||||
"""
|
||||
assets = []
|
||||
for asset_id, system_users in self.assets.items():
|
||||
assets.append({"id": asset_id, "system_users": system_users})
|
||||
return assets
|
||||
|
||||
#@timeit
|
||||
@timeit
|
||||
def get_nodes_with_assets(self):
|
||||
"""
|
||||
:return:
|
||||
[
|
||||
{
|
||||
'key': node.key,
|
||||
'assets_amount': 10
|
||||
'assets': {
|
||||
asset.id: {
|
||||
system_user.id: actions,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
"""
|
||||
if self._nodes_with_assets:
|
||||
return self._nodes_with_assets
|
||||
nodes = {}
|
||||
for node, values in self.nodes.items():
|
||||
node._assets_amount = values["assets_amount"]
|
||||
nodes[node] = {asset: self.assets.get(asset, {}) for asset in values["assets"]}
|
||||
util = PermAssetsAmountUtil()
|
||||
nodes_with_assets_amount = util.compute_nodes_assets_amount(self.nodes)
|
||||
nodes = []
|
||||
for key, values in nodes_with_assets_amount.items():
|
||||
assets = {asset_id: self.assets.get(asset_id) for asset_id in values["assets"]}
|
||||
nodes.append({
|
||||
"key": key, "assets": assets,
|
||||
"assets_amount": values["assets_amount"]
|
||||
})
|
||||
# 如果返回空节点,页面构造授权资产树报错
|
||||
if not nodes:
|
||||
nodes[self.empty_node] = {}
|
||||
nodes.append({
|
||||
"key": const.EMPTY_NODE_KEY, "assets": {}, "assets_amount": 0
|
||||
})
|
||||
nodes.sort(key=lambda n: self.key_sort(n["key"]))
|
||||
self._nodes_with_assets = nodes
|
||||
return dict(nodes)
|
||||
return nodes
|
||||
|
||||
def get_nodes(self):
|
||||
return list(self.nodes.keys())
|
||||
nodes = list(self.nodes.keys())
|
||||
if not nodes:
|
||||
nodes.append(const.EMPTY_NODE_KEY)
|
||||
return list(nodes)
|
||||
|
||||
|
||||
def get_user_permissions(user, include_group=True):
|
||||
|
@ -228,8 +258,8 @@ def get_system_user_permissions(system_user):
|
|||
|
||||
|
||||
class AssetPermissionCacheMixin:
|
||||
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_'
|
||||
CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_'
|
||||
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_V2_'
|
||||
CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_V2_'
|
||||
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
||||
CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh'))
|
||||
cache_policy = '1'
|
||||
|
@ -283,6 +313,7 @@ class AssetPermissionCacheMixin:
|
|||
return self.get_cache_key('SYSTEM_USER')
|
||||
|
||||
def get_resource_from_cache(self, resource):
|
||||
logger.debug("Try get resource from cache")
|
||||
key_map = {
|
||||
"assets": self.asset_key,
|
||||
"nodes": self.node_key,
|
||||
|
@ -294,18 +325,22 @@ class AssetPermissionCacheMixin:
|
|||
raise ValueError("Not a valid resource: {}".format(resource))
|
||||
cached = cache.get(key)
|
||||
if not cached:
|
||||
logger.debug("Not found resource cache, update it")
|
||||
self.update_cache()
|
||||
cached = cache.get(key)
|
||||
return cached
|
||||
|
||||
def get_resource(self, resource):
|
||||
if self._is_using_cache():
|
||||
logger.debug("Using cache to get resource")
|
||||
return self.get_resource_from_cache(resource)
|
||||
elif self._is_refresh_cache():
|
||||
logger.debug("Need refresh cache")
|
||||
self.expire_cache()
|
||||
data = self.get_resource_from_cache(resource)
|
||||
return data
|
||||
else:
|
||||
logger.debug("Not using cache get source")
|
||||
return self.get_resource_without_cache(resource)
|
||||
|
||||
def get_resource_without_cache(self, resource):
|
||||
|
@ -430,88 +465,91 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
self._permissions = permissions
|
||||
return permissions
|
||||
|
||||
#@timeit
|
||||
@timeit
|
||||
def filter_permissions(self, **filters):
|
||||
filters_json = json.dumps(filters, sort_keys=True)
|
||||
self._permissions = self.permissions.filter(**filters)
|
||||
self._filter_id = md5(filters_json.encode()).hexdigest()
|
||||
|
||||
#@timeit
|
||||
@timeit
|
||||
def get_nodes_direct(self):
|
||||
"""
|
||||
返回用户/组授权规则直接关联的节点
|
||||
:return: {node1: {system_user1: {'actions': set()},}}
|
||||
返回直接授权的节点,
|
||||
并将节点添加到tree.nodes中,并将节点下的资产添加到tree.assets中
|
||||
:return:
|
||||
{node.key: {system_user.id: actions,}, }
|
||||
"""
|
||||
if self._nodes_direct:
|
||||
return self._nodes_direct
|
||||
nodes = defaultdict(lambda: defaultdict(int))
|
||||
nodes_keys = defaultdict(lambda: defaultdict(int))
|
||||
for perm in self.permissions:
|
||||
actions = [perm.actions]
|
||||
system_users = perm.system_users.all()
|
||||
_nodes = perm.nodes.all()
|
||||
for node, system_user, action in itertools.product(_nodes, system_users, actions):
|
||||
nodes[node][system_user] |= action
|
||||
self.tree.add_nodes(nodes)
|
||||
self._nodes_direct = nodes
|
||||
return nodes
|
||||
system_users_ids = [s.id for s in perm.system_users.all()]
|
||||
_nodes_keys = [n.key for n in perm.nodes.all()]
|
||||
iterable = itertools.product(_nodes_keys, system_users_ids, actions)
|
||||
for node_key, sys_id, action in iterable:
|
||||
nodes_keys[node_key][sys_id] |= action
|
||||
|
||||
self.tree.add_nodes(nodes_keys)
|
||||
|
||||
pattern = set()
|
||||
for key in nodes_keys:
|
||||
pattern.add(r'^{0}$|^{0}:'.format(key))
|
||||
pattern = '|'.join(list(pattern))
|
||||
if pattern:
|
||||
assets_ids = Asset.objects.filter(
|
||||
nodes__key__regex=pattern
|
||||
).values_list("id", flat=True).distinct()
|
||||
else:
|
||||
assets_ids = []
|
||||
self.tree.add_assets_without_system_users(assets_ids)
|
||||
self._nodes_direct = nodes_keys
|
||||
return nodes_keys
|
||||
|
||||
def get_nodes_without_cache(self):
|
||||
self.get_assets_direct()
|
||||
self.get_assets_without_cache()
|
||||
return self.tree.get_nodes()
|
||||
|
||||
#@timeit
|
||||
@timeit
|
||||
def get_assets_direct(self):
|
||||
"""
|
||||
返回用户授权规则直接关联的资产
|
||||
:return: {asset1: {system_user1: 1,}}
|
||||
返回直接授权的资产,
|
||||
并添加到tree.assets中
|
||||
:return:
|
||||
{asset.id: {system_user.id: actions, }, }
|
||||
"""
|
||||
if self._assets_direct:
|
||||
return self._assets_direct
|
||||
assets = defaultdict(lambda: defaultdict(int))
|
||||
assets_ids = defaultdict(lambda: defaultdict(int))
|
||||
for perm in self.permissions:
|
||||
actions = [perm.actions]
|
||||
_assets = perm.assets.valid().only(*self.assets_only)
|
||||
system_users = perm.system_users.all()
|
||||
iterable = itertools.product(_assets, system_users, actions)
|
||||
for asset, system_user, action in iterable:
|
||||
assets[asset][system_user] |= action
|
||||
self.tree.add_assets(assets)
|
||||
self._assets_direct = assets
|
||||
return assets
|
||||
_assets_ids = [a.id for a in perm.assets.all()]
|
||||
system_users_ids = [s.id for s in perm.system_users.all()]
|
||||
iterable = itertools.product(_assets_ids, system_users_ids, actions)
|
||||
for asset_id, sys_id, action in iterable:
|
||||
assets_ids[asset_id][sys_id] |= action
|
||||
self.tree.add_assets(assets_ids)
|
||||
self._assets_direct = assets_ids
|
||||
return assets_ids
|
||||
|
||||
#@timeit
|
||||
@timeit
|
||||
def get_assets_without_cache(self):
|
||||
"""
|
||||
:return: {asset1: set(system_user1,)}
|
||||
:return:
|
||||
[
|
||||
{"id": asset.id, "system_users": {system_user.id: actions, }},
|
||||
]
|
||||
"""
|
||||
if self._assets:
|
||||
return self._assets
|
||||
self.get_nodes_direct()
|
||||
self.get_assets_direct()
|
||||
nodes = self.get_nodes_direct()
|
||||
pattern = set()
|
||||
for node in nodes:
|
||||
pattern.add(r'^{0}$|^{0}:'.format(node.key))
|
||||
pattern = '|'.join(list(pattern))
|
||||
if pattern:
|
||||
assets = Asset.objects.filter(nodes__key__regex=pattern).valid() \
|
||||
.prefetch_related('nodes')\
|
||||
.only(*self.assets_only)\
|
||||
.distinct()
|
||||
else:
|
||||
assets = []
|
||||
assets = list(assets)
|
||||
self.tree.add_assets_without_system_users(assets)
|
||||
assets = self.tree.get_assets()
|
||||
self._assets = assets
|
||||
return assets
|
||||
|
||||
#@timeit
|
||||
@timeit
|
||||
def get_nodes_with_assets_without_cache(self):
|
||||
"""
|
||||
返回节点并且包含资产
|
||||
{"node": {"asset": {"system_user": 1})}}
|
||||
:return:
|
||||
"""
|
||||
self.get_assets_without_cache()
|
||||
nodes_assets = self.tree.get_nodes_with_assets()
|
||||
return nodes_assets
|
||||
|
@ -545,67 +583,72 @@ def sort_assets(assets, order_by='hostname', reverse=False):
|
|||
return assets
|
||||
|
||||
|
||||
def parse_node_to_tree_node(node):
|
||||
name = '{} ({})'.format(node.value, node.assets_amount)
|
||||
data = {
|
||||
'id': node.key,
|
||||
'name': name,
|
||||
'title': name,
|
||||
'pId': node.parent_key,
|
||||
'isParent': True,
|
||||
'open': node.is_root(),
|
||||
'meta': {
|
||||
'node': {
|
||||
"id": node.id,
|
||||
"key": node.key,
|
||||
"value": node.value,
|
||||
},
|
||||
'type': 'node'
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
class ParserNode:
|
||||
nodes_only_fields = ("key", "value", "id")
|
||||
assets_only_fields = ("platform", "hostname", "id", "ip", "protocols")
|
||||
system_users_only_fields = (
|
||||
"id", "name", "username", "protocol", "priority", "login_mode",
|
||||
)
|
||||
|
||||
|
||||
def parse_asset_to_tree_node(node, asset, system_users):
|
||||
icon_skin = 'file'
|
||||
if asset.platform.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
elif asset.platform.lower() == 'linux':
|
||||
icon_skin = 'linux'
|
||||
_system_users = []
|
||||
for system_user, action in system_users.items():
|
||||
_system_users.append({
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
'protocol': system_user.protocol,
|
||||
'priority': system_user.priority,
|
||||
'login_mode': system_user.login_mode,
|
||||
'actions': [Action.value_to_choices(action)],
|
||||
})
|
||||
data = {
|
||||
'id': str(asset.id),
|
||||
'name': asset.hostname,
|
||||
'title': asset.ip,
|
||||
'pId': node.key,
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'system_users': _system_users,
|
||||
'type': 'asset',
|
||||
'asset': {
|
||||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'protocols': asset.protocols_as_list,
|
||||
'platform': asset.platform,
|
||||
'domain': None if not asset.domain else asset.domain.id,
|
||||
'is_active': asset.is_active,
|
||||
'comment': asset.comment
|
||||
},
|
||||
@staticmethod
|
||||
def parse_node_to_tree_node(node):
|
||||
name = '{} ({})'.format(node.value, node.assets_amount)
|
||||
data = {
|
||||
'id': node.key,
|
||||
'name': name,
|
||||
'title': name,
|
||||
'pId': node.parent_key,
|
||||
'isParent': True,
|
||||
'open': node.is_root(),
|
||||
'meta': {
|
||||
'node': {
|
||||
"id": node.id,
|
||||
"key": node.key,
|
||||
"value": node.value,
|
||||
},
|
||||
'type': 'node'
|
||||
}
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
@staticmethod
|
||||
def parse_asset_to_tree_node(node, asset, system_users):
|
||||
icon_skin = 'file'
|
||||
if asset.platform.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
elif asset.platform.lower() == 'linux':
|
||||
icon_skin = 'linux'
|
||||
_system_users = []
|
||||
for system_user in system_users:
|
||||
_system_users.append({
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
'protocol': system_user.protocol,
|
||||
'priority': system_user.priority,
|
||||
'login_mode': system_user.login_mode,
|
||||
'actions': [Action.value_to_choices(system_user.actions)],
|
||||
})
|
||||
data = {
|
||||
'id': str(asset.id),
|
||||
'name': asset.hostname,
|
||||
'title': asset.ip,
|
||||
'pId': node.key,
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'system_users': _system_users,
|
||||
'type': 'asset',
|
||||
'asset': {
|
||||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'protocols': asset.protocols_as_list,
|
||||
'platform': asset.platform,
|
||||
},
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from collections import defaultdict
|
||||
from common.struct import Stack
|
||||
from common.utils import timeit
|
||||
from assets.utils import NodeUtil
|
||||
|
||||
|
||||
class PermStackUtilMixin:
|
||||
def __init__(self, debug=False):
|
||||
self.stack = None
|
||||
self._nodes = {}
|
||||
self._debug = debug
|
||||
|
||||
@staticmethod
|
||||
def sorted_by(node_dict):
|
||||
return [int(i) for i in node_dict['key'].split(':')]
|
||||
|
||||
@staticmethod
|
||||
def is_children(item1, item2):
|
||||
key1 = item1["key"]
|
||||
key2 = item2["key"]
|
||||
return key2.startswith(key1 + ':') and (
|
||||
len(key2.split(':')) - len(key1.split(':'))
|
||||
) == 1
|
||||
|
||||
def debug(self, msg):
|
||||
self._debug and print(msg)
|
||||
|
||||
|
||||
class PermSystemUserNodeUtil(PermStackUtilMixin):
|
||||
"""
|
||||
self._nodes: {node.key: {system_user.id: actions,}}
|
||||
"""
|
||||
@timeit
|
||||
def get_nodes_family_and_system_users(self, nodes_with_system_users):
|
||||
"""
|
||||
返回所有nodes_with_system_users中的node的家族节点的信息,
|
||||
并子会继承祖先的系统用户和actions信息
|
||||
:param nodes_with_system_users:
|
||||
{node.key: {system_user.id: actions,}, }
|
||||
:return:
|
||||
{node.key: {system_user.id: actions,}, }
|
||||
"""
|
||||
node_util = NodeUtil()
|
||||
_nodes_keys = nodes_with_system_users.keys()
|
||||
family_keys = node_util.get_some_nodes_family_keys_by_keys(_nodes_keys)
|
||||
|
||||
nodes_items = []
|
||||
for i in family_keys:
|
||||
system_users = nodes_with_system_users.get(i, defaultdict(int))
|
||||
item = {"key": i, "system_users": system_users}
|
||||
nodes_items.append(item)
|
||||
# 按照父子关系排序
|
||||
nodes_items.sort(key=self.sorted_by)
|
||||
nodes_items.append({"key": "", "system_users": defaultdict(int)})
|
||||
|
||||
self.stack = Stack()
|
||||
for item in nodes_items:
|
||||
self.debug("准备: {} 栈顶: {}".format(
|
||||
item['key'], self.stack.top["key"] if self.stack.top else None)
|
||||
)
|
||||
# 入栈之前检查,该节点是不是栈顶节点的子节点
|
||||
# 如果不是,则栈顶出栈
|
||||
while self.stack.top and not self.is_children(self.stack.top, item):
|
||||
# 出栈
|
||||
self.pop_from_stack_system_users()
|
||||
# 入栈
|
||||
self.push_to_stack_system_users(item)
|
||||
# 出栈最后一个
|
||||
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
|
||||
return self._nodes
|
||||
|
||||
def push_to_stack_system_users(self, item):
|
||||
"""
|
||||
:param item:
|
||||
{"key": node.key, "system_users": {system_user.id: actions,},}
|
||||
"""
|
||||
if not self.stack.is_empty():
|
||||
item_system_users = item["system_users"]
|
||||
for system_user, action in self.stack.top["system_users"].items():
|
||||
# 更新栈顶的系统用户和action到将要入栈的item中
|
||||
item_system_users[system_user] |= action
|
||||
item["system_users"] = item_system_users
|
||||
self.debug("入栈: {}".format(item['key']))
|
||||
self.stack.push(item)
|
||||
|
||||
# 出栈
|
||||
def pop_from_stack_system_users(self):
|
||||
_node = self.stack.pop()
|
||||
self._nodes[_node["key"]] = _node["system_users"]
|
||||
self.debug("出栈: {} 栈顶: {}".format(_node['key'], self.stack.top['key'] if self.stack.top else None))
|
||||
|
||||
|
||||
class PermAssetsAmountUtil(PermStackUtilMixin):
|
||||
def push_to_stack_nodes_amount(self, item):
|
||||
self.debug("入栈: {}".format(item['key']))
|
||||
self.stack.push(item)
|
||||
|
||||
def pop_from_stack_nodes_amount(self):
|
||||
_node = self.stack.pop()
|
||||
self.debug("出栈: {} 栈顶: {}".format(
|
||||
_node['key'], self.stack.top['key'] if self.stack.top else None)
|
||||
)
|
||||
_node["assets_amount"] = len(_node["all_assets"] | _node["assets"])
|
||||
self._nodes[_node.pop("key")] = _node
|
||||
|
||||
if not self.stack.top:
|
||||
return
|
||||
self.stack.top["all_assets"]\
|
||||
.update(_node["all_assets"] | _node["assets"])
|
||||
|
||||
def compute_nodes_assets_amount(self, nodes_with_assets):
|
||||
self.stack = Stack()
|
||||
nodes_items = []
|
||||
for key, values in nodes_with_assets.items():
|
||||
nodes_items.append({
|
||||
"key": key, "assets": values["assets"],
|
||||
"all_assets": values["all_assets"], "assets_amount": 0
|
||||
})
|
||||
|
||||
nodes_items.sort(key=self.sorted_by)
|
||||
nodes_items.append({"key": "", "assets": set(), "all_assets": set(), "assets_amount": 0})
|
||||
self.stack = Stack()
|
||||
for item in nodes_items:
|
||||
self.debug("准备: {} 栈顶: {}".format(
|
||||
item['key'], self.stack.top["key"] if self.stack.top else None)
|
||||
)
|
||||
# 入栈之前检查,该节点是不是栈顶节点的子节点
|
||||
# 如果不是,则栈顶出栈
|
||||
while self.stack.top and not self.is_children(self.stack.top, item):
|
||||
self.pop_from_stack_nodes_amount()
|
||||
self.push_to_stack_nodes_amount(item)
|
||||
# 出栈最后一个
|
||||
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
|
||||
return self._nodes
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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>
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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' %}"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue