[Feature] 添加功能 RemoteApp (#2706)

* [Feature] RemoteApp添加Model

* [Feature] RemoteApp添加ViewSet API

* [Feature] RemoteApp添加获取connection-info API

* [Feature] Perms模块修改目录结构

* [Feature] RemoteAppPermission添加Model

* [Feature] RemoteAppPermission添加ViewSet API

* [Feature] RemoteAppPermission添加用户/用户组获取被授权的RemoteApp API

* [Feature] RemoteAppPermission添加校验用户对RemoteApp的权限 API

* [Feature] RemoteAppPermission添加获取用户授权的RemoteApp树 API

* [Feature] RemoteAppPermission添加<添加/移除>所授权的<用户/RemoteApp> API

* [Feature] RemoteApp添加创建、更新、详情、删除、用户RemoteApp等页面

* [Feature] RemoteAppPermission添加创建、更新、详情、删除、授权用户、授权RemoteApp等页面

* [Feature] RemoteApp从assets模块迁移到新添加的applications模块

* [Feature] RemoteApp/RemoteAppPermission添加迁移文件

* [Feature] RemoteApp/RemoteAppPermission修改小细节

* [Feature] RemoteApp/RemoteAppPermission修改小细节2

* [Feature] RemoteApp/RemoteAppPermission修改小细节3

* [Feature] RemoteApp更新迁移文件

* [Feature] RemoteApp/RemoteAppPermission添加翻译信息

* [Feature] RemoteApp/RemoteAppPermission删除迁移文件

* [Feature] RemoteApp/RemoteAppPermission添加迁移文件

* [Feature] RemoteApp/RemoteAppPermission修改代码风格
pull/2709/head
BaiJiangJie 6 years ago committed by 老广
parent 3855fecc69
commit 1eca517978

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1 @@
from .remote_app import *

@ -0,0 +1,31 @@
# coding: utf-8
#
from rest_framework import generics
from rest_framework.pagination import LimitOffsetPagination
from rest_framework_bulk import BulkModelViewSet
from ..hands import IsOrgAdmin, IsAppUser
from ..models import RemoteApp
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
__all__ = [
'RemoteAppViewSet', 'RemoteAppConnectionInfoApi',
]
class RemoteAppViewSet(BulkModelViewSet):
filter_fields = ('name',)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
queryset = RemoteApp.objects.all()
serializer_class = RemoteAppSerializer
pagination_class = LimitOffsetPagination
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
queryset = RemoteApp.objects.all()
permission_classes = (IsAppUser, )
serializer_class = RemoteAppConnectionInfoSerializer

@ -0,0 +1,7 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class ApplicationsConfig(AppConfig):
name = 'applications'

@ -0,0 +1,68 @@
# coding: utf-8
#
from django.utils.translation import ugettext_lazy as _
# RemoteApp
REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor'
REMOTE_APP_TYPE_CHROME = 'chrome'
REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench'
REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client'
REMOTE_APP_TYPE_CUSTOM = 'custom'
REMOTE_APP_TYPE_CHOICES = (
(
_('Browser'),
(
(REMOTE_APP_TYPE_CHROME, 'Chrome'),
)
),
(
_('Database tools'),
(
(REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
)
),
(
_('Virtualization tools'),
(
(REMOTE_APP_TYPE_VMWARE_CLIENT, 'VMware Client'),
)
),
(REMOTE_APP_TYPE_CUSTOM, _('Custom')),
)
# Fields attribute write_only default => False
REMOTE_APP_TYPE_CHROME_FIELDS = [
{'name': 'chrome_target'},
{'name': 'chrome_username'},
{'name': 'chrome_password', 'write_only': True}
]
REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [
{'name': 'mysql_workbench_ip'},
{'name': 'mysql_workbench_name'},
{'name': 'mysql_workbench_username'},
{'name': 'mysql_workbench_password', 'write_only': True}
]
REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [
{'name': 'vmware_target'},
{'name': 'vmware_username'},
{'name': 'vmware_password', 'write_only': True}
]
REMOTE_APP_TYPE_CUSTOM_FIELDS = [
{'name': 'custom_cmdline'},
{'name': 'custom_target'},
{'name': 'custom_username'},
{'name': 'custom_password', 'write_only': True}
]
REMOTE_APP_TYPE_MAP_FIELDS = {
REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS,
REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS,
REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS,
REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS
}

@ -0,0 +1 @@
from .remote_app import *

@ -0,0 +1,111 @@
# coding: utf-8
#
from django.utils.translation import ugettext as _
from django import forms
from orgs.mixins import OrgModelForm
from assets.models import Asset, SystemUser
from ..models import RemoteApp
__all__ = [
'RemoteAppCreateUpdateForm',
]
class RemoteAppTypeChromeForm(forms.ModelForm):
chrome_target = forms.CharField(
max_length=128, label=_('Target URL'), required=False
)
chrome_username = forms.CharField(
max_length=128, label=_('Login username'), required=False
)
chrome_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Login password'), required=False
)
class RemoteAppTypeMySQLWorkbenchForm(forms.ModelForm):
mysql_workbench_ip = forms.CharField(
max_length=128, label=_('Database IP'), required=False
)
mysql_workbench_name = forms.CharField(
max_length=128, label=_('Database name'), required=False
)
mysql_workbench_username = forms.CharField(
max_length=128, label=_('Database username'), required=False
)
mysql_workbench_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Database password'), required=False
)
class RemoteAppTypeVMwareForm(forms.ModelForm):
vmware_target = forms.CharField(
max_length=128, label=_('Target address'), required=False
)
vmware_username = forms.CharField(
max_length=128, label=_('Login username'), required=False
)
vmware_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Login password'), required=False
)
class RemoteAppTypeCustomForm(forms.ModelForm):
custom_cmdline = forms.CharField(
max_length=128, label=_('Operating parameter'), required=False
)
custom_target = forms.CharField(
max_length=128, label=_('Target address'), required=False
)
custom_username = forms.CharField(
max_length=128, label=_('Login username'), required=False
)
custom_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Login password'), required=False
)
class RemoteAppTypeForms(
RemoteAppTypeChromeForm,
RemoteAppTypeMySQLWorkbenchForm,
RemoteAppTypeVMwareForm,
RemoteAppTypeCustomForm
):
pass
class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
def __init__(self, *args, **kwargs):
# 过滤RDP资产和系统用户
super().__init__(*args, **kwargs)
field_asset = self.fields['asset']
field_asset.queryset = field_asset.queryset.filter(
protocol=Asset.PROTOCOL_RDP
)
field_system_user = self.fields['system_user']
field_system_user.queryset = field_system_user.queryset.filter(
protocol=SystemUser.PROTOCOL_RDP
)
class Meta:
model = RemoteApp
fields = [
'name', 'asset', 'system_user', 'type', 'path', 'comment'
]
widgets = {
'asset': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Asset')
}),
'system_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('System user')
})
}

