Merge pull request #1185 from jumpserver/dev

授权规则优化,支持细颗粒授权
pull/1204/head
老广 2018-04-11 15:25:02 +08:00 committed by GitHub
commit 24bdaecab4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1644 additions and 819 deletions

View File

@ -2,4 +2,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
__version__ = "1.0.0" __version__ = "1.3.0"

View File

@ -50,7 +50,9 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
if node_id: if node_id:
node = get_object_or_404(Node, id=node_id) node = get_object_or_404(Node, id=node_id)
if not node.is_root(): if not node.is_root():
queryset = queryset.filter(nodes__key__startswith=node.key).distinct() queryset = queryset.filter(
nodes__key__regex='{}(:[0-9]+)*$'.format(node.key),
).distinct()
return queryset return queryset

View File

@ -30,6 +30,7 @@ from .. import serializers
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeViewSet', 'NodeChildrenApi',
'NodeAssetsApi', 'NodeWithAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi' 'TestNodeConnectiveApi'
@ -47,6 +48,34 @@ class NodeViewSet(BulkModelViewSet):
serializer.save() serializer.save()
class NodeWithAssetsApi(generics.ListAPIView):
permission_classes = (IsSuperUser,)
serializers = serializers.NodeSerializer
def get_node(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('node')
if not pk:
node = Node.root()
else:
node = get_object_or_404(Node, pk)
return node
def get_queryset(self):
queryset = []
node = self.get_node()
children = node.get_children()
assets = node.get_assets()
queryset.extend(list(children))
for asset in assets:
node = Node()
node.id = asset.id
node.parent = node.id
node.value = asset.hostname
queryset.append(node)
return queryset
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
@ -69,14 +98,54 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
status=201, status=201,
) )
def get(self, request, *args, **kwargs): def get_object(self):
instance = self.get_object() pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if self.request.query_params.get("all"): if not pk:
children = instance.get_all_children() node = Node.root()
else: else:
children = instance.get_children() node = get_object_or_404(Node, pk=pk)
response = [{"id": node.id, "key": node.key, "value": node.value} for node in children] return node
return Response(response, status=200)
def get_queryset(self):
queryset = []
query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets')
node = self.get_object()
if node == Node.root():
queryset.append(node)
if query_all:
children = node.get_all_children()
else:
children = node.get_children()
queryset.extend(list(children))
if query_assets:
assets = node.get_assets()
for asset in assets:
node_fake = Node()
node_fake.id = asset.id
node_fake.parent = node
node_fake.value = asset.hostname
node_fake.is_asset = True
queryset.append(node_fake)
return queryset
def get(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetSerializer
def get_queryset(self):
node_id = self.kwargs.get('pk')
query_all = self.request.query_params.get('all')
instance = get_object_or_404(Node, pk=node_id)
if query_all:
return instance.get_all_assets()
else:
return instance.get_assets()
class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddChildrenApi(generics.UpdateAPIView):
@ -146,4 +215,3 @@ class TestNodeConnectiveApi(APIView):
task_name = _("测试节点下资产是否可连接: {}".format(node.name)) task_name = _("测试节点下资产是否可连接: {}".format(node.name))
task = test_asset_connectability_util.delay(assets, task_name=task_name) task = test_asset_connectability_util.delay(assets, task_name=task_name)
return Response({"task": task.id}) return Response({"task": task.id})

View File

@ -27,13 +27,16 @@ class AssetCreateForm(forms.ModelForm):
'class': 'select2', 'data-placeholder': _('Admin user') 'class': 'select2', 'data-placeholder': _('Admin user')
}), }),
'labels': forms.SelectMultiple(attrs={ 'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Labels') 'class': 'select2', 'data-placeholder': _('Label')
}), }),
'port': forms.TextInput(), 'port': forms.TextInput(),
'domain': forms.Select(attrs={ 'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain') 'class': 'select2', 'data-placeholder': _('Domain')
}), }),
} }
labels = {
'nodes': _("Node"),
}
help_texts = { help_texts = {
'hostname': '* required', 'hostname': '* required',
'ip': '* required', 'ip': '* required',
@ -57,19 +60,22 @@ class AssetUpdateForm(forms.ModelForm):
] ]
widgets = { widgets = {
'nodes': forms.SelectMultiple(attrs={ 'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Nodes') 'class': 'select2', 'data-placeholder': _('Node')
}), }),
'admin_user': forms.Select(attrs={ 'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user') 'class': 'select2', 'data-placeholder': _('Admin user')
}), }),
'labels': forms.SelectMultiple(attrs={ 'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Labels') 'class': 'select2', 'data-placeholder': _('Label')
}), }),
'port': forms.TextInput(), 'port': forms.TextInput(),
'domain': forms.Select(attrs={ 'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain') 'class': 'select2', 'data-placeholder': _('Domain')
}), }),
} }
labels = {
'nodes': _("Node"),
}
help_texts = { help_texts = {
'hostname': '* required', 'hostname': '* required',
'ip': '* required', 'ip': '* required',
@ -116,10 +122,10 @@ class AssetBulkUpdateForm(forms.ModelForm):
] ]
widgets = { widgets = {
'labels': forms.SelectMultiple( 'labels': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select labels')} attrs={'class': 'select2', 'data-placeholder': _('Label')}
), ),
'nodes': forms.SelectMultiple( 'nodes': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select nodes')} attrs={'class': 'select2', 'data-placeholder': _('Node')}
), ),
} }

View File

@ -84,7 +84,7 @@ class Asset(models.Model):
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
def __str__(self): def __str__(self):
return self.hostname return '{0.hostname}({0.ip})'.format(self)
@property @property
def is_valid(self): def is_valid(self):
@ -101,6 +101,10 @@ class Asset(models.Model):
else: else:
return False return False
def get_nodes(self):
from .node import Node
return self.nodes.all() or [Node.root()]
@property @property
def hardware_info(self): def hardware_info(self):
if self.cpu_count: if self.cpu_count:

View File

@ -16,6 +16,8 @@ class Node(models.Model):
child_mark = models.IntegerField(default=0) child_mark = models.IntegerField(default=0)
date_create = models.DateTimeField(auto_now_add=True) date_create = models.DateTimeField(auto_now_add=True)
is_asset = False
def __str__(self): def __str__(self):
return self.value return self.value
@ -73,6 +75,9 @@ class Node(models.Model):
assets = Asset.objects.filter(nodes__in=nodes) assets = Asset.objects.filter(nodes__in=nodes)
return assets return assets
def has_assets(self):
return self.get_all_assets()
def get_all_active_assets(self): def get_all_active_assets(self):
return self.get_all_assets().filter(is_active=True) return self.get_all_assets().filter(is_active=True)

View File

@ -3,6 +3,7 @@
# #
import logging import logging
import uuid
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
@ -100,14 +101,15 @@ class SystemUser(AssetUser):
) )
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
priority = models.IntegerField(default=10, verbose_name=_("Priority")) priority = models.IntegerField(default=10, verbose_name=_("Priority"))
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
def __str__(self): def __str__(self):
return self.name return '{0.name}({0.username})'.format(self)
def to_json(self): def to_json(self):
return { return {
@ -119,11 +121,8 @@ class SystemUser(AssetUser):
'auto_push': self.auto_push, 'auto_push': self.auto_push,
} }
@property def get_assets(self):
def assets(self): assets = set(self.assets.all())
assets = set()
for node in self.nodes.all():
assets.update(set(node.get_all_assets()))
return assets return assets
@property @property
@ -168,6 +167,3 @@ class SystemUser(AssetUser):
except IntegrityError: except IntegrityError:
print('Error continue') print('Error continue')
continue continue

View File

@ -42,7 +42,7 @@ class NodeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Node model = Node
fields = ['id', 'key', 'value', 'parent', 'assets_amount'] fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_asset']
list_serializer_class = BulkListSerializer list_serializer_class = BulkListSerializer
@staticmethod @staticmethod

View File

@ -34,7 +34,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_assets_amount(obj): def get_assets_amount(obj):
return len(obj.assets) return len(obj.get_assets())
class SystemUserAuthSerializer(AuthSerializer): class SystemUserAuthSerializer(AuthSerializer):

View File

@ -1,14 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from collections import defaultdict
from django.db.models.signals import post_save, m2m_changed from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver from django.dispatch import receiver
from common.utils import get_logger from common.utils import get_logger
from .models import Asset, SystemUser, Node from .models import Asset, SystemUser, Node
from .tasks import update_assets_hardware_info_util, \ from .tasks import update_assets_hardware_info_util, \
test_asset_connectability_util, push_system_user_to_node, \ test_asset_connectability_util, push_system_user_to_assets
push_node_system_users_to_asset
logger = get_logger(__file__) logger = get_logger(__file__)
@ -31,7 +30,6 @@ def set_asset_root_node(asset):
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs): def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
set_asset_root_node(instance)
if created: if created:
logger.info("Asset `{}` create signal received".format(instance)) logger.info("Asset `{}` create signal received".format(instance))
update_asset_hardware_info_on_created(instance) update_asset_hardware_info_on_created(instance)
@ -41,25 +39,39 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier") @receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
def on_system_user_update(sender, instance=None, created=True, **kwargs): def on_system_user_update(sender, instance=None, created=True, **kwargs):
if instance and not created: if instance and not created:
for node in instance.nodes.all(): logger.info("System user `{}` update signal received".format(instance))
push_system_user_to_node(instance, node) assets = instance.assets.all()
push_system_user_to_assets.delay(instance, assets)
@receiver(m2m_changed, sender=SystemUser.nodes.through) @receiver(m2m_changed, sender=SystemUser.nodes.through)
def on_system_user_node_change(sender, instance=None, **kwargs): def on_system_user_nodes_change(sender, instance=None, **kwargs):
if instance and kwargs["action"] == "post_add": if instance and kwargs["action"] == "post_add":
for pk in kwargs['pk_set']: assets = set()
node = kwargs['model'].objects.get(pk=pk) nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
push_system_user_to_node(instance, node) for node in nodes:
assets.update(set(node.get_all_assets()))
instance.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=SystemUser.assets.through)
def on_system_user_assets_change(sender, instance=None, **kwargs):
if instance and kwargs["action"] == "post_add":
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
push_system_user_to_assets(instance, assets)
@receiver(m2m_changed, sender=Asset.nodes.through) @receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs): def on_asset_node_changed(sender, instance=None, **kwargs):
if isinstance(instance, Asset) and kwargs['action'] == 'post_add': if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
logger.debug("Asset node change signal received") logger.debug("Asset node change signal received")
for pk in kwargs['pk_set']: nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
node = kwargs['model'].objects.get(pk=pk) system_users_assets = defaultdict(set)
push_node_system_users_to_asset(node, [instance]) system_users = SystemUser.objects.filter(nodes__in=nodes)
for system_user in system_users:
system_users_assets[system_user].update({instance})
for system_user, assets in system_users_assets.items():
system_user.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=Asset.nodes.through) @receiver(m2m_changed, sender=Asset.nodes.through)
@ -67,5 +79,6 @@ def on_node_assets_changed(sender, instance=None, **kwargs):
if isinstance(instance, Node) and kwargs['action'] == 'post_add': if isinstance(instance, Node) and kwargs['action'] == 'post_add':
logger.debug("Node assets change signal received") logger.debug("Node assets change signal received")
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
push_node_system_users_to_asset(instance, assets) system_users = SystemUser.objects.filter(nodes=instance)
for system_user in system_users:
system_user.assets.add(*tuple(assets))

View File

@ -276,7 +276,7 @@ def test_system_user_connectability_util(system_user, task_name):
:return: :return:
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
assets = system_user.assets assets = system_user.get_assets()
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()] hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
tasks = const.TEST_SYSTEM_USER_CONN_TASKS tasks = const.TEST_SYSTEM_USER_CONN_TASKS
if not hosts: if not hosts:
@ -386,52 +386,17 @@ def push_system_user_util(system_users, assets, task_name):
return task.run() return task.run()
def get_node_push_system_user_task_name(system_user, node):
# return _("Push system user to node: {} => {}").format(
return _("推送系统用户到节点资产: {} => {}").format(
system_user.name,
node.value
)
@shared_task
def push_system_user_to_node(system_user, node):
logger.info("Start push system user node: {} => {}".format(system_user.name, node.value))
assets = node.get_all_assets()
task_name = get_node_push_system_user_task_name(system_user, node)
push_system_user_util([system_user], assets, task_name)
@shared_task
def push_system_user_related_nodes(system_user):
if not system_user.is_need_push():
msg = "push system user `{}` passed, may be not auto push or ssh " \
"protocol is not ssh".format(system_user.name)
logger.info(msg)
return
nodes = system_user.nodes.all()
for node in nodes:
push_system_user_to_node(system_user, node)
@shared_task @shared_task
def push_system_user_to_assets_manual(system_user): def push_system_user_to_assets_manual(system_user):
push_system_user_related_nodes(system_user) assets = system_user.get_assets()
task_name = "推送系统用户到入资产: {}".format(system_user.name)
return push_system_user_util([system_user], assets, task_name=task_name)
def push_node_system_users_to_asset(node, assets): @shared_task
system_users = [] def push_system_user_to_assets(system_user, assets):
nodes = node.ancestor_with_node task_name = _("推送系统用户到入资产: {}").format(system_user.name)
# 获取该节点所有父节点有的系统用户, 然后推送 return push_system_user_util.delay([system_user], assets, task_name)
for n in nodes:
system_users.extend(list(n.systemuser_set.all()))
if system_users:
# task_name = _("Push system users to node: {}").format(node.value)
task_name = _("推送节点系统用户到新加入资产中: {}").format(node.value)
push_system_user_util.delay(system_users, assets, task_name)
# @shared_task # @shared_task

View File

@ -34,7 +34,7 @@
<div class="form-group {% if form.errors.labels %} has-error {% endif %}"> <div class="form-group {% if form.errors.labels %} has-error {% endif %}">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label> <label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9"> <div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Select labels' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}"> <select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %} {% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}"> <optgroup label="{{ name }}">
{% for label in labels %} {% for label in labels %}

View File

@ -41,9 +41,9 @@
{% block content %} {% block content %}
<div class="wrapper wrapper-content"> <div class="wrapper wrapper-content">
<div class="row"> <div class="row">
<div class="col-lg-3" id="split-left"> <div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager "> <div class="file-manager ">
<div id="assetTree" class="ztree"> <div id="assetTree" class="ztree">
</div> </div>

View File

@ -39,7 +39,7 @@
<div class="form-group"> <div class="form-group">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label> <label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9"> <div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}"> <select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %} {% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}"> <optgroup label="{{ name }}">
{% for label in labels %} {% for label in labels %}

View File

@ -1,81 +1,171 @@
{% extends '_base_list.html' %} {% extends 'base.html' %}
{% load i18n %}
{% load static %} {% load static %}
{% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
{% endblock %}
{% block content_left_head %}{% endblock %}
{% block table_search %}
{% endblock %} {% endblock %}
{% block table_container %} {% block content %}
<table class="table table-striped table-bordered table-hover " id="asset_list_table" > <div class="wrapper wrapper-content">
<thead> <div class="row">
<tr> <div class="col-lg-3" id="split-left" style="padding-left: 3px">
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th> <div class="ibox float-e-margins">
<th class="text-center">{% trans 'Hostname' %}</th> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<th class="text-center">{% trans 'IP' %}</th> <div class="file-manager ">
<th class="text-center">{% trans 'Port' %}</th> <div id="assetTree" class="ztree">
<th class="text-center">{% trans 'Hardware' %}</th> </div>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Connective' %}</th> <div class="clearfix"></div>
</tr> </div>
</thead> </div>
<tbody> </div>
</tbody> </div>
</table> <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>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script> <script>
<script type="text/javascript"> var zTree, rMenu, asset_table;
var inited = false;
var url;
function initTable() { function initTable() {
if (inited){
return
} else {
inited = true;
}
var options = { var options = {
ele: $('#asset_list_table'), ele: $('#user_assets_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>'; var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
{targets: 5, createdCell: function (td, cellData) { {targets: 3, createdCell: function (td, cellData) {
if (!cellData) { if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>') $(td).html('<i class="fa fa-times text-danger"></i>')
} else { } else {
$(td).html('<i class="fa fa-check text-navy"></i>') $(td).html('<i class="fa fa-check text-navy"></i>')
} }
}}, }},
{targets: 6, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
if (cellData == 'Unknown'){ var users = [];
$(td).html('<i class="fa fa-circle text-warning"></i>') $.each(cellData, function (id, data) {
} else if (!cellData) { users.push(data.name);
$(td).html('<i class="fa fa-circle text-danger"></i>') });
} else { $(td).html(users.join(', '))
$(td).html('<i class="fa fa-circle text-navy"></i>') }}
}
}},
{# {targets: 9, createdCell: function (td, cellData, rowData) {#}
{# var conn_btn = '<a href="{% url "terminal:web-terminal" %}?id={{ DEFAULT_PK }}" class="btn btn-xs btn-info">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);#}
{# $(td).html(conn_btn)#}
{# }}#}
], ],
ajax_url: '{% url "api-assets:user-asset-list" %}', ajax_url: url,
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "hardware_info"}, {data: "is_active" }, {data: "is_connective"} {data: "is_active", orderable: false },
], {data: "system_users_granted", orderable: false}
op_html: $('#actions').html() ]
}; };
return jumpserver.initDataTable(options); asset_table = jumpserver.initDataTable(options);
return asset_table
} }
$(document).ready(function(){ function onSelected(event, treeNode) {
console.log("select");
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
initTable(); initTable();
}); setCookie('node_selected', treeNode.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
}
};
var zNodes = [];
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
if (value["key"] === "0") {
value["open"] = true;
}
value["name"] = value["value"]
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rMenu = $("#rMenu");
selectQueryNode();
});
}
$(document).ready(function () {
initTree();
});
</script> </script>
{% endblock %}
{% endblock %}

View File

@ -36,7 +36,9 @@ urlpatterns = [
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$', url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
url(r'^v1/nodes/children/$', api.NodeChildrenApi.as_view(), name='node-children-2'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.NodeAssetsApi.as_view(), name='node-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),

View File

@ -43,3 +43,7 @@ class StringIDField(serializers.Field):
def to_representation(self, value): def to_representation(self, value):
return {"pk": value.pk, "name": value.__str__()} return {"pk": value.pk, "name": value.__str__()}
class StringManyToManyField(serializers.RelatedField):
def to_representation(self, value):
return value.__str__()

View File

@ -232,6 +232,14 @@ def setattr_bulk(seq, key, value):
return map(set_attr, seq) return map(set_attr, seq)
def set_or_append_attr_bulk(seq, key, value):
for obj in seq:
ori = getattr(obj, key, None)
if ori:
value += " " + ori
setattr(obj, key, value)
def content_md5(data): def content_md5(data):
"""计算data的MD5值经过Base64编码并返回str类型。 """计算data的MD5值经过Base64编码并返回str类型。
@ -350,11 +358,17 @@ def get_short_uuid_str():
return str(uuid.uuid4()).split('-')[-1] return str(uuid.uuid4()).split('-')[-1]
def is_uuid(s): def is_uuid(seq):
if UUID_PATTERN.match(s): if isinstance(seq, str):
return True if UUID_PATTERN.match(seq):
return True
else:
return False
else: else:
return False for s in seq:
if not is_uuid(s):
return False
return True
def get_signer(): def get_signer():
@ -378,3 +392,4 @@ class TeeObj:
def close(self): def close(self):
self.file_obj.close() self.file_obj.close()

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,14 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework.generics import ListAPIView, get_object_or_404 from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
from rest_framework import viewsets from rest_framework import viewsets
from common.utils import set_or_append_attr_bulk
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
from .utils import NodePermissionUtil from .utils import AssetPermissionUtil
from .models import NodePermission from .models import AssetPermission
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, \ from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
NodeGrantedSerializer, SystemUser, NodeSerializer NodeGrantedSerializer, SystemUser, NodeSerializer
from . import serializers from . import serializers
@ -18,7 +19,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
""" """
资产授权列表的增删改查api 资产授权列表的增删改查api
""" """
queryset = NodePermission.objects.all() queryset = AssetPermission.objects.all()
serializer_class = serializers.AssetPermissionCreateUpdateSerializer serializer_class = serializers.AssetPermissionCreateUpdateSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
@ -29,12 +30,28 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
node_id = self.request.query_params.get('node_id') asset_id = self.request.query_params.get('asset')
node_id = self.request.query_params.get('node')
inherit_nodes = set()
if not asset_id and not node_id:
return queryset
if node_id: permissions = set()
queryset = queryset.filter(node__id=node_id) if asset_id:
asset = get_object_or_404(Asset, pk=asset_id)
permissions = set(queryset.filter(assets=asset))
for node in asset.nodes.all():
inherit_nodes.update(set(node.ancestor_with_node))
elif node_id:
node = get_object_or_404(Node, pk=node_id)
permissions = set(queryset.filter(nodes=node))
inherit_nodes = node.ancestor
return queryset for n in inherit_nodes:
_permissions = queryset.filter(nodes=n)
set_or_append_attr_bulk(_permissions, "inherit", n.value)
permissions.update(_permissions)
return permissions
class UserGrantedAssetsApi(ListAPIView): class UserGrantedAssetsApi(ListAPIView):
@ -53,7 +70,7 @@ class UserGrantedAssetsApi(ListAPIView):
else: else:
user = self.request.user user = self.request.user
for k, v in NodePermissionUtil.get_user_assets(user).items(): for k, v in AssetPermissionUtil.get_user_assets(user).items():
if k.is_unixlike(): if k.is_unixlike():
system_users_granted = [s for s in v if s.protocol == 'ssh'] system_users_granted = [s for s in v if s.protocol == 'ssh']
else: else:
@ -78,38 +95,11 @@ class UserGrantedNodesApi(ListAPIView):
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
else: else:
user = self.request.user user = self.request.user
nodes = NodePermissionUtil.get_user_nodes(user) nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
return nodes.keys() return nodes.keys()
class UserGrantedNodesWithAssetsApi(ListAPIView): class UserGrantedNodesWithAssetsApi(ListAPIView):
"""
授权用户的资产组这里的资产组并非是授权列表中授权的
而是把所有资产取出来然后反查出所有资产组然后合并得到
结果里也包含资产组下授权的资产
数据结构如下
[
{
"id": 1,
"value": "node",
... 其它属性
"assets_granted": [
{
"id": 1,
"hostname": "testserver",
"ip": "192.168.1.1",
"port": 22,
"system_users_granted": [
"id": 1,
"name": "web",
"username": "web",
"protocol": "ssh",
]
}
]
}
]
"""
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
serializer_class = NodeGrantedSerializer serializer_class = NodeGrantedSerializer
@ -121,18 +111,16 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
else: else:
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
nodes = NodePermissionUtil.get_user_nodes_with_assets(user) nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
assets = {} for node, _assets in nodes.items():
for k, v in NodePermissionUtil.get_user_assets(user).items(): assets = _assets.keys()
if k.is_unixlike(): for k, v in _assets.items():
system_users_granted = [s for s in v if s.protocol == 'ssh'] if k.is_unixlike():
else: system_users_granted = [s for s in v if s.protocol == 'ssh']
system_users_granted = [s for s in v if s.protocol == 'rdp'] else:
assets[k] = system_users_granted system_users_granted = [s for s in v if s.protocol == 'rdp']
for node, v in nodes.items(): k.system_users_granted = system_users_granted
for asset in v['assets']: node.assets_granted = assets
asset.system_users_granted = assets[asset]
node.assets_granted = v['assets']
queryset.append(node) queryset.append(node)
return queryset return queryset
@ -142,6 +130,31 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
return super().get_permissions() return super().get_permissions()
class UserGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsSuperUserOrAppUser,)
serializer_class = AssetGrantedSerializer
def get_queryset(self):
user_id = self.kwargs.get('pk', '')
node_id = self.kwargs.get('node_id')
if user_id:
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
node = get_object_or_404(Node, id=node_id)
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
assets = nodes.get(node, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
return assets
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedAssetsApi(ListAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
serializer_class = AssetGrantedSerializer serializer_class = AssetGrantedSerializer
@ -154,7 +167,7 @@ class UserGroupGrantedAssetsApi(ListAPIView):
return queryset return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id) user_group = get_object_or_404(UserGroup, id=user_group_id)
assets = NodePermissionUtil.get_user_group_assets(user_group) assets = AssetPermissionUtil.get_user_group_assets(user_group)
for k, v in assets.items(): for k, v in assets.items():
k.system_users_granted = v k.system_users_granted = v
queryset.append(k) queryset.append(k)
@ -171,8 +184,8 @@ class UserGroupGrantedNodesApi(ListAPIView):
if group_id: if group_id:
group = get_object_or_404(UserGroup, id=group_id) group = get_object_or_404(UserGroup, id=group_id)
nodes = NodePermissionUtil.get_user_group_nodes(group) nodes = AssetPermissionUtil.get_user_group_nodes_with_assets(group)
queryset = nodes.keys() return nodes.keys()
return queryset return queryset
@ -188,15 +201,33 @@ class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
return queryset return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id) user_group = get_object_or_404(UserGroup, id=user_group_id)
nodes = NodePermissionUtil.get_user_group_nodes_with_assets(user_group) nodes = AssetPermissionUtil.get_user_group_nodes_with_assets(user_group)
for node, v in nodes.items(): for node, _assets in nodes.items():
for asset in v['assets']: assets = _assets.keys()
asset.system_users_granted = v['system_users'] for asset, system_users in _assets.items():
node.assets_granted = v['assets'] asset.system_users_granted = system_users
node.assets_granted = assets
queryset.append(node) queryset.append(node)
return queryset return queryset
class UserGroupGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsSuperUserOrAppUser,)
serializer_class = AssetGrantedSerializer
def get_queryset(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)
node = get_object_or_404(Node, id=node_id)
nodes = AssetPermissionUtil.get_user_group_nodes_with_assets(user_group)
assets = nodes.get(node, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
return assets
class ValidateUserAssetPermissionView(APIView): class ValidateUserAssetPermissionView(APIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
@ -210,8 +241,82 @@ class ValidateUserAssetPermissionView(APIView):
asset = get_object_or_404(Asset, id=asset_id) asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id) system_user = get_object_or_404(SystemUser, id=system_id)
assets_granted = NodePermissionUtil.get_user_assets(user) assets_granted = AssetPermissionUtil.get_user_assets(user)
if system_user in assets_granted.get(asset, []): if system_user in assets_granted.get(asset, []):
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
else: else:
return Response({'msg': False}, status=403) return Response({'msg': False}, status=403)
class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
"""
将用户从授权中移除Detail页面会调用
"""
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
users = serializer.validated_data.get('users')
if users:
perm.users.remove(*tuple(users))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
users = serializer.validated_data.get('users')
if users:
perm.users.add(*tuple(users))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
"""
将用户从授权中移除Detail页面会调用
"""
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
assets = serializer.validated_data.get('assets')
if assets:
perm.assets.remove(*tuple(assets))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
assets = serializer.validated_data.get('assets')
if assets:
perm.assets.add(*tuple(assets))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})