@ -0,0 +1,16 @@
"""
jumpserver.__app__.hands.py
~~~~~~~~~~~~~~~~~
This app depends other apps api, function .. should be import or write mack here.
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2018 by Jumpserver Team.
:license: GPL v2, see LICENSE for more details.
"""
from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup

@ -0,0 +1,42 @@
# Generated by Django 2.1.7 on 2019-05-20 11:04
import common.fields.model
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('assets', '0026_auto_20190325_2035'),
]
operations = [
migrations.CreateModel(
name='RemoteApp',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'VMware Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')),
('path', models.CharField(max_length=128, verbose_name='App path')),
('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')),
],
options={
'verbose_name': 'RemoteApp',
'ordering': ('name',),
},
),
migrations.AlterUniqueTogether(
name='remoteapp',
unique_together={('org_id', 'name')},
),
]

@ -0,0 +1 @@
from .remote_app import *

@ -0,0 +1,89 @@
# coding: utf-8
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
from common.fields.model import EncryptJsonDictTextField
from .. import const
__all__ = [
'RemoteApp',
]
class RemoteApp(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
asset = models.ForeignKey(
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
)
system_user = models.ForeignKey(
'assets.SystemUser', on_delete=models.CASCADE,
verbose_name=_('System user')
)
type = models.CharField(
default=const.REMOTE_APP_TYPE_CHROME,
choices=const.REMOTE_APP_TYPE_CHOICES,
max_length=128, verbose_name=_('App type')
)
path = models.CharField(
max_length=128, blank=False, null=False,
verbose_name=_('App path')
)
params = EncryptJsonDictTextField(
max_length=4096, default={}, blank=True, null=True,
verbose_name=_('Parameters')
)
created_by = models.CharField(
max_length=32, null=True, blank=True, verbose_name=_('Created by')
)
date_created = models.DateTimeField(
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
)
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
class Meta:
verbose_name = _("RemoteApp")
unique_together = [('org_id', 'name')]
ordering = ('name', )
def __str__(self):
return self.name
@property
def parameters(self):
"""
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
"""
_parameters = list()
_parameters.append(self.type)
path = '\"%s\"' % self.path
_parameters.append(path)
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[self.type]:
value = self.params.get(field['name'])
if value is None:
continue
_parameters.append(value)
_parameters = ' '.join(_parameters)
return _parameters
@property
def asset_info(self):
return {
'id': self.asset.id,
'hostname': self.asset.hostname
}
@property
def system_user_info(self):
return {
'id': self.system_user.id,
'name': self.system_user.name
}

@ -0,0 +1 @@
from .remote_app import *

@ -0,0 +1,103 @@
# coding: utf-8
#
from rest_framework import serializers
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from .. import const
from ..models import RemoteApp
__all__ = [
'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
]
class RemoteAppParamsDictField(serializers.DictField):
"""
RemoteApp field => params
"""
@staticmethod
def filter_attribute(attribute, instance):
"""
过滤掉params字段值中write_only特性的key-value值
For example, the chrome_password field is not returned when serializing
{
'chrome_target': 'http://www.jumpserver.org/',
'chrome_username': 'admin',
'chrome_password': 'admin',
}
"""
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]:
if field.get('write_only', False):
attribute.pop(field['name'], None)
return attribute
def get_attribute(self, instance):
"""
序列化时调用
"""
attribute = super().get_attribute(instance)
attribute = self.filter_attribute(attribute, instance)
return attribute
@staticmethod
def filter_value(dictionary, value):
"""
过滤掉不属于当前app_type所包含的key-value值
"""
app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME)
fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type]
fields_names = [field['name'] for field in fields]
no_need_keys = [k for k in value.keys() if k not in fields_names]
for k in no_need_keys:
value.pop(k)
return value
def get_value(self, dictionary):
"""
反序列化时调用
"""
value = super().get_value(dictionary)
value = self.filter_value(dictionary, value)
return value
class RemoteAppSerializer(BulkSerializerMixin, serializers.ModelSerializer):
params = RemoteAppParamsDictField()
class Meta:
model = RemoteApp
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'asset', 'system_user', 'type', 'path', 'params',
'comment', 'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display',
]
read_only_fields = [
'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display'
]
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
parameter_remote_app = serializers.SerializerMethodField()
class Meta:
model = RemoteApp
fields = [
'id', 'name', 'asset', 'system_user', 'parameter_remote_app',
]
read_only_fields = ['parameter_remote_app']
@staticmethod
def get_parameter_remote_app(obj):
parameter = {
'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
'working_directory': '',
'parameters': obj.parameters,
}
return parameter