View File

@ -4,27 +4,64 @@ from __future__ import absolute_import, unicode_literals
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .models import NodePermission from .hands import User
from .models import AssetPermission
class AssetPermissionForm(forms.ModelForm): class AssetPermissionForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP),
label=_("User"),
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Select users')
}
),
required=False,
)
class Meta: class Meta:
model = NodePermission model = AssetPermission
fields = [ exclude = (
'node', 'user_group', 'system_user', 'is_active', 'id', 'date_created', 'created_by'
'date_expired', 'comment', )
]
widgets = { widgets = {
'node': forms.Select( 'users': forms.SelectMultiple(
attrs={'style': 'display:none'} attrs={'class': 'select2', 'data-placeholder': _("User")}
), ),
'user_group': forms.Select( 'user_groups': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _("User group")} attrs={'class': 'select2', 'data-placeholder': _("User group")}
), ),
'system_user': forms.Select( 'assets': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _("Asset")}
),
'nodes': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _("Node")}
),
'system_users': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('System user')} attrs={'class': 'select2', 'data-placeholder': _('System user')}
), ),
} }
labels = {
'nodes': _("Node"),
}
def clean_system_user(self): def clean_user_groups(self):
return self.cleaned_data['system_user'] users = self.cleaned_data.get('users')
user_groups = self.cleaned_data.get('user_groups')
if not users and not user_groups:
raise forms.ValidationError(
_("User or group at least one required"))
return self.cleaned_data["user_groups"]
def clean_asset_groups(self):
assets = self.cleaned_data.get('assets')
asset_groups = self.cleaned_data.get('asset_groups')
if not assets and not asset_groups:
raise forms.ValidationError(
_("Asset or group at least one required"))
return self.cleaned_data["asset_groups"]

View File

@ -4,70 +4,63 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from common.utils import date_expired_default from common.utils import date_expired_default, set_or_append_attr_bulk
class ValidManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True) \
.filter(date_start__lt=timezone.now())\
.filter(date_expired__gt=timezone.now())
class AssetPermission(models.Model): class AssetPermission(models.Model):
from users.models import User, UserGroup
from assets.models import Asset, AssetGroup, SystemUser, Cluster
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
users = models.ManyToManyField(User, related_name='asset_permissions', blank=True, verbose_name=_("User")) users = models.ManyToManyField('users.User', related_name='asset_permissions', blank=True, verbose_name=_("User"))
user_groups = models.ManyToManyField(UserGroup, related_name='asset_permissions', blank=True, verbose_name=_("User group")) user_groups = models.ManyToManyField('users.UserGroup', related_name='asset_permissions', blank=True, verbose_name=_("User group"))
assets = models.ManyToManyField(Asset, related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
asset_groups = models.ManyToManyField(AssetGroup, related_name='granted_by_permissions', blank=True, verbose_name=_("Asset group")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
system_users = models.ManyToManyField(SystemUser, related_name='granted_by_permissions', verbose_name=_("System user")) system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
is_active = models.BooleanField(default=True, verbose_name=_('Active')) is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(default=timezone.now, verbose_name=_("Date start"))
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired')) date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True) comment = models.TextField(verbose_name=_('Comment'), blank=True)
objects = models.Manager()
valid = ValidManager()
def __str__(self): def __str__(self):
return self.name return self.name
@property
def id_str(self):
return str(self.id)
@property @property
def is_valid(self): def is_valid(self):
if self.date_expired > timezone.now() and self.is_active: if self.date_expired > timezone.now() > self.date_start and self.is_active:
return True return True
return False return False
def get_granted_users(self): def get_all_users(self):
return list(set(self.users.all()) | self.get_granted_user_groups_member()) users = set(self.users.all())
for group in self.user_groups.all():
def get_granted_user_groups_member(self): _users = group.users.all()
users = set() set_or_append_attr_bulk(_users, 'inherit', group.name)
for user_group in self.user_groups.all(): users.update(set(_users))
for user in user_group.users.all():
setattr(user, 'is_inherit_from_user_groups', True)
setattr(user, 'inherit_from_user_groups',
getattr(user, 'inherit_from_user_groups', set()).add(user_group))
users.add(user)
return users return users
def get_granted_assets(self): def get_all_assets(self):
return list(set(self.assets.all()) | self.get_granted_asset_groups_member()) assets = set(self.assets.all())
for node in self.nodes.all():
def get_granted_asset_groups_member(self): _assets = node.get_all_assets()
assets = set() set_or_append_attr_bulk(_assets, 'inherit', node.value)
for asset_group in self.asset_groups.all(): assets.update(set(_assets))
for asset in asset_group.assets.all():
setattr(asset, 'is_inherit_from_asset_groups', True)
setattr(asset, 'inherit_from_asset_groups',
getattr(asset, 'inherit_from_user_groups', set()).add(asset_group))
assets.add(asset)
return assets return assets
def check_system_user_in_assets(self):
errors = {}
assets = self.get_granted_assets()
clusters = set([asset.cluster for asset in assets])
for system_user in self.system_users.all():
cluster_remain = clusters - set(system_user.cluster.all())
if cluster_remain:
errors[system_user] = cluster_remain
return errors
class NodePermission(models.Model): class NodePermission(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)

View File

@ -1,31 +1,36 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.utils import get_object_or_none from .models import AssetPermission
from common.fields import StringIDField from common.fields import StringManyToManyField
from .models import AssetPermission, NodePermission
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer): class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = NodePermission model = AssetPermission
fields = [ exclude = ('id', 'created_by', 'date_created')
'id', 'node', 'user_group', 'system_user',
'is_active', 'date_expired'
]
class AssetPermissionListSerializer(serializers.ModelSerializer): class AssetPermissionListSerializer(serializers.ModelSerializer):
node = StringIDField(read_only=True) users = StringManyToManyField(many=True, read_only=True)
user_group = StringIDField(read_only=True) user_groups = StringManyToManyField(many=True, read_only=True)
system_user = StringIDField(read_only=True) assets = StringManyToManyField(many=True, read_only=True)
nodes = StringManyToManyField(many=True, read_only=True)
system_users = StringManyToManyField(many=True, read_only=True)
inherit = serializers.SerializerMethodField()
class Meta: class Meta:
model = NodePermission model = AssetPermission
fields = '__all__' fields = '__all__'
@staticmethod
def get_inherit(obj):
if hasattr(obj, 'inherit'):
return obj.inherit
else:
return None
class AssetPermissionUpdateUserSerializer(serializers.ModelSerializer): class AssetPermissionUpdateUserSerializer(serializers.ModelSerializer):
@ -40,14 +45,3 @@ class AssetPermissionUpdateAssetSerializer(serializers.ModelSerializer):
model = AssetPermission model = AssetPermission
fields = ['id', 'assets'] fields = ['id', 'assets']
class UserAssetPermissionCreateUpdateSerializer(AssetPermissionCreateUpdateSerializer):
is_inherited = serializers.SerializerMethodField()
@staticmethod
def get_is_inherited(obj):
if getattr(obj, 'inherited', ''):
return True
else:
return False

View File

@ -1,18 +1,42 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from common.utils import get_logger from common.utils import get_logger
from .models import NodePermission from .models import AssetPermission
logger = get_logger(__file__) logger = get_logger(__file__)
@receiver(post_save, sender=NodePermission, dispatch_uid="my_unique_identifier") @receiver(m2m_changed, sender=AssetPermission.nodes.through)
def on_asset_permission_create_or_update(sender, instance=None, **kwargs): def on_permission_nodes_changed(sender, instance=None, **kwargs):
if instance and instance.node and instance.system_user: if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
instance.system_user.nodes.add(instance.node) logger.debug("Asset permission nodes change signal received")
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
system_users = instance.system_users.all()
for system_user in system_users:
system_user.nodes.add(*tuple(nodes))
@receiver(m2m_changed, sender=AssetPermission.assets.through)
def on_permission_assets_changed(sender, instance=None, **kwargs):
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
logger.debug("Asset permission assets change signal received")
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
system_users = instance.system_users.all()
for system_user in system_users:
system_user.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=AssetPermission.system_users.through)
def on_permission_system_users_changed(sender, instance=None, **kwargs):
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
logger.debug("Asset permission system_users change signal received")
system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
assets = instance.assets.all()
nodes = instance.nodes.all()
for system_user in system_users:
system_user.nodes.add(*tuple(nodes))
system_user.assets.add(*tuple(assets))