@ -0,0 +1,91 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="appForm" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.asset layout="horizontal" %}
{% bootstrap_field form.system_user layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.path layout="horizontal" %}
<div class="hr-line-dashed"></div>
{# chrome #}
<div class="chrome-fields">
{% bootstrap_field form.chrome_target layout="horizontal" %}
{% bootstrap_field form.chrome_username layout="horizontal" %}
{% bootstrap_field form.chrome_password layout="horizontal" %}
</div>
{# mysql workbench #}
<div class="mysql_workbench-fields">
{% bootstrap_field form.mysql_workbench_ip layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_name layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_username layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_password layout="horizontal" %}
</div>
{# vmware #}
<div class="vmware_client-fields">
{% bootstrap_field form.vmware_target layout="horizontal" %}
{% bootstrap_field form.vmware_username layout="horizontal" %}
{% bootstrap_field form.vmware_password layout="horizontal" %}
</div>
{# custom #}
<div class="custom-fields">
{% bootstrap_field form.custom_cmdline layout="horizontal" %}
{% bootstrap_field form.custom_target layout="horizontal" %}
{% bootstrap_field form.custom_username layout="horizontal" %}
{% bootstrap_field form.custom_password layout="horizontal" %}
</div>
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
var type_id = '#' + '{{ form.type.id_for_label }}';
var all_type_fields = [
'.chrome-fields',
'.mysql_workbench-fields',
'.vmware_client-fields',
'.custom-fields'
];
function appTypeChange(){
$.each(all_type_fields, function(index, value){
$(value).addClass('hidden')
});
var type = $(type_id+ " option:selected").val();
$('.' + type + '-fields').removeClass('hidden');
}
$(document).ready(function () {
$('.select2').select2({
closeOnSelect: true
});
appTypeChange()
})
.on('change', type_id, function(){
appTypeChange();
});
</script>
{% endblock %}

@ -0,0 +1,109 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'applications:remote-app-detail' pk=remote_app.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'applications:remote-app-update' pk=remote_app.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-application">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ remote_app.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ remote_app.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset' %}:</td>
<td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td>
</tr>
<tr>
<td>{% trans 'System user' %}:</td>
<td><b><a href="{% url 'assets:system-user-detail' pk=remote_app.system_user.id %}">{{ remote_app.system_user.name }}</a></b></td>
</tr>
<tr>
<td>{% trans 'App type' %}:</td>
<td><b>{{ remote_app.get_type_display }}</b></td>
</tr>
<tr>
<td>{% trans 'App path' %}:</td>
<td><b>{{ remote_app.path }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ remote_app.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ remote_app.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ remote_app.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.nodes_selected = {};
$(document).ready(function () {
})
.on('click', '.btn-delete-application', function () {
var $this = $(this);
var name = "{{ remote_app.name }}";
var rid = "{{ remote_app.id }}";
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
var redirect_url = "{% url 'applications:remote-app-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
</script>
{% endblock %}

@ -0,0 +1,90 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block help_message %}
<div class="alert alert-info help-message">
{% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %}
<b><a href="#">{% trans 'Download application loader' %}</a></b>
</div>
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'applications:remote-app-create' %}" class="btn btn-sm btn-primary"> {% trans "Create RemoteApp" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#remote_app_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'applications:remote-app-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var hostname = htmlEscape(cellData.hostname);
var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name);
var detail_btn = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + name + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "applications:remote-app-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-applications:remote-app-list" %}',
columns: [
{data: "id"},
{data: "name" },
{data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment"},
{data: "id", orderable: false}
],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#remote_app_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var rid = $this.data('rid');
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

@ -0,0 +1,79 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block custom_head_css_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var inited = false;
var remote_app_table, url;
function initTable() {
if (inited){
return
} else {
inited = true;
}
url = '{% url "api-perms:my-remote-apps" %}';
var options = {
ele: $('#remote_app_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData);
$(td).html(name)
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var hostname = htmlEscape(cellData.hostname);
$(td).html(hostname);
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name);
$(td).html(name);
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(conn_btn)
}}
],
ajax_url: url,
columns: [
{data: "id"},
{data: "name"},
{data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment", orderable: false},
{data: "id", orderable: false}
]
};
remote_app_table = jumpserver.initServerSideDataTable(options);
return remote_app_table
}
$(document).ready(function(){
initTable();
})
</script>
{% endblock %}

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,7 @@
# coding: utf-8
#
__all__ = [
]

@ -0,0 +1,20 @@
# coding:utf-8
#
from django.urls import path
from rest_framework_bulk.routes import BulkRouter
from .. import api
app_name = 'applications'
router = BulkRouter()
router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app')
urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/',
api.RemoteAppConnectionInfoApi.as_view(),
name='remote-app-connection-info')
]
urlpatterns += router.urls

@ -0,0 +1,16 @@
# coding:utf-8
from django.urls import path
from .. import views
app_name = 'applications'
urlpatterns = [
# RemoteApp
path('remote-app/', views.RemoteAppListView.as_view(), name='remote-app-list'),
path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'),
path('remote-app/<uuid:pk>/update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'),
path('remote-app/<uuid:pk>/', views.RemoteAppDetailView.as_view(), name='remote-app-detail'),
# User RemoteApp view
path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list')
]

@ -0,0 +1 @@
from .remote_app import *