View File

@ -57,12 +57,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for asset in page_obj %} {% for asset in object_list %}
<tr> <tr>
<td>{{ asset.hostname }}</td> <td>{{ asset.hostname }}</td>
<td>{{ asset.ip }}</td> <td>{{ asset.ip }}</td>
<td> <td>
<button title="{{ asset.inherit_from_asset_groups }}" data-gid="{{ asset.id }}" class="btn btn-danger btn-xs btn-remove-asset {% if asset.is_inherit_from_asset_groups %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -105,7 +105,7 @@
<div class="panel panel-info"> <div class="panel panel-info">
<div class="panel-heading"> <div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Add asset group to this permission' %} <i class="fa fa-info-circle"></i> {% trans 'Add node to this permission' %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<table class="table group_edit"> <table class="table group_edit">
@ -113,25 +113,25 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select asset groups' %}" class="select2 group" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select nodes' %}" class="select2 group" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups_remain %} {% for node in nodes_remain %}
<option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}">{{ asset_group.name }}</option> <option value="{{ node.id }}" id="opt_{{ node.id }}">{{ node.value }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-add-group">{% trans 'Join' %}</button> <button type="button" class="btn btn-info btn-sm" id="btn-add-node">{% trans 'Join' %}</button>
</td> </td>
</tr> </tr>
</form> </form>
{% for asset_group in asset_groups %} {% for node in asset_permission.nodes.all %}
<tr> <tr>
<td ><b class="bdg_user_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td> <td ><b class="bdg_user_group" data-gid={{ node.id }}>{{ node.value }}</b></td>
<td> <td>
<button class="btn btn-danger btn-xs btn-remove-group" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger btn-xs btn-remove-node" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -179,10 +179,10 @@ function removeAssets(assets) {
}); });
} }
function updateGroup(groups) { function updateNodes(nodes) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}"; var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}";
var body = { var body = {
asset_groups: groups nodes: nodes
}; };
APIUpdateAttr({ APIUpdateAttr({
url: the_url, url: the_url,
@ -231,17 +231,17 @@ $(document).ready(function () {
var assets = [asset_id]; var assets = [asset_id];
removeAssets(assets) removeAssets(assets)
}) })
.on('click', '#btn-add-group', function () { .on('click', '#btn-add-node', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) { if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false; return false;
} }
var groups = $('.bdg_group').map(function() { var nodes = $('.bdg_group').map(function() {
return $(this).data('gid'); return $(this).data('gid');
}).get(); }).get();
$.map(jumpserver.nodes_selected, function(group_name, index) { $.map(jumpserver.nodes_selected, function(group_name, index) {
groups.push(index); nodes.push(index);
$('#opt_' + index).remove(); $('#opt_' + index).remove();
$('.group_edit tbody').append( $('.group_edit tbody').append(
'<tr>' + '<tr>' +
@ -251,17 +251,17 @@ $(document).ready(function () {
) )
}); });
updateGroup(groups); updateNodes(nodes);
}) })
.on('click', '.btn-remove-group', function () { .on('click', '.btn-remove-node', function () {
var $this = $(this); var $this = $(this);
var $tr = $this.closest('tr'); var $tr = $this.closest('tr');
var groups = $('.bdg_group').map(function() { var nodes = $('.bdg_group').map(function() {
if ($(this).data('gid') !== $this.data('gid')){ if ($(this).data('gid') !== $this.data('gid')){
return $(this).data('gid'); return $(this).data('gid');
} }
}).get(); }).get();
updateGroup(groups); updateNodes(nodes);
$tr.remove() $tr.remove()
}) })
</script> </script>

View File

@ -28,23 +28,19 @@
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<form method="post" class="form-horizontal" action="" > <form method="post" class="form-horizontal" action="" >
{% csrf_token %} {% csrf_token %}
<h3>{% trans 'Basic' %}</h3> <h3>{% trans 'Basic' %}</h3>
<div class="form-group"> {% bootstrap_field form.name layout="horizontal" %}
<label class="col-md-2 control-label" for="id_name">{% trans 'Node' %}</label> <div class="hr-line-dashed"></div>
<div class="col-md-9"> <h3>{% trans 'User' %}</h3>
<input type="text" class="form-control" readonly value="{{ form.node.initial }}"> {% bootstrap_field form.users layout="horizontal" %}
</div> {% bootstrap_field form.user_groups layout="horizontal" %}
</div> <div class="hr-line-dashed"></div>
{{ form.node }} <h3>{% trans 'Asset' %}</h3>
{% bootstrap_field form.user_group layout="horizontal" %} {% bootstrap_field form.assets layout="horizontal" %}
{% bootstrap_field form.system_user layout="horizontal" %} {% bootstrap_field form.nodes layout="horizontal" %}
{% bootstrap_field form.system_users layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3> <h3>{% trans 'Other' %}</h3>
<div class="form-group"> <div class="form-group">
@ -53,17 +49,19 @@
{{ form.is_active }} {{ form.is_active }}
</div> </div>
</div> </div>
<div class="form-group {% if form.date_expired.errors or form.date_start.errors %} has-error {% endif %}" id="date_5">
<div class="form-group {% if form.date_expired.errors %} has-error {% endif %}" id="date_5">
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label> <label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group date"> <div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span> <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input id="{{ form.date_expired.id_for_label }}" name="{{ form.date_expired.html_name }}" type="text" class="form-control" value="{{ form.date_expired.value|date:'Y-m-d'|default:form.date_expired.value }}"> <input type="text" class="input-sm form-control" name="date_start" value="{{ form.date_start.value|date:'Y-m-d' }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" name="date_expired" value="{{ form.date_expired.value|date:'Y-m-d' }}">
</div> </div>
<span class="help-block ">{{ form.date_expired.errors }}</span> <span class="help-block ">{{ form.date_expired.errors }}</span>
<span class="help-block ">{{ form.date_start.errors }}</span>
</div> </div>
</div> </div>
{% bootstrap_field form.comment layout="horizontal" %} {% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group"> <div class="form-group">
@ -84,15 +82,14 @@
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
$('#datepicker').datepicker({
$('.input-group.date').datepicker({
format: "yyyy-mm-dd", format: "yyyy-mm-dd",
todayBtn: "linked", todayBtn: "linked",
keyboardNavigation: false, keyboardNavigation: false,
forceParse: false, forceParse: false,
calendarWeeks: true, calendarWeeks: true,
autoclose: true autoclose: true
}) });
}) })
</script> </script>
{% endblock %} {% endblock %}

View File

@ -15,19 +15,19 @@
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"> <li class="active">
<a href="{% url 'perms:asset-permission-detail' pk=asset_permission.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a> <a href="{% url 'perms:asset-permission-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li> </li>
<li> <li>
<a href="{% url 'perms:asset-permission-user-list' pk=asset_permission.id %}" class="text-center"> <a href="{% url 'perms:asset-permission-user-list' pk=object.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'Users and user groups' %} <i class="fa fa-bar-chart-o"></i> {% trans 'Users and user groups' %}
</a> </a>
</li> </li>
<li> <li>
<a href="{% url 'perms:asset-permission-asset-list' pk=asset_permission.id %}" class="text-center"> <a href="{% url 'perms:asset-permission-asset-list' pk=object.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets and asset groups' %}</a> <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and asset groups' %}</a>
</li> </li>
<li class="pull-right"> <li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'perms:asset-permission-update' pk=asset_permission.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a> <a class="btn btn-outline btn-default" href="{% url 'perms:asset-permission-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li> </li>
<li class="pull-right"> <li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-perm"> <a class="btn btn-outline btn-danger btn-delete-perm">
@ -40,7 +40,7 @@
<div class="col-sm-7" style="padding-left: 0;"> <div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label"><b>{{ asset_permission.name }}</b></span> <span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools"> <div class="ibox-tools">
<a class="collapse-link"> <a class="collapse-link">
<i class="fa fa-chevron-up"></i> <i class="fa fa-chevron-up"></i>
@ -60,43 +60,47 @@
<tbody> <tbody>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td> <td>{% trans 'Name' %}:</td>
<td><b>{{ asset_permission.name }}</b></td> <td><b>{{ object.name }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'User count' %}:</td> <td>{% trans 'User count' %}:</td>
<td><b>{{ asset_permission.users.count }}</b></td> <td><b>{{ object.users.count }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'User group count' %}:</td> <td>{% trans 'User group count' %}:</td>
<td><b>{{ asset_permission.users.count }}</b></td> <td><b>{{ object.users.count }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Asset count' %}:</td> <td>{% trans 'Asset count' %}:</td>
<td><b>{{ asset_permission.assets.count }}</b></td> <td><b>{{ object.assets.count }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Asset group count' %}:</td> <td>{% trans 'Node count' %}:</td>
<td><b>{{ asset_permission.asset_groups.count }}</b></td> <td><b>{{ object.nodes.count }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'System user count' %}:</td> <td>{% trans 'System user count' %}:</td>
<td><b>{{ asset_permission.system_users.count }}</b></td> <td><b>{{ object.system_users.count }}</b></td>
</tr>
<tr>
<td>{% trans 'Date start' %}:</td>
<td><b>{{ object.date_start }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Date expired' %}:</td> <td>{% trans 'Date expired' %}:</td>
<td><b>{{ asset_permission.date_expired }}</b></td> <td><b>{{ object.date_expired }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Date created' %}:</td> <td>{% trans 'Date created' %}:</td>
<td><b>{{ asset_permission.date_created }}</b></td> <td><b>{{ object.date_created }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Created by' %}:</td> <td>{% trans 'Created by' %}:</td>
<td><b>{{ asset_permission.created_by }}</b></td> <td><b>{{ object.created_by }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Comment' %}:</td> <td>{% trans 'Comment' %}:</td>
<td><b>{{ asset_permission.comment }}</b></td> <td><b>{{ object.comment }}</b></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -117,7 +121,7 @@
<td><span style="float: right"> <td><span style="float: right">
<div class="switch"> <div class="switch">
<div class="onoffswitch"> <div class="onoffswitch">
<input type="checkbox" {% if asset_permission.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active"> <input type="checkbox" {% if object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
<label class="onoffswitch-label" for="is_active"> <label class="onoffswitch-label" for="is_active">
<span class="onoffswitch-inner"></span> <span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span> <span class="onoffswitch-switch"></span>
@ -155,7 +159,7 @@
</tr> </tr>
</form> </form>
{% for system_user in system_users %} {% for system_user in object.system_users.all %}
<tr {% if forloop.counter == 1 %} class="no-borders-tr" {% endif %} > <tr {% if forloop.counter == 1 %} class="no-borders-tr" {% endif %} >
<td ><b class="bdg-system-user" data-uid={{ system_user.id }}>{{ system_user.name }}</b></td> <td ><b class="bdg-system-user" data-uid={{ system_user.id }}>{{ system_user.name }}</b></td>
<td> <td>
@ -179,7 +183,7 @@
jumpserver.system_users_selected = {}; jumpserver.system_users_selected = {};
function updateSystemUser(system_users) { function updateSystemUser(system_users) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}"; var the_url = "{% url 'api-perms:asset-permission-detail' pk=object.id %}";
var body = { var body = {
system_users: Object.assign([], system_users) system_users: Object.assign([], system_users)
}; };
@ -203,7 +207,7 @@ $(document).ready(function () {
.on('click', '.btn-delete-perm', function () { .on('click', '.btn-delete-perm', function () {
var $this = $(this); var $this = $(this);
var name = "{{ asset_permission.name }}"; var name = "{{ asset_permission.name }}";
var uid = "{{ asset_permission.id }}"; var uid = "{{ object.id }}";
var the_url = '{% url "api-perms:asset-permission-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); var the_url = '{% url "api-perms:asset-permission-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'perms:asset-permission-list' %}"; var redirect_url = "{% url 'perms:asset-permission-list' %}";
objectDelete($this, name, the_url, redirect_url); objectDelete($this, name, the_url, redirect_url);
@ -238,7 +242,7 @@ $(document).ready(function () {
updateSystemUser(system_users); updateSystemUser(system_users);
$tr.remove() $tr.remove()
}).on('click', '#is_active', function () { }).on('click', '#is_active', function () {
var the_url = '{% url "api-perms:asset-permission-detail" pk=asset_permission.id %}'; var the_url = '{% url "api-perms:asset-permission-detail" pk=object.id %}';
var checked = $(this).prop('checked'); var checked = $(this).prop('checked');
var body = { var body = {
'is_active': checked 'is_active': checked

View File

@ -3,41 +3,29 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<link href="{% static 'css/plugins/jstree/style.min.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/jstree/style.min.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<style type="text/css"> <style>
div#rMenu { .toggle {
position:absolute; cursor: pointer;
visibility:hidden;
text-align: left;
top: 100%;
left: 0;
z-index: 1000;
float: left;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
background-clip: padding-box;
} }
div#rMenu li{ .detail-key {
margin: 1px 0; width: 70px;
cursor: pointer;
{#list-style: none outside none;#}
} }
.dropdown a:hover { </style>
background-color: #f1f1f1
}
</style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content"> <div class="wrapper wrapper-content">
<div class="row"> <div class="row">
<div class="col-lg-3" id="split-left"> <div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager "> <div class="file-manager ">
<div id="assetTree" class="ztree"> <div id="assetTree" class="ztree">
</div> </div>
@ -61,16 +49,16 @@
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%"> <table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
<thead> <thead>
<tr> <tr>
<th class="text-center"> <th></th>
<input type="checkbox" id="check_all" class="ipt_check_all" > <th>{% trans 'Name' %}</th>
</th> <th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'Node' %}</th> <th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'User group' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th> <th class="text-center">{% trans 'Node'%}</th>
<th class="text-center">{% trans 'Is active' %}</th> <th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Date expired' %}</th> <th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center" >{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>
@ -82,64 +70,21 @@
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
var zTree, rMenu, table, show = 0; var zTree, table, show = 0;
function initTable() {
var options = {
ele: $('#permission_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData) {
var html = '<a href="{% url 'assets:asset-list' %}?node=99899">' + cellData.name + '</a>';
$(td).html(html.replace("99899", cellData.pk));
}},
{targets: 2, createdCell: function (td, cellData) {
var html = '<a href="{% url "users:user-group-detail" pk=DEFAULT_PK %}">' + cellData.name + '</a>';
$(td).html(html.replace("{{ DEFAULT_PK }}", cellData.pk))
}},
{targets: 3, createdCell: function (td, cellData) {
var html = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + cellData.name + '</a>';
$(td).html(html.replace("{{ DEFAULT_PK }}", cellData.pk));
}},
{targets: 4, 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: 5, createdCell: function (td, cellData) {
var date_expired = cellData.split(" ");
if (date_expired && date_expired.length === 3) {
$(td).html(date_expired[0]);
} else {
$(td).html(cellData);
}
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var name = rowData.user_group.name + "=>" + rowData.system_user.name + "=>" + rowData.node.name;
var update_btn = '<a href="{% url "perms:asset-permission-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-del" data-uid="{{ DEFAULT_PK }}" data-name="99991938">{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', name);
$(td).html(update_btn + del_btn);
}}
],
ajax_url: '{% url "api-perms:asset-permission-list" %}',
columns: [
{data: "id"}, {data: "node"}, {data: "user_group" }, {data: "system_user"},
{data: "is_active"}, {data: "date_expired"}, {data: "id"}
],
op_html: $('#actions').html()
};
table = jumpserver.initDataTable(options);
return table
}
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
setCookie('node_selected', treeNode.id);
var url = table.ajax.url(); var url = table.ajax.url();
url = setUrlParam(url, "node_id", treeNode.id); if (treeNode.is_asset) {
url = setUrlParam(url, 'node', "");
url = setUrlParam(url, 'asset', treeNode.id)
} else {
url = setUrlParam(url, 'asset', "");
url = setUrlParam(url, 'node', treeNode.id)
}
setCookie('node_selected', treeNode.id); setCookie('node_selected', treeNode.id);
table.ajax.url(url); table.ajax.url(url);
table.ajax.reload(); table.ajax.reload();
@ -160,9 +105,114 @@ function selectQueryNode() {
node = zTree.getNodesByParam("id", node_id, null); node = zTree.getNodesByParam("id", node_id, null);
if (node){ if (node){
zTree.selectNode(node[0]); zTree.selectNode(node[0]);
node.open = true;
} }
} }
function filter(treeId, parentNode, childNodes) {
$.each(childNodes, function (index, value) {
value["pId"] = value["parent"];
value["name"] = value["value"];
value["isParent"] = value["assets_amount"] !== 0;
value["iconSkin"] = value["is_asset"] ? "file" : null;
});
return childNodes;
}
function beforeAsync(treeId, treeNode) {
return true;
}
function makeLabel(data) {
return "<label class='detail-key'><b>" + data[0] + ": </b></label>" + data[1] + "</br>"
}
function format(d) {
var data = "";
if (d.users.length > 0 ) {
data += makeLabel(["{% trans 'User' %}", d.users.join(", ")])
}
if (d.user_groups.length > 0) {
data += makeLabel(["{% trans 'User group' %}", d.user_groups.join(", ")])
}
if (d.assets.length > 0) {
data += makeLabel(["{% trans 'Asset' %}", d.assets.join(", ")])
}
if (d.nodes.length > 0) {
data += makeLabel(["{% trans 'Node' %}", d.nodes.join(", ")])
}
if (d.system_users.length > 0) {
data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")])
}
return data
}
function initTable() {
var options = {
ele: $('#permission_list_table'),
toggle: true,
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
$(td).addClass("toggle");
$(td).html("<i class='fa fa-angle-right'></i>");
}},
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "perms:asset-permission-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 3, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 4, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 5, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 6, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 7, 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: 8, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "perms:asset-permission-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-del" data-uid="{{ DEFAULT_PK }}" mark=1 data-name="99991938">{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', rowData.name);
if (rowData.inherit) {
del_btn = del_btn.replace("mark", "disabled")
}
$(td).html(update_btn + del_btn);
}}
],
ajax_url: '{% url "api-perms:asset-permission-list" %}',
columns: [
{data: "id"}, {data: "name"}, {data: "users"},
{data: "user_groups"}, {data: "assets"},
{data: "nodes"}, {data: "system_users"},
{data: "is_active", orderable: false}, {data: "id", orderable: false}
],
select: {},
op_html: $('#actions').html()
};
table = jumpserver.initDataTable(options);
return table
}
function initTree() { function initTree() {
var setting = { var setting = {
view: { view: {
@ -174,25 +224,32 @@ function initTree() {
enable: true enable: true
} }
}, },
async: {
enable: true,
url: "{% url 'api-assets:node-children-2' %}?assets=1",
autoParam:["id", "name=n", "level=lv"],
dataFilter: filter,
type: 'get'
},
callback: { callback: {
onSelected: onSelected onSelected: onSelected,
beforeAsync: beforeAsync
} }
}; };
var zNodes = []; var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){ $.get("{% url 'api-assets:node-children-2' %}", function(data, status){
$.each(data, function (index, value) { $.each(data, function (index, value) {
value["pId"] = value["parent"]; value["pId"] = value["parent"];
{#if (value["key"] === "0") {#} value["isParent"] = value["assets_amount"] !== 0;
value["open"] = true; value["name"] = value["value"];
{# }#} value["open"] = value["key"] === "0";
value["name"] = value["value"]
}); });
zNodes = data; zNodes = data;
{#$.fn.zTree.init($("#assetTree"), setting);#}
$.fn.zTree.init($("#assetTree"), setting, zNodes); $.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree"); zTree = $.fn.zTree.getZTreeObj("assetTree");
rMenu = $("#rMenu"); selectQueryNode();
selectQueryNode();
}); });
} }
@ -226,12 +283,41 @@ $(document).ready(function(){
.on('click', '.btn-create-permission', function () { .on('click', '.btn-create-permission', function () {
var url = "{% url 'perms:asset-permission-create' %}"; var url = "{% url 'perms:asset-permission-create' %}";
var nodes = zTree.getSelectedNodes(); var nodes = zTree.getSelectedNodes();
var current_node; var _nodes = [];
if (nodes && nodes.length ===1 ){ var _assets = [];
current_node = nodes[0]; $.each(nodes, function (id, node) {
url += "?node_id=" + current_node.id; if (node.is_asset) {
} _assets.push(node.id)
} else {
_nodes.push(node.id)
}
});
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
window.open(url, '_self'); window.open(url, '_self');
}).on('click', '.toggle', function (e) {
e.preventDefault();
var detailRows = [];
var tr = $(this).closest('tr');
var row = table.row(tr);
var idx = $.inArray(tr.attr('id'), detailRows);
if (row.child.isShown()) {
tr.removeClass('details');
row.child.hide();
// Remove from the 'open' array
detailRows.splice(idx, 1);
}
else {
tr.addClass('details');
$('.toggle i').removeClass('fa-angle-right').addClass('fa-angle-down');
row.child(format(row.data())).show();
// Add to the 'open' array
if ( idx === -1 ) {
detailRows.push(tr.attr('id'));
}
}
}) })
</script> </script>

View File

@ -57,12 +57,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in page_obj %} {% for user in object_list %}
<tr> <tr>
<td>{{ user.name }}</td> <td>{{ user.name }}</td>
<td>{{ user.username }}</td> <td>{{ user.username }}</td>
<td> <td>
<button class="btn btn-danger btn-xs btn-remove-user {% if user.is_inherit_from_user_groups %} disabled {% endif %}" data-gid="{{ user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger btn-xs btn-remove-user {% if user.inherit %} disabled {% endif %}" data-gid="{{ user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -127,7 +127,7 @@
</tr> </tr>
</form> </form>
{% for user_group in user_groups %} {% for user_group in asset_permission.user_groups.all %}
<tr> <tr>
<td ><b class="bdg_group" data-gid={{ user_group.id }}>{{ user_group.name }}</b></td> <td ><b class="bdg_group" data-gid={{ user_group.id }}>{{ user_group.name }}</b></td>
<td> <td>

View File

@ -11,17 +11,50 @@ router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permi
urlpatterns = [ urlpatterns = [
# 查询某个用户授权的资产和资产组 # 查询某个用户授权的资产和资产组
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'), url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
url(r'^v1/user/assets/$', api.UserGrantedAssetsApi.as_view(), name='my-assets'), api.UserGrantedAssetsApi.as_view(), name='user-assets'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$', api.UserGrantedNodesApi.as_view(), name='user-nodes'), url(r'^v1/user/assets/$', api.UserGrantedAssetsApi.as_view(),
url(r'^v1/user/nodes/$', api.UserGrantedNodesApi.as_view(), name='my-nodes'), name='my-assets'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
url(r'^v1/user/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), api.UserGrantedNodesApi.as_view(), name='user-nodes'),
url(r'^v1/user/nodes/$', api.UserGrantedNodesApi.as_view(),
name='my-nodes'),
url(
r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
url(r'^v1/user/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$',
api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'),
url(r'^v1/user/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(),
name='my-nodes-assets'),
# 查询某个用户组授权的资产和资产组 # 查询某个用户组授权的资产和资产组
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'), url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$',
api.UserGroupGrantedNodesWithAssetsApi.as_view(),
name='user-group-nodes-assets'),
url(
r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
api.UserGroupGrantedNodeAssetsApi.as_view(),
name='user-group-node-assets'),
# 用户和资产授权变更
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/remove/$',
api.AssetPermissionRemoveUserApi.as_view(),
name='asset-permission-remove-user'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/add/$',
api.AssetPermissionAddUserApi.as_view(),
name='asset-permission-add-user'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/remove/$',
api.AssetPermissionRemoveAssetApi.as_view(),
name='asset-permission-remove-asset'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/add/$',
api.AssetPermissionAddAssetApi.as_view(),
name='asset-permission-add-asset'),
# 验证用户是否有某个资产和系统用户的权限 # 验证用户是否有某个资产和系统用户的权限
url(r'v1/asset-permission/user/validate/$', api.ValidateUserAssetPermissionView.as_view(), name='validate-user-asset-permission'), url(r'v1/asset-permission/user/validate/$', api.ValidateUserAssetPermissionView.as_view(), name='validate-user-asset-permission'),

View File

@ -9,10 +9,10 @@ urlpatterns = [
url(r'^asset-permission$', views.AssetPermissionListView.as_view(), name='asset-permission-list'), url(r'^asset-permission$', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
url(r'^asset-permission/create$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'), url(r'^asset-permission/create$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
# url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
# url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
# url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
] ]

View File

@ -2,17 +2,204 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
import collections import collections
from collections import defaultdict
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _
import copy import copy
from common.utils import setattr_bulk, get_logger from common.utils import set_or_append_attr_bulk, get_logger
from .models import NodePermission from .models import AssetPermission
logger = get_logger(__file__) logger = get_logger(__file__)
class AssetPermissionUtil:
@staticmethod
def get_user_permissions(user):
return AssetPermission.valid.all().filter(users=user)
@staticmethod
def get_user_group_permissions(user_group):
return AssetPermission.valid.all().filter(user_groups=user_group)
@staticmethod
def get_asset_permissions(asset):
return AssetPermission.valid.all().filter(assets=asset)
@staticmethod
def get_node_permissions(node):
return AssetPermission.valid.all().filter(nodes=node)
@staticmethod
def get_system_user_permissions(system_user):
return AssetPermission.objects.all().filter(system_users=system_user)
@classmethod
def get_user_group_nodes(cls, group):
nodes = defaultdict(set)
permissions = cls.get_user_group_permissions(group)
for perm in permissions:
_nodes = perm.nodes.all()
_system_users = perm.system_users.all()
set_or_append_attr_bulk(_nodes, 'permission', perm.id)
for node in _nodes:
nodes[node].update(set(_system_users))
return nodes
@classmethod
def get_user_group_assets_direct(cls, group):
assets = defaultdict(set)
permissions = cls.get_user_group_permissions(group)
for perm in permissions:
_assets = perm.assets.all()
_system_users = perm.system_users.all()
set_or_append_attr_bulk(_assets, 'permission', perm.id)
for asset in _assets:
assets[asset].update(set(_system_users))
return assets
@classmethod
def get_user_group_nodes_assets(cls, group):
assets = defaultdict(set)
nodes = cls.get_user_group_nodes(group)
for node, _system_users in nodes.items():
_assets = node.get_all_assets()
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
for asset in _assets:
assets[asset].update(set(_system_users))
return assets
@classmethod
def get_user_group_assets(cls, group):
assets = defaultdict(set)
_assets = cls.get_user_group_assets_direct(group)
_nodes_assets = cls.get_user_group_nodes_assets(group)
for asset, _system_users in _assets.items():
assets[asset].update(set(_system_users))
for asset, _system_users in _nodes_assets.items():
assets[asset].update(set(_system_users))
return assets
@classmethod
def get_user_group_nodes_with_assets(cls, user):
"""
:param user:
:return: {node: {asset: set(su1, su2)}}
"""
nodes = defaultdict(dict)
_assets = cls.get_user_group_assets(user)
for asset, _system_users in _assets.items():
_nodes = asset.get_nodes()
for node in _nodes:
if asset in nodes[node]:
nodes[node][asset].update(_system_users)
else:
nodes[node][asset] = _system_users
return nodes
@classmethod
def get_user_assets_direct(cls, user):
assets = defaultdict(set)
permissions = list(cls.get_user_permissions(user))
for perm in permissions:
_assets = perm.assets.all()
_system_users = perm.system_users.all()
set_or_append_attr_bulk(_assets, 'permission', perm.id)
for asset in _assets:
assets[asset].update(set(_system_users))
return assets
@classmethod
def get_user_nodes_direct(cls, user):
nodes = defaultdict(set)
permissions = cls.get_user_permissions(user)
for perm in permissions:
_nodes = perm.nodes.all()
_system_users = perm.system_users.all()
set_or_append_attr_bulk(_nodes, 'permission', perm.id)
for node in _nodes:
nodes[node].update(set(_system_users))
return nodes
@classmethod
def get_user_nodes_assets_direct(cls, user):
assets = defaultdict(set)
nodes = cls.get_user_nodes_direct(user)
for node, _system_users in nodes.items():
_assets = node.get_all_assets()
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
for asset in _assets:
assets[asset].update(set(_system_users))
return assets
@classmethod
def get_user_assets_inherit_group(cls, user):
assets = defaultdict(set)
for group in user.groups.all():
_assets = cls.get_user_group_assets(group)
set_or_append_attr_bulk(_assets, 'inherit_group', group.id)
for asset, _system_users in _assets.items():
assets[asset].update(_system_users)
return assets
@classmethod
def get_user_assets(cls, user):
assets = defaultdict(set)
_assets_direct = cls.get_user_assets_direct(user)
_nodes_assets_direct = cls.get_user_nodes_assets_direct(user)
_assets_inherit_group = cls.get_user_assets_inherit_group(user)
for asset, _system_users in _assets_direct.items():
assets[asset].update(_system_users)
for asset, _system_users in _nodes_assets_direct.items():
assets[asset].update(_system_users)
for asset, _system_users in _assets_inherit_group.items():
assets[asset].update(_system_users)
return assets
@classmethod
def get_user_nodes_with_assets(cls, user):
"""
:param user:
:return: {node: {asset: set(su1, su2)}}
"""
nodes = defaultdict(dict)
_assets = cls.get_user_assets(user)
for asset, _system_users in _assets.items():
_nodes = asset.get_nodes()
for node in _nodes:
if asset in nodes[node]:
nodes[node][asset].update(_system_users)
else:
nodes[node][asset] = _system_users
return nodes
@classmethod
def get_system_user_assets(cls, system_user):
assets = set()
permissions = cls.get_system_user_permissions(system_user)
for perm in permissions:
assets.update(set(perm.assets.all()))
nodes = perm.nodes.all()
for node in nodes:
assets.update(set(node.get_all_assets()))
return assets
@classmethod
def get_node_system_users(cls, node):
system_users = set()
permissions = cls.get_node_permissions(node)
for perm in permissions:
system_users.update(perm.system_users.all())
return system_users
# Abandon
class NodePermissionUtil: class NodePermissionUtil:
"""
"""
@staticmethod @staticmethod
def get_user_group_permissions(user_group): def get_user_group_permissions(user_group):

View File

@ -3,20 +3,22 @@
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import ListView, CreateView, UpdateView from django.views.generic import ListView, CreateView, UpdateView, DetailView
from django.views.generic.edit import DeleteView from django.views.generic.edit import DeleteView, SingleObjectMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings
from common.utils import get_object_or_none from common.mixins import AdminUserRequiredMixin
from .hands import AdminUserRequiredMixin, Node from .hands import Node, Asset, SystemUser, User, UserGroup
from .models import AssetPermission, NodePermission from .models import AssetPermission
from .forms import AssetPermissionForm from .forms import AssetPermissionForm
class AssetPermissionListView(AdminUserRequiredMixin, ListView): class AssetPermissionListView(AdminUserRequiredMixin, ListView):
model = NodePermission model = AssetPermission
context_object_name = 'asset_permission_list'
template_name = 'perms/asset_permission_list.html' template_name = 'perms/asset_permission_list.html'
paginate_by = settings.DISPLAY_PER_PAGE
user = user_group = asset = node = system_user = q = ""
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
@ -28,18 +30,24 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView):
class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView): class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
model = NodePermission model = AssetPermission
form_class = AssetPermissionForm form_class = AssetPermissionForm
template_name = 'perms/asset_permission_create_update.html' template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy('perms:asset-permission-list') success_url = reverse_lazy('perms:asset-permission-list')
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class=form_class) form = super().get_form(form_class=form_class)
node_id = self.request.GET.get("node_id") nodes_id = self.request.GET.get("nodes")
node = get_object_or_none(Node, id=node_id) assets_id = self.request.GET.get("assets")
if not node:
node = Node.root() if nodes_id:
form['node'].initial = node nodes_id = nodes_id.split(",")
nodes = Node.objects.filter(id__in=nodes_id)
form['nodes'].initial = nodes
if assets_id:
assets_id = assets_id.split(",")
assets = Asset.objects.filter(id__in=assets_id)
form['assets'].initial = assets
return form return form
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -52,16 +60,11 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView): class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
model = NodePermission model = AssetPermission
form_class = AssetPermissionForm form_class = AssetPermissionForm
template_name = 'perms/asset_permission_create_update.html' template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy("perms:asset-permission-list") success_url = reverse_lazy("perms:asset-permission-list")
def get_form(self, form_class=None):
form = super().get_form(form_class=form_class)
form['node'].initial = form.instance.node
return form
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
@ -71,9 +74,84 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
model = AssetPermission
form_class = AssetPermissionForm
template_name = 'perms/asset_permission_detail.html'
success_url = reverse_lazy("perms:asset-permission-list")
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Update asset permission'),
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView): class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView):
model = AssetPermission model = AssetPermission
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('perms:asset-permission-list') success_url = reverse_lazy('perms:asset-permission-list')
class AssetPermissionUserView(AdminUserRequiredMixin,
SingleObjectMixin,
ListView):
template_name = 'perms/asset_permission_user.html'
context_object_name = 'asset_permission'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetPermission.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
queryset = self.object.get_all_users()
return queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Asset permission user list'),
'users_remain': User.objects.exclude(asset_permissions=self.object)
.exclude(role=User.ROLE_APP),
'user_groups_remain': UserGroup.objects.exclude(
asset_permissions=self.object
)
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AssetPermissionAssetView(AdminUserRequiredMixin,
SingleObjectMixin,
ListView):
template_name = 'perms/asset_permission_asset.html'
context_object_name = 'asset_permission'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetPermission.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
queryset = self.object.get_all_assets()
return queryset
def get_context_data(self, **kwargs):
assets_granted = self.get_queryset()
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),
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -212,49 +212,49 @@ website: http://code.google.com/p/jquerytree/
height: 20px; height: 20px;
} }
.ztree li span.button.root_open::before { .ztree li span.button.root_open::before {
content: "\f078"; content: "\f107";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
} }
.ztree li span.button.root_close::before { .ztree li span.button.root_close::before {
content: "\f054"; content: "\f105";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
} }
.ztree li span.button.roots_open::before { .ztree li span.button.roots_open::before {
content: "\f078"; content: "\f107";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
} }
.ztree li span.button.roots_close::before { .ztree li span.button.roots_close::before {
content: "\f054"; content: "\f105";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
} }
.ztree li span.button.center_open::before { .ztree li span.button.center_open::before {
content: "\f078"; content: "\f107";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
} }
.ztree li span.button.center_close::before { .ztree li span.button.center_close::before {
content: "\f054"; content: "\f105";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
} }
.ztree li span.button.bottom_open::before { .ztree li span.button.bottom_open::before {
content: "\f078"; content: "\f107";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
} }
.ztree li span.button.bottom_close::before { .ztree li span.button.bottom_close::before {
content: "\f054"; content: "\f105";
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;
display: inline-block; display: inline-block;
@ -300,7 +300,31 @@ website: http://code.google.com/p/jquerytree/
color: #676a6c; color: #676a6c;
} }
.ztree li span.button.ico_docu::before { .ztree li span.button.ico_docu::before {
content: "\f114"; content: "\f07b";
font-family: FontAwesome;
padding-top: 10px;
padding-left: 2px;
display: inline-block;
color: #676a6c;
}
.ztree li span.button.file_ico_docu::before {
content: "\f022";
font-family: FontAwesome;
padding-top: 10px;
padding-left: 2px;
display: inline-block;
color: #676a6c;
}
.ztree li span.button.linux_ico_docu::before {
content: "\f17c";
font-family: FontAwesome;
padding-top: 10px;
padding-left: 2px;
display: inline-block;
color: #676a6c;
}
.ztree li span.button.windows_ico_docu::before {
content: "\f17a";
font-family: FontAwesome; font-family: FontAwesome;
padding-top: 10px; padding-top: 10px;
padding-left: 2px; padding-left: 2px;

View File

@ -39,11 +39,11 @@ website: http://code.google.com/p/jquerytree/
margin:0; padding:5px; color:@color-normal; background-color: @color-bg; margin:0; padding:5px; color:@color-normal; background-color: @color-bg;
li { li {
padding:0; margin:0; list-style:none; line-height:17px; text-align:left; white-space:nowrap; outline:0; padding:0; margin:0; list-style:none; line-height:17px; text-align:left; white-space:nowrap; outline:0;
ul { ul {
margin: 0px; padding:0 0 0 18px; margin: 0px; padding:0 0 0 18px;
} }
ul.line { } ul.line { }
a {padding-right:3px; margin:0; cursor:pointer; height:@h; color:@color-normal; background-color: transparent; a {padding-right:3px; margin:0; cursor:pointer; height:@h; color:@color-normal; background-color: transparent;
text-decoration:none; vertical-align:top; display: inline-block; text-decoration:none; vertical-align:top; display: inline-block;
input.rename {height:14px; width:80px; padding:0; margin:0; input.rename {height:14px; width:80px; padding:0; margin:0;
color: @color-bg; background-color: @color-normal; color: @color-bg; background-color: @color-normal;
@ -64,11 +64,11 @@ website: http://code.google.com/p/jquerytree/
span.button {line-height:0; margin:0; padding: 0; width:@w; height:@h; display: inline-block; vertical-align:top; span.button {line-height:0; margin:0; padding: 0; width:@w; height:@h; display: inline-block; vertical-align:top;
border:0px solid; cursor: pointer;outline:none; border:0px solid; cursor: pointer;outline:none;
background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; background-color:transparent; background-repeat:no-repeat; background-attachment: scroll;
&::before{color: @color-normal; font-family: FontAwesome; padding-top:@pad-top;} &::before{color: @color-normal; font-family: FontAwesome; padding-top:@pad-top;}
&.chk { margin:0px; cursor: auto; width: 12px; &.chk { margin:0px; cursor: auto; width: 12px;
display: inline-block;padding-top:@pad-top;padding-left:@pad-left; display: inline-block;padding-top:@pad-top;padding-left:@pad-left;
&.checkbox_false_full::before {content: @fa-square-o;} &.checkbox_false_full::before {content: @fa-square-o;}
&.checkbox_false_full_focus::before {content: @fa-square-o; color:@color-highlight;} &.checkbox_false_full_focus::before {content: @fa-square-o; color:@color-highlight;}
&.checkbox_false_part::before {content: @fa-square-o;color: @color-partial;} &.checkbox_false_part::before {content: @fa-square-o;color: @color-partial;}
@ -82,7 +82,7 @@ website: http://code.google.com/p/jquerytree/
&.checkbox_true_part::before {content: @fa-check-square-o;color: @color-partial} &.checkbox_true_part::before {content: @fa-check-square-o;color: @color-partial}
&.checkbox_true_part_focus::before {content: @fa-check-square-o;color: @color-partfocus;} &.checkbox_true_part_focus::before {content: @fa-check-square-o;color: @color-partfocus;}
&.checkbox_true_disable::before {content: @fa-check-square-o;color: @color-disabled} &.checkbox_true_disable::before {content: @fa-check-square-o;color: @color-disabled}
&.radio_false_full::before {content: @fa-circle-o;} &.radio_false_full::before {content: @fa-circle-o;}
&.radio_false_full_focus::before {content: @fa-circle-o;color: @color-highlight} &.radio_false_full_focus::before {content: @fa-circle-o;color: @color-highlight}
&.radio_false_part::before {content: @fa-circle-o;color: @color-partial} &.radio_false_part::before {content: @fa-circle-o;color: @color-partial}
@ -93,17 +93,17 @@ website: http://code.google.com/p/jquerytree/
&.radio_true_part::before {content: @fa-dot-circle-o;color: @color-partial} &.radio_true_part::before {content: @fa-dot-circle-o;color: @color-partial}
&.radio_true_part_focus::before {content: @fa-dot-circle-o;color: @color-partial;} &.radio_true_part_focus::before {content: @fa-dot-circle-o;color: @color-partial;}
&.radio_true_disable::before {content: @fa-circle-thin;color: @color-disabled} &.radio_true_disable::before {content: @fa-circle-thin;color: @color-disabled}
} }
&.switch {width:@w; height:@h} &.switch {width:@w; height:@h}
&.root_open::before{content: @fa-chevron-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.root_open::before{content: @fa-angle-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.root_close::before{content: @fa-chevron-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.root_close::before{content: @fa-angle-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.roots_open::before{content: @fa-chevron-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.roots_open::before{content: @fa-angle-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.roots_close::before{content: @fa-chevron-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.roots_close::before{content: @fa-angle-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.center_open::before{content: @fa-chevron-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.center_open::before{content: @fa-angle-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.center_close::before{content: @fa-chevron-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.center_close::before{content: @fa-angle-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.bottom_open::before{content: @fa-chevron-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.bottom_open::before{content: @fa-angle-down;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.bottom_close::before{content: @fa-chevron-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;} &.bottom_close::before{content: @fa-angle-right;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;}
&.noline_open{} &.noline_open{}
&.noline_close{} &.noline_close{}
&.root_docu{ background:none;} &.root_docu{ background:none;}
@ -111,11 +111,15 @@ website: http://code.google.com/p/jquerytree/
&.center_docu::before{padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;} &.center_docu::before{padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.bottom_docu::before{padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;} &.bottom_docu::before{padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.noline_docu{ background:none;} &.noline_docu{ background:none;}
&.ico_open::before {content: @fa-folder-open;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;} &.ico_open::before {content: @fa-folder-open;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.ico_close::before {content: @fa-folder;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;} &.ico_close::before {content: @fa-folder;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.ico_docu::before{content: @fa-folder-o;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;} &.ico_docu::before{content: @fa-folder;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.file_ico_docu::before{content: @fa-list-alt;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.linux_ico_docu::before{content: @fa-linux;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.windows_ico_docu::before{content: @fa-windows;font-family: FontAwesome;padding-top:@pad-top;padding-left:@pad-left;display: inline-block;color:@color-normal;}
&.edit {margin-left:4px; margin-right: -1px; vertical-align:top; *vertical-align:middle;padding-top:@pad-top;} &.edit {margin-left:4px; margin-right: -1px; vertical-align:top; *vertical-align:middle;padding-top:@pad-top;}
&.edit::before{content: @fa-pencil-square-o;font-family: FontAwesome;} &.edit::before{content: @fa-pencil-square-o;font-family: FontAwesome;}

View File

@ -62,7 +62,6 @@ function GetTableDataBox() {
} }
} }
for (i in id_list) { for (i in id_list) {
console.log(tabProduct);
tableData.push(GetRowData(tabProduct.rows[id_list[i]])); tableData.push(GetRowData(tabProduct.rows[id_list[i]]));
} }
@ -240,6 +239,13 @@ $.fn.serializeObject = function()
}); });
return o; return o;
}; };
function makeLabel(data) {
return "<label class='detail-key'><b>" + data[0] + ": </b></label>" + data[1] + "</br>"
}
var jumpserver = {}; var jumpserver = {};
jumpserver.checked = false; jumpserver.checked = false;
jumpserver.selected = {}; jumpserver.selected = {};
@ -281,7 +287,7 @@ jumpserver.initDataTable = function (options) {
buttons: [], buttons: [],
columnDefs: columnDefs, columnDefs: columnDefs,
ajax: { ajax: {
url: options.ajax_url , url: options.ajax_url,
dataSrc: "" dataSrc: ""
}, },
columns: options.columns || [], columns: options.columns || [],

View File

@ -18,7 +18,7 @@
{% endblock %} {% endblock %}
{% block table_search %} {% block table_search %}
<form id="search_form" method="get" action="" class="pull-right form-inline"> <form id="search_form" method="get" action="" class="pull-right form-inline" style="padding-bottom: 8px">
<div class="form-group" id="date"> <div class="form-group" id="date">
<div class="input-daterange input-group" id="datepicker"> <div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span> <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
@ -64,7 +64,7 @@
</form> </form>
{% endblock %} {% endblock %}
{% block table_container %} {% block table_container %}
<table class="footable table table-stripped toggle-arrow-tiny" data-page="false"> <table class="footable table table-stripped table-bordered toggle-arrow-tiny" data-page="false" >
<thead> <thead>
<tr> <tr>
<th data-toggle="true">ID</th> <th data-toggle="true">ID</th>

View File

@ -26,11 +26,15 @@ logger = get_logger(__name__)
class UserViewSet(IDInFilterMixin, BulkModelViewSet): class UserViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App") queryset = User.objects.exclude(role="App")
# queryset = User.objects.all().exclude(role="App").order_by("date_joined")
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = (IsSuperUserOrAppUser, IsAuthenticated) permission_classes = (IsSuperUser,)
filter_fields = ('username', 'email', 'name', 'id') filter_fields = ('username', 'email', 'name', 'id')
def get_permissions(self):
if self.action == "retrieve":
self.permission_classes = (IsSuperUserOrAppUser,)
return super().get_permissions()
class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView): class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
@ -57,7 +61,6 @@ class UserResetPasswordApi(generics.UpdateAPIView):
def perform_update(self, serializer): def perform_update(self, serializer):
# Note: we are not updating the user object here. # Note: we are not updating the user object here.
# We just do the reset-password stuff. # We just do the reset-password stuff.
import uuid
from .utils import send_reset_password_mail from .utils import send_reset_password_mail
user = self.get_object() user = self.get_object()
user.password_raw = str(uuid.uuid4()) user.password_raw = str(uuid.uuid4())
@ -68,6 +71,7 @@ class UserResetPasswordApi(generics.UpdateAPIView):
class UserResetPKApi(generics.UpdateAPIView): class UserResetPKApi(generics.UpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def perform_update(self, serializer): def perform_update(self, serializer):
from .utils import send_reset_ssh_key_mail from .utils import send_reset_ssh_key_mail
@ -91,6 +95,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all() queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer serializer_class = UserGroupSerializer
permission_classes = (IsSuperUser,)
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):

View File

@ -6,7 +6,6 @@ from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from perms.models import AssetPermission
from .models import User, UserGroup from .models import User, UserGroup
@ -253,30 +252,30 @@ class UserGroupForm(forms.ModelForm):
} }
class UserGroupPrivateAssetPermissionForm(forms.ModelForm): # class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
def save(self, commit=True): # def save(self, commit=True):
self.instance = super(UserGroupPrivateAssetPermissionForm, self)\ # self.instance = super(UserGroupPrivateAssetPermissionForm, self)\
.save(commit=commit) # .save(commit=commit)
self.instance.user_groups = [self.user_group] # self.instance.user_groups = [self.user_group]
self.instance.save() # self.instance.save()
return self.instance # return self.instance
#
class Meta: # class Meta:
model = AssetPermission # model = AssetPermission
fields = [ # fields = [
'assets', 'asset_groups', 'system_users', 'name', # 'assets', 'asset_groups', 'system_users', 'name',
] # ]
widgets = { # widgets = {
'assets': forms.SelectMultiple( # 'assets': forms.SelectMultiple(
attrs={'class': 'select2', # attrs={'class': 'select2',
'data-placeholder': _('Select assets')}), # 'data-placeholder': _('Select assets')}),
'asset_groups': forms.SelectMultiple( # 'asset_groups': forms.SelectMultiple(
attrs={'class': 'select2', # attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}), # 'data-placeholder': _('Select asset groups')}),
'system_users': forms.SelectMultiple( # 'system_users': forms.SelectMultiple(
attrs={'class': 'select2', # attrs={'class': 'select2',
'data-placeholder': _('Select system users')}), # 'data-placeholder': _('Select system users')}),
} # }
class FileForm(forms.Form): class FileForm(forms.Form):

View File

@ -30,6 +30,11 @@ class User(AbstractUser):
(ROLE_USER, _('User')), (ROLE_USER, _('User')),
(ROLE_APP, _('Application')) (ROLE_APP, _('Application'))
) )
OTP_LEVEL_CHOICES = (
(0, _('Disable')),
(1, _('Enable')),
(2, _("Force enable")),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(max_length=128, unique=True, verbose_name=_('Username')) username = models.CharField(max_length=128, unique=True, verbose_name=_('Username'))
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
@ -39,8 +44,8 @@ class User(AbstractUser):
avatar = models.ImageField(upload_to="avatar", null=True, verbose_name=_('Avatar')) avatar = models.ImageField(upload_to="avatar", null=True, verbose_name=_('Avatar'))
wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat')) wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat'))
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone')) phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP')) otp_level = models.SmallIntegerField(default=0, choices=OTP_LEVEL_CHOICES, verbose_name=_('Enable OTP'))
secret_key_otp = models.CharField(max_length=16, blank=True) otp_secret_key = models.CharField(max_length=16, blank=True)
# Todo: Auto generate key, let user download # Todo: Auto generate key, let user download
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Private key')) _private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Private key'))
_public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Public key')) _public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Public key'))
@ -50,7 +55,7 @@ class User(AbstractUser):
created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by')) created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by'))
def __str__(self): def __str__(self):
return self.username return '{0.name}({0.username})'.format(self)
@property @property
def password_raw(self): def password_raw(self):
@ -202,6 +207,20 @@ class User(AbstractUser):
def generate_reset_token(self): def generate_reset_token(self):
return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600) return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600)
@property
def otp_enabled(self):
return self.otp_level > 0
def enabled_otp(self):
self.otp_level = 1
def force_enable_otp(self):
self.otp_level = 2
@property
def otp_force_enabled(self):
return self.otp_level == 2
def to_json(self): def to_json(self):
return OrderedDict({ return OrderedDict({
'id': self.id, 'id': self.id,
@ -222,7 +241,7 @@ class User(AbstractUser):
def create_app_user(cls, name, comment): def create_app_user(cls, name, comment):
app = cls.objects.create( app = cls.objects.create(
username=name, name=name, email='{}@local.domain'.format(name), username=name, name=name, email='{}@local.domain'.format(name),
is_active=False, role='App', enable_otp=False, comment=comment, is_active=False, role='App', comment=comment,
is_first_login=False, created_by='System' is_first_login=False, created_by='System'
) )
access_key = app.create_access_key() access_key = app.create_access_key()

View File

@ -2,19 +2,29 @@
# #
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save # from django.db.models.signals import post_save
from common.utils import get_logger from common.utils import get_logger
from .models import User from .signals import post_user_create
# from .models import User
logger = get_logger(__file__) logger = get_logger(__file__)
@receiver(post_save, sender=User) # @receiver(post_save, sender=User)
def on_user_created(sender, instance=None, created=False, **kwargs): # def on_user_created(sender, instance=None, created=False, **kwargs):
if created: # if created:
logger.debug("Receive user `{}` create signal".format(instance.name)) # logger.debug("Receive user `{}` create signal".format(instance.name))
from .utils import send_user_created_mail # from .utils import send_user_created_mail
logger.info(" - Sending welcome mail ...".format(instance.name)) # logger.info(" - Sending welcome mail ...".format(instance.name))
if instance.email: # if instance.email:
send_user_created_mail(instance) # send_user_created_mail(instance)
@receiver(post_user_create)
def on_user_create(sender, user=None, **kwargs):
logger.debug("Receive user `{}` create signal".format(user.name))
from .utils import send_user_created_mail
logger.info(" - Sending welcome mail ...".format(user.name))
if user.email:
send_user_created_mail(user)

View File

@ -44,7 +44,7 @@
<th class="text-center">{% trans 'Hostname' %}</th> <th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th> <th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Active' %}</th> <th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th> <th class="text-center">{% trans 'System users' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -63,6 +63,8 @@
<script> <script>
var zTree; var zTree;
var inited = false; var inited = false;
var url;
var asset_table;
function initTable() { function initTable() {
if (inited){ if (inited){
@ -86,31 +88,28 @@ function initTable() {
} }
}}, }},
{targets: 4, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
if (cellData === 'Unknown'){ var users = [];
$(td).html('<i class="fa fa-circle text-warning"></i>') $.each(cellData, function (id, data) {
} else if (!cellData) { users.push(data.name);
$(td).html('<i class="fa fa-circle text-danger"></i>') });
} else { $(td).html(users.join(', '))
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}} }}
], ],
ajax_url: '{% url "api-assets:asset-list" %}', ajax_url: url,
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false }, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false} {data: "system_users_granted", orderable: false}
] ]
}; };
asset_table = jumpserver.initServerSideDataTable(options); return jumpserver.initDataTable(options);
return asset_table
} }
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
initTable(); url = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}';
var url = asset_table.ajax.url(); url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
url = setUrlParam(url, "node_id", treeNode.id);
setCookie('node_selected', treeNode.id); setCookie('node_selected', treeNode.id);
asset_table = initTable();
asset_table.ajax.url(url); asset_table.ajax.url(url);
asset_table.ajax.reload(); asset_table.ajax.reload();
} }