@ -0,0 +1,99 @@
# coding: utf-8
#
from django.utils.translation import ugettext as _
from django.views.generic import TemplateView
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.detail import DetailView
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from ..models import RemoteApp
from .. import forms
__all__ = [
'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView',
'RemoteAppDetailView', 'UserRemoteAppListView',
]
class RemoteAppListView(AdminUserRequiredMixin, TemplateView):
template_name = 'applications/remote_app_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('RemoteApp list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
template_name = 'applications/remote_app_create_update.html'
model = RemoteApp
form_class = forms.RemoteAppCreateUpdateForm
success_url = reverse_lazy('applications:remote-app-list')
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create RemoteApp'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
return create_success_msg % ({'name': cleaned_data['name']})
class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
template_name = 'applications/remote_app_create_update.html'
model = RemoteApp
form_class = forms.RemoteAppCreateUpdateForm
success_url = reverse_lazy('applications:remote-app-list')
def get_initial(self):
return {k: v for k, v in self.object.params.items()}
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update RemoteApp'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
return update_success_msg % ({'name': cleaned_data['name']})
class RemoteAppDetailView(AdminUserRequiredMixin, DetailView):
template_name = 'applications/remote_app_detail.html'
model = RemoteApp
context_object_name = 'remote_app'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('RemoteApp detail'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserRemoteAppListView(LoginRequiredMixin, TemplateView):
template_name = 'applications/user_remote_app_list.html'
def get_context_data(self, **kwargs):
context = {
'action': _('My RemoteApp'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
UPDATE_ASSETS_HARDWARE_TASKS = [
{
'name': "setup",
@ -51,3 +54,4 @@ TASK_OPTIONS = {
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'

@ -8,3 +8,4 @@ from .asset import *
from .cmd_filter import *
from .utils import *
from .authbook import *
from applications.models.remote_app import *

@ -11,7 +11,7 @@ __all__ = [
'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin',
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
'EncryptTextField', 'EncryptMixin',
'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
]
signer = get_signer()
@ -129,4 +129,7 @@ class EncryptCharField(EncryptMixin, models.CharField):
super().__init__(*args, **kwargs)
class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
pass

@ -67,6 +67,7 @@ INSTALLED_APPS = [
'terminal.apps.TerminalConfig',
'audits.apps.AuditsConfig',
'authentication.apps.AuthenticationConfig', # authentication
'applications.apps.ApplicationsConfig',
'rest_framework',
'rest_framework_swagger',
'drf_yasg',

@ -20,6 +20,7 @@ api_v1 = [
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')),
]
api_v2 = [
@ -37,6 +38,7 @@ app_view_patterns = [
path('audits/', include('audits.urls.view_urls', namespace='audits')),
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('applications/', include('applications.urls.views_urls', namespace='applications')),
]

File diff suppressed because it is too large Load Diff

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
from .permission import *
from .asset_permission import *
from .user_permission import *
from .user_group_permission import *
from .remote_app_permission import *

@ -0,0 +1,101 @@
# coding: utf-8
#
from rest_framework import viewsets, generics
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.views import Response
from common.permissions import IsOrgAdmin
from ..models import RemoteAppPermission
from ..serializers import (
RemoteAppPermissionSerializer,
RemoteAppPermissionUpdateUserSerializer,
RemoteAppPermissionUpdateRemoteAppSerializer,
)
__all__ = [
'RemoteAppPermissionViewSet',
'RemoteAppPermissionAddUserApi', 'RemoteAppPermissionAddRemoteAppApi',
'RemoteAppPermissionRemoveUserApi', 'RemoteAppPermissionRemoveRemoteAppApi',
]
class RemoteAppPermissionViewSet(viewsets.ModelViewSet):
filter_fields = ('name', )
search_fields = filter_fields
queryset = RemoteAppPermission.objects.all()
serializer_class = RemoteAppPermissionSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdmin,)
class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateUserSerializer
queryset = RemoteAppPermission.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 RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateUserSerializer
queryset = RemoteAppPermission.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 RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
remote_apps = serializer.validated_data.get('remote_apps')
if remote_apps:
perm.remote_apps.add(*tuple(remote_apps))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
remote_apps = serializer.validated_data.get('remote_apps')
if remote_apps:
perm.remote_apps.remove(*tuple(remote_apps))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})

@ -10,10 +10,12 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer
from orgs.utils import set_to_root_org
from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
RemoteAppPermissionUtil,
)
from ..hands import (
AssetGrantedSerializer, UserGroup, Node, NodeSerializer
AssetGrantedSerializer, UserGroup, Node, NodeSerializer,
RemoteAppSerializer,
)
from .. import serializers
@ -22,6 +24,7 @@ __all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
'UserGroupGrantedNodesWithAssetsAsTreeApi',
'UserGroupGrantedRemoteAppsApi',
]
@ -138,3 +141,20 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
for asset, system_users in assets.items():
asset.system_users_granted = system_users
return assets
# RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView):
permission_classes = (IsOrgAdmin, )
serializer_class = RemoteAppSerializer
def get_queryset(self):
queryset = []
user_group_id = self.kwargs.get('pk')
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = RemoteAppPermissionUtil(user_group)
queryset = util.get_remote_apps()
return queryset

@ -17,14 +17,15 @@ from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
check_system_user_action
check_system_user_action, RemoteAppPermissionUtil,
construct_remote_apps_tree_root, parse_remote_app_to_tree_node,
)
from ..hands import (
AssetGrantedSerializer, User, Asset, Node,
SystemUser, NodeSerializer
User, Asset, Node, SystemUser, RemoteApp, AssetGrantedSerializer,
NodeSerializer, RemoteAppSerializer,
)
from .. import serializers
from ..mixins import AssetsFilterMixin
from ..mixins import AssetsFilterMixin, RemoteAppFilterMixin
from ..models import Action
logger = get_logger(__name__)
@ -34,6 +35,8 @@ __all__ = [
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
'UserGrantedRemoteAppsAsTreeApi',
]
@ -447,3 +450,78 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
actions = [action.name for action in getattr(_su, 'actions', [])]
return Response({'actions': actions}, status=200)
# RemoteApp permission
class UserGrantedRemoteAppsApi(RemoteAppFilterMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = RemoteAppSerializer
pagination_class = LimitOffsetPagination
def get_object(self):
user_id = self.kwargs.get('pk', '')
if user_id:
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
return user
def get_queryset(self):
util = RemoteAppPermissionUtil(self.get_object())
queryset = util.get_remote_apps()
queryset = list(queryset)
return queryset
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
class UserGrantedRemoteAppsAsTreeApi(ListAPIView):
serializer_class = TreeNodeSerializer
permission_classes = (IsOrgAdminOrAppUser,)
def get_object(self):
user_id = self.kwargs.get('pk', '')
if not user_id:
user = self.request.user
else:
user = get_object_or_404(User, id=user_id)
return user
def get_queryset(self):
queryset = []
tree_root = construct_remote_apps_tree_root()
queryset.append(tree_root)
util = RemoteAppPermissionUtil(self.get_object())
remote_apps = util.get_remote_apps()
for remote_app in remote_apps:
node = parse_remote_app_to_tree_node(tree_root, remote_app)
queryset.append(node)
queryset = sorted(queryset)
return queryset
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
class ValidateUserRemoteAppPermissionApi(APIView):
def get(self, request, *args, **kwargs):
user_id = request.query_params.get('user_id', '')
remote_app_id = request.query_params.get('remote_app_id', '')
user = get_object_or_404(User, id=user_id)
remote_app = get_object_or_404(RemoteApp, id=remote_app_id)
util = RemoteAppPermissionUtil(user)
remote_apps = util.get_remote_apps()
if remote_app not in remote_apps:
return Response({'msg': False}, status=403)
return Response({'msg': True}, status=200)

@ -0,0 +1,5 @@
# coding: utf-8
#
from .asset_permission import *
from .remote_app_permission import *

@ -6,9 +6,13 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
from .models import AssetPermission
from perms.models import AssetPermission
from assets.models import Asset
__all__ = [
'AssetPermissionForm',
]
class AssetPermissionForm(OrgModelForm):
def __init__(self, *args, **kwargs):

@ -0,0 +1,49 @@
# coding: utf-8
#
from django.utils.translation import ugettext as _
from django import forms
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
from ..models import RemoteAppPermission
__all__ = [
'RemoteAppPermissionCreateUpdateForm',
]
class RemoteAppPermissionCreateUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
users_field = self.fields.get('users')
if hasattr(users_field, 'queryset'):
users_field.queryset = current_org.get_org_users()
class Meta:
model = RemoteAppPermission
exclude = (
'id', 'date_created', 'created_by', 'org_id'
)
widgets = {
'users': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('User')}
),
'user_groups': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('User group')}
),
'remote_apps': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('RemoteApp')}
)
}
def clean_user_groups(self):
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']

@ -3,8 +3,11 @@
from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node
from assets.serializers import AssetGrantedSerializer, NodeSerializer
from assets.models import Asset, SystemUser, Node, RemoteApp
from assets.serializers import (
AssetGrantedSerializer, NodeSerializer
)
from applications.serializers import RemoteAppSerializer