View File

@ -63,6 +63,7 @@
<script> <script>
var zTree; var zTree;
var inited = false; var inited = false;
var url;
function initTable() { function initTable() {
if (inited){ if (inited){
@ -86,31 +87,29 @@ function initTable() {
} }
}}, }},
{targets: 4, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
if (cellData === 'Unknown'){ var users = [];
$(td).html('<i class="fa fa-circle text-warning"></i>') $.each(cellData, function (id, data) {
} else if (!cellData) { users.push(data.name);
$(td).html('<i class="fa fa-circle text-danger"></i>') });
} else { $(td).html(users.join(', '))
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}} }}
], ],
ajax_url: '{% url "api-assets:asset-list" %}', ajax_url: url,
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false }, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false} {data: "system_users_granted", orderable: false}
] ]
}; };
asset_table = jumpserver.initServerSideDataTable(options); asset_table = jumpserver.initDataTable(options);
return asset_table return asset_table
} }
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
initTable(); url = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}';
var url = asset_table.ajax.url(); url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
url = setUrlParam(url, "node_id", treeNode.id);
setCookie('node_selected', treeNode.id); setCookie('node_selected', treeNode.id);
asset_table = initTable();
asset_table.ajax.url(url); asset_table.ajax.url(url);
asset_table.ajax.reload(); asset_table.ajax.reload();
} }

View File

@ -65,7 +65,7 @@
</tr> </tr>
<tr> <tr>
<td class="text-navy">{% trans 'OTP' %}</td> <td class="text-navy">{% trans 'OTP' %}</td>
<td>{{ user.enable_otp|yesno:"Yes,No,Unkown" }}</td> <td>{{ user.otp_enabled|yesno:"Yes,No,Unkown" }}</td>
</tr> </tr>
<tr> <tr>
<td class="text-navy">{% trans 'Public key' %}</td> <td class="text-navy">{% trans 'Public key' %}</td>

View File

@ -2,7 +2,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
from django import forms
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth import login as auth_login, logout as auth_logout from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -20,10 +19,9 @@ from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from formtools.wizard.views import SessionWizardView from formtools.wizard.views import SessionWizardView
from django.conf import settings from django.conf import settings
from django.utils import timezone
from common.utils import get_object_or_none from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from ..models import User, LoginLog from ..models import User, LoginLog
from ..utils import send_reset_password_mail from ..utils import send_reset_password_mail
from ..tasks import write_login_log_async from ..tasks import write_login_log_async
@ -194,8 +192,6 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
for field in form: for field in form:
if field.value(): if field.value():
setattr(user, field.name, field.value()) setattr(user, field.name, field.value())
if field.name == 'enable_otp':
user.enable_otp = field.value()
user.is_first_login = False user.is_first_login = False
user.is_public_key_valid = True user.is_public_key_valid = True
user.save() user.save()
@ -228,7 +224,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
return form return form
class LoginLogListView(DatetimeSearchMixin, ListView): class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
template_name = 'users/login_log_list.html' template_name = 'users/login_log_list.html'
model = LoginLog model = LoginLog
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE

View File

@ -79,6 +79,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
user = form.save(commit=False) user = form.save(commit=False)
user.created_by = self.request.user.username or 'System' user.created_by = self.request.user.username or 'System'
user.save() user.save()
post_user_create.send(self.__class__, user=user)
return super().form_valid(form) return super().form_valid(form)

View File

@ -0,0 +1,24 @@
#!/bin/bash
#
python ../apps/manage.py shell << EOF
from perms.models import *
for old in NodePermission.objects.all():
perm = asset_perm_model.objects.using(db_alias).create(
name="{}-{}-{}".format(
old.node.value,
old.user_group.name,
old.system_user.name
),
is_active=old.is_active,
date_expired=old.date_expired,
created_by=old.date_expired,
date_created=old.date_created,
comment=old.comment,
)
perm.user_groups.add(old.user_group)
perm.nodes.add(old.node)
perm.system_users.add(old.system_user)
EOF