@ -0,0 +1,45 @@
# Generated by Django 2.1.7 on 2019-05-20 11:04
import common.utils.django
from django.conf import settings
from django.db import migrations, models
import django.utils.timezone
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('applications', '0001_initial'),
('users', '0019_auto_20190304_1459'),
('perms', '0004_assetpermission_actions'),
]
operations = [
migrations.CreateModel(
name='RemoteAppPermission',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('is_active', models.BooleanField(default=True, verbose_name='Active')),
('date_start', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date start')),
('date_expired', models.DateTimeField(db_index=True, default=common.utils.django.date_expired_default, verbose_name='Date expired')),
('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('remote_apps', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='applications.RemoteApp', verbose_name='RemoteApp')),
('user_groups', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='users.UserGroup', verbose_name='User group')),
('users', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'RemoteApp permission',
'ordering': ('name',),
},
),
migrations.AlterUniqueTogether(
name='remoteapppermission',
unique_together={('org_id', 'name')},
),
]

@ -2,6 +2,11 @@
#
__all__ = [
'AssetsFilterMixin', 'RemoteAppFilterMixin',
]
class AssetsFilterMixin(object):
"""
对资产进行过滤(查询排序)
@ -34,3 +39,38 @@ class AssetsFilterMixin(object):
queryset = sort_assets(queryset, order_by=order_by, reverse=reverse)
return queryset
class RemoteAppFilterMixin(object):
"""
对RemoteApp进行过滤(查询排序)
"""
def filter_queryset(self, queryset):
queryset = self.search_remote_apps(queryset)
queryset = self.sort_remote_apps(queryset)
return queryset
def search_remote_apps(self, queryset):
value = self.request.query_params.get('search')
if not value:
return queryset
queryset = [
remote_app for remote_app in queryset if value in remote_app.name
]
return queryset
def sort_remote_apps(self, queryset):
order_by = self.request.query_params.get('order')
if not order_by:
order_by = 'name'
if order_by.startswith('-'):
order_by = order_by.lstrip('-')
reverse = True
else:
reverse = False
queryset = sorted(
queryset, key=lambda x: getattr(x, order_by), reverse=reverse
)
return queryset

@ -0,0 +1,5 @@
# coding: utf-8
#
from .asset_permission import *
from .remote_app_permission import *

@ -7,7 +7,12 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin, OrgManager
from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
from perms.const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
__all__ = [
'Action', 'AssetPermission', 'NodePermission',
]
class Action(models.Model):

@ -0,0 +1,75 @@
# coding: utf-8
#
import uuid
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin, OrgManager
from common.utils import date_expired_default, set_or_append_attr_bulk
__all__ = [
'RemoteAppPermission',
]
class RemoteAppPermissionQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def valid(self):
return self.active().filter(date_start__lt=timezone.now())\
.filter(date_expired__gt=timezone.now())
class RemoteAppPermissionManager(OrgManager):
def valid(self):
return self.get_queryset().valid()
class RemoteAppPermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
users = models.ManyToManyField('users.User', related_name='remote_app_permissions', blank=True, verbose_name=_("User"))
user_groups = models.ManyToManyField('users.UserGroup', related_name='remote_app_permissions', blank=True, verbose_name=_("User group"))
remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='remote_app_permissions', blank=True, verbose_name=_("RemoteApp"))
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))
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'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
objects = RemoteAppPermissionManager.from_queryset(RemoteAppPermissionQuerySet)()
class Meta:
unique_together = [('org_id', 'name')]
verbose_name = _('RemoteApp permission')
ordering = ('name',)
def __str__(self):
return self.name
@property
def is_expired(self):
if self.date_expired > timezone.now() > self.date_start:
return False
return True
@property
def is_valid(self):
if not self.is_expired and self.is_active:
return True
return False
def get_all_users(self):
users = set(self.users.all())
for group in self.user_groups.all():
_users = group.users.all()
set_or_append_attr_bulk(_users, 'inherit', group.name)
users.update(set(_users))
return users
def get_all_remote_apps(self):
return set(self.remote_apps.all())

@ -0,0 +1,5 @@
# coding: utf-8
#
from .asset_permission import *
from .remote_app_permission import *

@ -4,7 +4,7 @@
from rest_framework import serializers
from common.fields import StringManyToManyField
from .models import AssetPermission, Action
from perms.models import AssetPermission, Action
from assets.models import Node, Asset, SystemUser
from assets.serializers import AssetGrantedSerializer
@ -13,7 +13,7 @@ __all__ = [
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer',
'GrantedAssetSerializer', 'GrantedSystemUserSerializer',
'ActionSerializer',
'ActionSerializer', 'NodeGrantedSerializer',
]

@ -0,0 +1,36 @@
# coding: utf-8
#
from rest_framework import serializers
from ..models import RemoteAppPermission
__all__ = [
'RemoteAppPermissionSerializer',
'RemoteAppPermissionUpdateUserSerializer',
'RemoteAppPermissionUpdateRemoteAppSerializer',
]
class RemoteAppPermissionSerializer(serializers.ModelSerializer):
class Meta:
model = RemoteAppPermission
fields = [
'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment',
'is_active', 'date_start', 'date_expired', 'is_valid',
'created_by', 'date_created', 'org_id'
]
read_only_fields = ['created_by', 'date_created']
class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer):
class Meta:
model = RemoteAppPermission
fields = ['id', 'users']
class RemoteAppPermissionUpdateRemoteAppSerializer(serializers.ModelSerializer):
class Meta:
model = RemoteAppPermission
fields = ['id', 'remote_apps']

@ -0,0 +1,120 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<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/datepicker/datepicker3.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<form method="post" class="form-horizontal" action="" >
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'User' %}</h3>
{% bootstrap_field form.users layout="horizontal" %}
{% bootstrap_field form.user_groups layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'RemoteApp' %}</h3>
{% bootstrap_field form.remote_apps layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
<div class="form-group">
<label for="{{ form.is_active.id_for_label }}" class="col-sm-2 control-label">{% trans 'Active' %}</label>
<div class="col-sm-8">
{{ form.is_active }}
</div>
</div>
<div class="form-group {% if form.date_expired.errors or form.date_start.errors %} has-error {% endif %}" id="date_5">
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{% trans 'Validity period' %}</label>
<div class="col-sm-9">
<div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
{% if form.errors %}
<input type="text" class="input-sm form-control" id="date_start" name="date_start" value="{{ form.date_start.value }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" id="date_expired" name="date_expired" value="{{ form.date_expired.value }}">
{% else %}
<input type="text" class="input-sm form-control" id="date_start" name="date_start" value="{{ form.date_start.value|date:'Y-m-d H:i' }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" id="date_expired" name="date_expired" value="{{ form.date_expired.value|date:'Y-m-d H:i' }}">
{% endif %}
</div>
<span class="help-block ">{{ form.date_expired.errors }}</span>
<span class="help-block ">{{ form.date_start.errors }}</span>
</div>
</div>
{% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script type="text/javascript" src='{% static "js/plugins/daterangepicker/moment.min.js" %}'></script>
<script type="text/javascript" src='{% static "js/plugins/daterangepicker/daterangepicker.min.js" %}'></script>
<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
<script>
var dateOptions = {
singleDatePicker: true,
showDropdowns: true,
timePicker: true,
timePicker24Hour: true,
autoApply: true,
locale: {
format: 'YYYY-MM-DD HH:mm'
}
};
$(document).ready(function () {
$('.select2').select2({
closeOnSelect: false
});
$('#date_start').daterangepicker(dateOptions);
$('#date_expired').daterangepicker(dateOptions);
})
</script>
{% endblock %}

@ -0,0 +1,169 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'perms:remote-app-permission-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li>
<a href="{% url 'perms:remote-app-permission-user-list' pk=object.id %}" class="text-center">
<i class="fa fa-group"></i> {% trans 'Users and user groups' %}
</a>
</li>
<li>
<a href="{% url 'perms:remote-app-permission-remote-app-list' pk=object.id %}" class="text-center">
<i class="fa fa-th"></i> {% trans 'RemoteApp' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'perms:remote-app-permission-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ object.name }}</b></td>
</tr>
<tr>
<td>{% trans 'User count' %}:</td>
<td><b>{{ object.users.count }}</b></td>
</tr>
<tr>
<td>{% trans 'User group count' %}:</td>
<td><b>{{ object.user_groups.count }}</b></td>
</tr>
<tr>
<td>{% trans 'RemoteApp count' %}:</td>
<td><b>{{ object.remote_apps.count }}</b></td>
</tr>
<tr>
<td>{% trans 'Date start' %}:</td>
<td><b>{{ object.date_start }}</b></td>
</tr>
<tr>
<td>{% trans 'Date expired' %}:</td>
<td><b>{{ object.date_expired }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ object.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ object.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ object.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Active' %} :</td>
<td><span style="float: right">
<div class="switch">
<div class="onoffswitch">
<input type="checkbox" {% if object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
<label class="onoffswitch-label" for="is_active">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.system_users_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.system_users_selected[data.id]
})
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var name = "{{ object.name }}";
var rid = "{{ object.id }}";
var the_url = '{% url "api-perms:remote-app-permission-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
var redirect_url = "{% url 'perms:remote-app-permission-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#is_active', function () {
var the_url = '{% url "api-perms:remote-app-permission-detail" pk=object.id %}';
var checked = $(this).prop('checked');
var body = {
'is_active': checked
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body)
});
})
</script>
{% endblock %}

@ -0,0 +1,93 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'perms:remote-app-permission-create' %}" class="btn btn-sm btn-primary"> {% trans "Create permission" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="remote_app_permission_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'RemoteApp' %}</th>
<th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#remote_app_permission_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'perms:remote-app-permission-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData, rowData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "perms:remote-app-permission-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-perms:remote-app-permission-list" %}',
columns: [
{data: "id"},
{data: "name" },
{data: "users", orderable: false},
{data: "user_groups", orderable: false},
{data: "remote_apps", orderable: false},
{data: "is_valid", orderable: false},
{data: "id", orderable: false}
],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#remote_app_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var rid = $this.data('rid');
var the_url = '{% url "api-perms:remote-app-permission-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

@ -0,0 +1,164 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'perms:remote-app-permission-detail' pk=remote_app_permission.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li>
<a href="{% url 'perms:remote-app-permission-user-list' pk=remote_app_permission.id %}" class="text-center">
<i class="fa fa-group"></i> {% trans 'Users and user groups' %}
</a>
</li>
<li class="active">
<a href="{% url 'perms:remote-app-permission-remote-app-list' pk=remote_app_permission.id %}" class="text-center">
<i class="fa fa-th"></i> {% trans 'RemoteApp' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'RemoteApp list of ' %} <b>{{ remote_app_permission.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Type' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for remote_app in object_list %}
<tr>
<td>{{ remote_app.name }}</td>
<td>{{ remote_app.get_type_display }}</td>
<td>
<button data-aid="{{ remote_app.id }}" class="btn btn-danger btn-xs btn-remove-remote-app" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row">
{% include '_pagination.html' %}
</div>
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Add RemoteApp to this permission' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select RemoteApp' %}" class="select2" id="remote_app_select2" style="width: 100%" multiple="" tabindex="4">
{% for remote_app in remote_app_remain %}
<option value="{{ remote_app.id }}">{{ remote_app }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm btn-add-remote-app">{% trans 'Add' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function addRemoteApps(remote_apps) {
var the_url = "{% url 'api-perms:remote-app-permission-add-remote-app' pk=remote_app_permission.id %}";
var body = {
remote_apps : remote_apps
};
var success = function(data) {
location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function removeRemoteApps(remote_apps) {
var the_url = "{% url 'api-perms:remote-app-permission-remove-remote-app' pk=remote_app_permission.id %}";
var body = {
remote_apps: remote_apps
};
var success = function(data) {
location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
$(document).ready(function () {
$('.select2').select2();
})
.on('click', '.btn-add-remote-app', function () {
var remote_apps_selected = $("#remote_app_select2 option:selected").map(function () {
return $(this).attr('value');
}).get();
if (remote_apps_selected.length === 0) {
return false;
}
addRemoteApps(remote_apps_selected);
})
.on('click', '.btn-remove-remote-app', function () {
var remote_app_id= $(this).data("aid");
if (remote_app_id === "") {
return
}
var remote_apps = [remote_app_id];
removeRemoteApps(remote_apps)
})
</script>
{% endblock %}

@ -0,0 +1,256 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'perms:remote-app-permission-detail' pk=remote_app_permission.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="active">
<a href="{% url 'perms:remote-app-permission-user-list' pk=remote_app_permission.id %}" class="text-center">
<i class="fa fa-group"></i> {% trans 'Users and user groups' %}
</a>
</li>
<li>
<a href="{% url 'perms:remote-app-permission-remote-app-list' pk=remote_app_permission.id %}" class="text-center">
<i class="fa fa-th"></i> {% trans 'RemoteApp' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'User list of ' %} <b>{{ remote_app_permission.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Username' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in object_list %}
<tr>
<td>{{ user.name }}</td>
<td>{{ user.username }}</td>
<td>
<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>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row">
{% include '_pagination.html' %}
</div>
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Add user to this permission' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select user' %}" class="select2 user" style="width: 100%" multiple="" tabindex="4">
{% for user in users_remain %}
<option value="{{ user.id }}">{{ user }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm btn-add-user">{% trans 'Add' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Add user group to this permission' %}
</div>
<div class="panel-body">
<table class="table group_edit">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select user groups' %}" class="select2 user-group" style="width: 100%" multiple="" tabindex="4">
{% for user_group in user_groups_remain %}
<option value="{{ user_group.id }}" id="opt_{{ user_group.id }}">{{ user_group }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-add-group">{% trans 'Add' %}</button>
</td>
</tr>
</form>
{% for user_group in remote_app_permission.user_groups.all %}
<tr>
<td ><b class="bdg_group" data-gid={{ user_group.id }}>{{ user_group }}</b></td>
<td>
<button class="btn btn-danger btn-xs btn-remove-group" type="button" data-gid="{{ user_group.id }}" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.users_selected = {};
jumpserver.user_groups_selected = {};
function addUsers(users) {
var the_url = "{% url 'api-perms:remote-app-permission-add-user' pk=remote_app_permission.id %}";
var body = {
users: users
};
var success = function(data) {
location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function removeUser(users) {
var the_url = "{% url 'api-perms:remote-app-permission-remove-user' pk=remote_app_permission.id %}";
var body = {
users: users
};
var success = function(data) {
location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function updateGroup(groups) {
var the_url = "{% url 'api-perms:remote-app-permission-detail' pk=remote_app_permission.id %}";
var body = {
user_groups: groups
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body)
});
}
$(document).ready(function () {
$('.select2.user').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.users_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.users_selected[data.id]
});
$('.select2.user-group').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.user_groups_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.user_groups_selected[data.id]
})
}).on('click', '.btn-add-user', function () {
if (Object.keys(jumpserver.users_selected).length === 0) {
return false;
}
var users_id = [];
$.map(jumpserver.users_selected, function(value, index) {
users_id.push(index);
});
addUsers(users_id);
}).on('click', '.btn-remove-user', function () {
var user_id = $(this).data("gid");
if (user_id === "") {
return
}
var users = [user_id];
removeUser(users)
}).on('click', '#btn-add-group', function () {
if (Object.keys(jumpserver.user_groups_selected).length === 0) {
return false;
}
var groups = $('.bdg_group').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.user_groups_selected, function(group_name, index) {
groups.push(index);
$('#opt_' + index).remove();
$('.group_edit tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-group" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
updateGroup(groups);
}).on('click', '.btn-remove-group', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var groups = $('.bdg_group').map(function() {
if ($(this).data('gid') !== $this.data('gid')){
return $(this).data('gid');
}
}).get();
updateGroup(groups);
$tr.remove()
})
</script>
{% endblock %}

@ -9,8 +9,9 @@ app_name = 'perms'
router = routers.DefaultRouter()
router.register('actions', api.ActionViewSet, 'action')
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission')
urlpatterns = [
asset_permission_urlpatterns = [
# 查询某个用户授权的资产和资产组
path('user/<uuid:pk>/assets/',
api.UserGrantedAssetsApi.as_view(), name='user-assets'),
@ -35,7 +36,6 @@ urlpatterns = [
path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(),
name='my-nodes-assets-as-tree'),
# 查询某个用户组授权的资产和资产组
path('user-group/<uuid:pk>/assets/',
api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
@ -72,5 +72,48 @@ urlpatterns = [
name='get-user-asset-permission-actions'),
]
remote_app_permission_urlpatterns = [
# 查询用户授权的RemoteApp
path('user/<uuid:pk>/remote-apps/',
api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'),
path('user/remote-apps/',
api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'),
# 获取用户授权的RemoteApp树
path('user/<uuid:pk>/remote-apps/tree/',
api.UserGrantedRemoteAppsAsTreeApi.as_view(),
name='user-remote-apps-as-tree'),
path('user/remote-apps/tree/',
api.UserGrantedRemoteAppsAsTreeApi.as_view(),
name='my-remote-apps-as-tree'),
# 查询用户组授权的RemoteApp
path('user-group/<uuid:pk>/remote-apps/',
api.UserGroupGrantedRemoteAppsApi.as_view(),
name='user-group-remote-apps'),
# 校验用户对RemoteApp的权限
path('remote-app-permission/user/validate/',
api.ValidateUserRemoteAppPermissionApi.as_view(),
name='validate-user-remote-app-permission'),
# 用户和RemoteApp变更
path('remote-app-permissions/<uuid:pk>/user/add/',
api.RemoteAppPermissionAddUserApi.as_view(),
name='remote-app-permission-add-user'),
path('remote-app-permissions/<uuid:pk>/user/remove/',
api.RemoteAppPermissionRemoveUserApi.as_view(),
name='remote-app-permission-remove-user'),
path('remote-app-permissions/<uuid:pk>/remote-app/remove/',
api.RemoteAppPermissionRemoveRemoteAppApi.as_view(),
name='remote-app-permission-remove-remote-app'),
path('remote-app-permissions/<uuid:pk>/remote-app/add/',
api.RemoteAppPermissionAddRemoteAppApi.as_view(),
name='remote-app-permission-add-remote-app'),
]
urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns
urlpatterns += router.urls

@ -7,6 +7,7 @@ from .. import views
app_name = 'perms'
urlpatterns = [
# asset-permission
path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
path('asset-permission/create/', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
path('asset-permission/<uuid:pk>/update/', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
@ -14,4 +15,12 @@ urlpatterns = [
path('asset-permission/<uuid:pk>/delete/', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
path('asset-permission/<uuid:pk>/user/', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
path('asset-permission/<uuid:pk>/asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
# remote-app-permission
path('remote-app-permission/', views.RemoteAppPermissionListView.as_view(), name='remote-app-permission-list'),
path('remote-app-permission/create/', views.RemoteAppPermissionCreateView.as_view(), name='remote-app-permission-create'),
path('remote-app-permission/<uuid:pk>/update/', views.RemoteAppPermissionUpdateView.as_view(), name='remote-app-permission-update'),
path('remote-app-permission/<uuid:pk>/', views.RemoteAppPermissionDetailView.as_view(), name='remote-app-permission-detail'),
path('remote-app-permission/<uuid:pk>/user/', views.RemoteAppPermissionUserView.as_view(), name='remote-app-permission-user-list'),
path('remote-app-permission/<uuid:pk>/remote-app/', views.RemoteAppPermissionRemoteAppView.as_view(), name='remote-app-permission-remote-app-list'),
]

@ -0,0 +1,5 @@
# coding: utf-8
#
from .asset_permission import *
from .remote_app_permission import *

@ -12,12 +12,19 @@ from django.conf import settings
from common.utils import get_logger
from common.tree import TreeNode
from .models import AssetPermission, Action
from .hands import Node
from perms.models import AssetPermission, Action
from perms.hands import Node
logger = get_logger(__file__)
__all__ = [
'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets',
'parse_asset_to_tree_node', 'parse_node_to_tree_node',
'check_system_user_action',
]
class GenerateTree:
def __init__(self):
"""
@ -378,7 +385,7 @@ def sort_assets(assets, order_by='hostname', reverse=False):
def parse_node_to_tree_node(node):
from . import serializers
from .. import serializers
name = '{} ({})'.format(node.value, node.assets_amount)
node_serializer = serializers.GrantedNodeSerializer(node)
data = {
@ -444,11 +451,6 @@ def parse_asset_to_tree_node(node, asset, system_users):
return tree_node
#
# actions
#
def check_system_user_action(system_user, action):
"""
:param system_user: SystemUser object (包含动态属性: actions)

@ -0,0 +1,81 @@
# coding: utf-8
#
from django.db.models import Q
from common.tree import TreeNode
from ..models import RemoteAppPermission
__all__ = [
'RemoteAppPermissionUtil',
'construct_remote_apps_tree_root',
'parse_remote_app_to_tree_node',
]
def get_user_remote_app_permissions(user, include_group=True):
if include_group:
groups = user.groups.all()
arg = Q(users=user) | Q(user_groups__in=groups)
else:
arg = Q(users=user)
return RemoteAppPermission.objects.all().valid().filter(arg)
def get_user_group_remote_app_permissions(user_group):
return RemoteAppPermission.objects.all().valid().filter(
user_groups=user_group
)
class RemoteAppPermissionUtil:
get_permissions_map = {
"User": get_user_remote_app_permissions,
"UserGroup": get_user_group_remote_app_permissions,
}
def __init__(self, obj):
self.object = obj
@property
def permissions(self):
obj_class = self.object.__class__.__name__
func = self.get_permissions_map[obj_class]
_permissions = func(self.object)
return _permissions
def get_remote_apps(self):
remote_apps = set()
for perm in self.permissions:
remote_apps.update(list(perm.remote_apps.all()))
return remote_apps
def construct_remote_apps_tree_root():
tree_root = {
'id': 'ID_REMOTE_APP_ROOT',
'name': 'RemoteApp',
'title': 'RemoteApp',
'pId': '',
'open': False,
'isParent': True,
'iconSkin': '',
'meta': {'type': 'remote_app'}
}
return TreeNode(**tree_root)
def parse_remote_app_to_tree_node(parent, remote_app):
tree_node = {
'id': remote_app.id,
'name': remote_app.name,
'title': remote_app.name,
'pId': parent.id,
'open': False,
'isParent': False,
'iconSkin': 'file',
'meta': {'type': 'remote_app'}
}
return TreeNode(**tree_node)

@ -0,0 +1,5 @@
# coding: utf-8
#
from .asset_permission import *
from .remote_app_permission import *

@ -10,10 +10,19 @@ from django.conf import settings
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from .hands import Node, Asset, SystemUser, User, UserGroup
from .models import AssetPermission, Action
from .forms import AssetPermissionForm
from .const import PERMS_ACTION_NAME_ALL
from perms.hands import Node, Asset, SystemUser, User, UserGroup
from perms.models import AssetPermission, Action
from perms.forms import AssetPermissionForm
from perms.const import PERMS_ACTION_NAME_ALL
__all__ = [
'AssetPermissionListView', 'AssetPermissionCreateView',
'AssetPermissionUpdateView', 'AssetPermissionDetailView',
'AssetPermissionDeleteView', 'AssetPermissionUserView',
'AssetPermissionAssetView',
]
class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
@ -84,7 +93,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Update asset permission'),
'action': _('Asset permission detail'),
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),

@ -0,0 +1,144 @@
# coding: utf-8
#
from django.utils.translation import ugettext as _
from django.urls import reverse_lazy
from django.views.generic import (
TemplateView, CreateView, UpdateView, DetailView, ListView
)
from django.views.generic.edit import SingleObjectMixin
from django.conf import settings
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from users.models import UserGroup
from assets.models import RemoteApp
from ..models import RemoteAppPermission
from ..forms import RemoteAppPermissionCreateUpdateForm
__all__ = [
'RemoteAppPermissionListView', 'RemoteAppPermissionCreateView',
'RemoteAppPermissionUpdateView', 'RemoteAppPermissionDetailView',
'RemoteAppPermissionUserView', 'RemoteAppPermissionRemoteAppView'
]
class RemoteAppPermissionListView(AdminUserRequiredMixin, TemplateView):
template_name = 'perms/remote_app_permission_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('RemoteApp permission list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class RemoteAppPermissionCreateView(AdminUserRequiredMixin, CreateView):
template_name = 'perms/remote_app_permission_create_update.html'
model = RemoteAppPermission
form_class = RemoteAppPermissionCreateUpdateForm
success_url = reverse_lazy('perms:remote-app-permission-list')
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Create RemoteApp permission'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class RemoteAppPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
template_name = 'perms/remote_app_permission_create_update.html'
model = RemoteAppPermission
form_class = RemoteAppPermissionCreateUpdateForm
success_url = reverse_lazy('perms:remote-app-permission-list')
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Update RemoteApp permission')
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class RemoteAppPermissionDetailView(AdminUserRequiredMixin, DetailView):
template_name = 'perms/remote_app_permission_detail.html'
model = RemoteAppPermission
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('RemoteApp permission detail'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class RemoteAppPermissionUserView(AdminUserRequiredMixin,
SingleObjectMixin,
ListView):
template_name = 'perms/remote_app_permission_user.html'
context_object_name = 'remote_app_permission'
paginate_by = settings.DISPLAY_PER_PAGE
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(
queryset=RemoteAppPermission.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
queryset = list(self.object.get_all_users())
return queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('RemoteApp permission user list'),
'users_remain': current_org.get_org_users().exclude(
remote_app_permissions=self.object
),
'user_groups_remain': UserGroup.objects.exclude(
remote_app_permissions=self.object
)
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class RemoteAppPermissionRemoteAppView(AdminUserRequiredMixin,
SingleObjectMixin,
ListView):
template_name = 'perms/remote_app_permission_remote_app.html'
context_object_name = 'remote_app_permission'
paginate_by = settings.DISPLAY_PER_PAGE
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(
queryset=RemoteAppPermission.objects.all()
)
return super().get(request, *args, **kwargs)
def get_queryset(self):
queryset = list(self.object.get_all_remote_apps())
return queryset
def get_context_data(self, **kwargs):
remote_app_granted = self.get_queryset()
remote_app_remain = RemoteApp.objects.exclude(
id__in=[a.id for a in remote_app_granted])
context = {
'app': _('Perms'),
'action': _('RemoteApp permission RemoteApp list'),
'remote_app_remain': remote_app_remain
}
kwargs.update(context)
return super().get_context_data(**kwargs)

@ -27,12 +27,23 @@
<li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li>
</ul>
</li>
<li id="applications">
<a>
<i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'Applications' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li id="remote-app"><a href="{% url 'applications:remote-app-list' %}">{% trans 'RemoteApp' %}</a></li>
</ul>
</li>
<li id="perms">
<a href="#"><i class="fa fa-edit" style="width: 14px"></i> <span class="nav-label">{% trans 'Perms' %}</span><span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li id="asset-permission">
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a>
</li>
<li id="remote-app-permission">
<a href="{% url 'perms:remote-app-permission-list' %}">{% trans 'RemoteApp permission' %}</a>
</li>
</ul>
</li>
<li id="terminal">

@ -4,6 +4,18 @@
<i class="fa fa-files-o" style="width: 14px"></i><span class="nav-label">{% trans 'My assets' %}</span><span class="label label-info pull-right"></span>
</a>
</li>
<li id="applications">
<a>
<i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'My Applications' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li id="user-remote-app">
<a href="{% url 'applications:user-remote-app-list' %}">
<i class="" style="width: 14px"></i><span class="nav-label">{% trans 'RemoteApp' %}</span><span class="label label-info pull-right"></span>
</a>
</li>
</ul>
</li>
<li id="ops">
<a href="{% url 'ops:command-execution-start' %}">
<i class="fa fa-terminal" style="width: 14px"></i> <span class="nav-label">{% trans 'Command execution' %}</span><span class="label label-info pull-right"></span>

Loading…
Cancel
Save