Commit 4944ac8e authored by 老广's avatar 老广 Committed by BaiJiangJie

Config (#3502)

* [Update] 修改config

* [Update] 移动存储设置到到terminal中

* [Update] 修改permission 查看

* [Update] pre merge

* [Update] 录像存储

* [Update] 命令存储

* [Update] 添加存储测试可连接性

* [Update] 修改 meta 值的 key 为大写

* [Update] 修改 Terminal 相关 Storage 配置

* [Update] 删除之前获取录像/命令存储的代码

* [Update] 修改导入失败

* [Update] 迁移文件添加default存储

* [Update] 删除之前代码,添加help_text信息

* [Update] 删除之前代码

* [Update] 删除之前代码

* [Update] 抽象命令/录像存储 APIView

* [Update] 抽象命令/录像存储 APIView 1

* [Update] 抽象命令/录像存储 DictField

* [Update] 抽象命令/录像存储列表页面

* [Update] 修复CustomDictField的bug

* [Update] RemoteApp 页面添加 hidden

* [Update] 用户页面添加用户关联授权

* [Update] 修改存储测试可连接性 target

* [Update] 修改配置

* [Update] 修改存储前端 Form 渲染逻辑

* [Update] 修改存储细节

* [Update] 统一存储类型到 const 文件

* [Update] 修改迁移文件及Model,创建默认存储

* [Update] 修改迁移文件及Model初始化默认数据

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 限制删除默认存储配置,只允许创建扩展的存储类型

* [Update] 修改ip字段长度

* [Update] 修改ip字段长度

* [Update] 修改一些css

* [Update] 修改关联

* [Update] 添加操作日志定时清理

* [Update] 修改记录syslog的instance encoder

* [Update] 忽略登录产生的操作日志

* [Update] 限制更新存储时不覆盖原有AK SK 等字段

* [Update] 修改迁移文件添加comment字段

* [Update] 修改迁移文件

* [Update] 添加 comment 字段

* [Update] 修改默认存储no -> null

* [Update] 修改细节

* [Update] 更新翻译(存储配置

* [Update] 修改定时任务注册,修改系统用户资产、节点关系api

* [Update] 添加监控磁盘任务

* [Update] 修改session

* [Update] 拆分serializer

* [Update] 还原setting原来的manager
parent fd1b9d97
......@@ -5,6 +5,7 @@
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from common.fields.serializer import CustomMetaDictField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .. import const
......@@ -16,54 +17,9 @@ __all__ = [
]
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 RemoteAppParamsDictField(CustomMetaDictField):
type_map_fields = const.REMOTE_APP_TYPE_MAP_FIELDS
default_type = const.REMOTE_APP_TYPE_CHROME
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
......
......@@ -19,14 +19,14 @@
<div class="hr-line-dashed"></div>
{# chrome #}
<div class="chrome-fields">
<div class="chrome-fields hidden">
{% 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">
<div class="mysql_workbench-fields hidden">
{% bootstrap_field form.mysql_workbench_ip layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_name layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_username layout="horizontal" %}
......@@ -34,14 +34,14 @@
</div>
{# vmware #}
<div class="vmware_client-fields">
<div class="vmware_client-fields hidden">
{% 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">
<div class="custom-fields hidden">
{% bootstrap_field form.custom_cmdline layout="horizontal" %}
{% bootstrap_field form.custom_target layout="horizontal" %}
{% bootstrap_field form.custom_username layout="horizontal" %}
......
......@@ -2,11 +2,6 @@
{% 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">
......@@ -102,4 +97,4 @@ $(document).ready(function () {
objectDelete($this, name, the_url, redirect_url);
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,6 +2,7 @@ from .admin_user import *
from .asset import *
from .label import *
from .system_user import *
from .system_user_relation import *
from .node import *
from .domain import *
from .cmd_filter import *
......
......@@ -14,6 +14,7 @@
# limitations under the License.
from django.db import transaction
from django.db.models import Count
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet
......@@ -44,6 +45,11 @@ class AdminUserViewSet(OrgBulkModelViewSet):
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(_assets_amount=Count('assets'))
return queryset
class AdminUserAuthApi(generics.UpdateAPIView):
model = AdminUser
......
......@@ -114,7 +114,7 @@ class AssetUserExportViewSet(AssetUserViewSet):
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
......@@ -124,7 +124,7 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
......
......@@ -14,8 +14,8 @@
# limitations under the License.
from django.shortcuts import get_object_or_404
from django.conf import settings
from rest_framework.response import Response
from django.db.models import Count
from common.serializers import CeleryTaskSerializer
from common.utils import get_logger
......@@ -50,6 +50,11 @@ class SystemUserViewSet(OrgBulkModelViewSet):
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(_assets_amount=Count('assets'))
return queryset
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
......
# -*- coding: utf-8 -*-
#
from django.db.models import F, Value
from django.db.models.functions import Concat
from common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
from .. import models, serializers
__all__ = ['SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet']
class RelationMixin(OrgBulkModelViewSet):
def get_queryset(self):
queryset = self.model.objects.all()
org_id = current_org.org_id()
if org_id is not None:
queryset = queryset.filter(systemuser__org_id=org_id)
queryset = queryset.annotate(systemuser_display=Concat(
F('systemuser__name'), Value('('), F('systemuser__username'),
Value(')')
))
return queryset
class SystemUserAssetRelationViewSet(RelationMixin):
serializer_class = serializers.SystemUserAssetRelationSerializer
model = models.SystemUser.assets.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'asset', 'systemuser',
]
search_fields = [
"id", "asset__hostname", "asset__ip",
"systemuser__name", "systemuser__username"
]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
asset_display=Concat(
F('asset__hostname'), Value('('),
F('asset__ip'), Value(')')
)
)
return queryset
class SystemUserNodeRelationViewSet(RelationMixin):
serializer_class = serializers.SystemUserNodeRelationSerializer
model = models.SystemUser.nodes.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'node', 'systemuser',
]
search_fields = [
"node__value", "systemuser__name", "systemuser_username"
]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(node_key=F('node__key'))
return queryset
......@@ -41,6 +41,7 @@ class AssetUser(OrgModelMixin):
ASSET_USER_CACHE_TIME = 3600 * 24
_prefer = "system_user"
_assets_amount = None
@property
def private_key_obj(self):
......@@ -143,6 +144,8 @@ class AssetUser(OrgModelMixin):
@property
def assets_amount(self):
if self._assets_amount is not None:
return self._assets_amount
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cached = cache.get(cache_key)
if not cached:
......
......@@ -4,8 +4,10 @@ from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.serializers import AdaptedBulkListSerializer
from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from assets.models import Node
from ..models import SystemUser
from ..const import (
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
......@@ -13,6 +15,12 @@ from ..const import (
)
from .base import AuthSerializer, AuthSerializerMixin
__all__ = [
'SystemUserSerializer', 'SystemUserAuthSerializer',
'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer',
'SystemUserNodeRelationSerializer',
]
class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
"""
......@@ -143,4 +151,43 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'username')
class RelationMixin(BulkSerializerMixin, serializers.Serializer):
systemuser_display = serializers.ReadOnlyField()
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['systemuser', "systemuser_display"])
return fields
class Meta:
list_serializer_class = AdaptedBulkListSerializer
class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
asset_display = serializers.ReadOnlyField()
class Meta(RelationMixin.Meta):
model = SystemUser.assets.through
fields = [
'id', "asset", "asset_display",
]
class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
node_display = serializers.SerializerMethodField()
class Meta(RelationMixin.Meta):
model = SystemUser.nodes.through
fields = [
'id', 'node', "node_display",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tree = Node.tree()
def get_node_display(self, obj):
if hasattr(obj, 'node_key'):
return self.tree.get_node_full_tag(obj.node_key)
else:
return obj.node.full_value
......@@ -190,6 +190,16 @@ function setAssetModalOptions(options) {
assetModalOption = options;
}
function initAssetTreeModel(selector) {
$(selector).parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
}
})
}
$(document).ready(function(){
......
......@@ -2,10 +2,6 @@
{% 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>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......@@ -247,4 +243,4 @@ $(document).ready(function () {
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,11 +2,6 @@
{% 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">
......
......@@ -2,10 +2,6 @@
{% 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>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......@@ -87,4 +83,4 @@ $(document).ready(function () {
})
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,11 +2,6 @@
{% 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">
......
......@@ -32,13 +32,7 @@
<script>
$(document).ready(function () {
$('.select2').select2();
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
}
})
initAssetTreeModel("#id_assets");
}).on('click', '.field-tag', function() {
changeField(this);
}).on('click', '#change_all', function () {
......
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<link href='{% static "css/plugins/sweetalert/sweetalert.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
<script src='{% static "js/plugins/sweetalert/sweetalert.min.js" %}'></script>
{% endblock %}
......
......@@ -382,9 +382,6 @@ $(document).ready(function(){
var data = {
'resources': id_list
};
function refreshPage() {
setTimeout( function () {window.location.reload();}, 300);
}
function reloadTable() {
asset_table.ajax.reload();
......
......@@ -2,11 +2,6 @@
{% 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">
......
......@@ -2,10 +2,6 @@
{% 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>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......@@ -91,4 +87,4 @@ $(document).ready(function(){
formSubmit(props);
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,11 +2,6 @@
{% 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">
......
......@@ -25,9 +25,7 @@
<script type="text/javascript">
$(document).ready(function () {
$('.select2').select2().off("select2:open");
}).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault();
$("#asset_list_modal").modal();
initAssetTreeModel('#id_assets');
})
.on("submit", "form", function (evt) {
evt.preventDefault();
......@@ -51,4 +49,4 @@ $(document).ready(function () {
formSubmit(props);
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,11 +2,6 @@
{% 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">
......@@ -138,4 +133,4 @@ $(document).ready(function(){
})
;
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,11 +2,6 @@
{% 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">
......
......@@ -2,10 +2,6 @@
{% 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>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......@@ -125,4 +121,4 @@ $(document).ready(function(){
protocolChange();
});
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -29,9 +29,7 @@ $(document).ready(function () {
$('.select2').select2({
closeOnSelect: false
})
}).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault();
$("#asset_list_modal").modal();
initAssetTreeModel("#id_assets");
})
.on("submit", "form", function (evt) {
evt.preventDefault();
......@@ -55,4 +53,4 @@ $(document).ready(function () {
formSubmit(props);
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -4,9 +4,13 @@
{% 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>
<style>
.table.node_edit {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
......@@ -98,15 +102,8 @@
</td>
</tr>
</form>
{% for node in system_user.nodes.all|sort %}
<tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.full_value }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
<table class="table" id="node_list_table">
</table>
</tbody>
</table>
</div>
......@@ -120,91 +117,115 @@
{% endblock %}
{% block custom_foot_js %}
<script>
var assetsRelationUrl = "{% url 'api-assets:system-users-assets-relation-list' %}";
var nodesRelationUrl = "{% url 'api-assets:system-users-nodes-relation-list' %}";
function updateSystemUserNode(nodes) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = {
nodes: Object.assign([], nodes)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#node_selected').val('');
$.map(jumpserver.nodes_selected, function(node_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('.node_edit tbody').append(
'<tr>' +
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.nodes_selected
jumpserver.nodes_selected = {};
};
function getRelationUrl(type) {
var theUrl = "";
switch (type) {
case "asset":
theUrl = assetsRelationUrl;
break;
case "node":
theUrl = nodesRelationUrl;
break;
}
return theUrl;
}
function addObjects(objectsId, type) {
if (!objectsId || objectsId.length === 0) {
return
}
var theUrl = getRelationUrl(type);
var body = [];
objectsId.forEach(function (v) {
var data = {systemuser: "{{ object.id }}"};
data[type] = v;
body.push(data)
});
requestApi({
url: the_url,
url: theUrl,
body: JSON.stringify(body),
method: "POST",
success: reloadPage
});
}
function removeObject(objectId, type, success) {
if (!objectId) {
return
}
var theUrl = getRelationUrl(type);
theUrl = setUrlParam(theUrl, 'systemuser', "{{ object.id }}");
theUrl = setUrlParam(theUrl, type, objectId);
if (!success) {
success = reloadPage
}
requestApi({
url: theUrl,
method: "DELETE",
success: success
});
}
jumpserver.nodes_selected = {};
function initNodeTable() {
var theUrl = setUrlParam(nodesRelationUrl, "systemuser", "{{ object.id }}");
var options = {
ele: $('#node_list_table'),
toggle: true,
columnDefs: [
{targets: 1, createdCell: function (td, cellData) {
var removeBtn = '<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" data-uid="UID" type="button"><i class="fa fa-minus"></i></button>'
.replace('UID', cellData);
$(td).html(removeBtn);
}}
],
ajax_url: theUrl,
dom: "trp",
hideDefaultDefs: true,
columns: [
{data: "node_display", orderable: false},
{data: "node", orderable: false},
],
};
table = jumpserver.initServerSideDataTable(options);
return table
}
$(document).ready(function () {
$('.select2').select2()
nodesSelect2Init(".nodes-select2")
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id];
});
$('.select2').select2();
nodesSelect2Init(".nodes-select2");
assetUserListUrl = setUrlParam(assetUserListUrl, "system_user_id", "{{ system_user.id }}");
needPush = true;
initAssetUserTable();
initNodeTable();
})
.on('click', '.btn-remove-from-node', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_node');
var gid = $badge.data('gid');
var node_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
);
$tr.remove();
var nodes = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserNode(nodes);
var nodeId = $(this).data('uid');
var success = function() {
var $tr = $this.closest('tr');
$tr.remove();
};
removeObject(nodeId, "node", success)
})
.on('click', '#btn-add-to-node', function() {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
updateSystemUserNode(nodes);
var nodes = $("#node_selected").val();
addObjects(nodes, "node");
})
.on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var theUrl = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
showCeleryTaskLog(task_id);
var taskId = data.task;
showCeleryTaskLog(taskId);
};
requestApi({
url: the_url,
url: theUrl,
error: error,
method: 'GET',
success: success
......@@ -213,39 +234,37 @@ $(document).ready(function () {
.on('click', '.btn-push-auth', function () {
var $this = $(this);
var asset_id = $this.data('asset');
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
var theUrl = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
theUrl = theUrl.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) {
var task_id = data.task;
showCeleryTaskLog(task_id);
var taskId = data.task;
showCeleryTaskLog(taskId);
};
var error = function (data) {
alert(data)
};
requestApi({
url: the_url,
url: theUrl,
method: 'GET',
success: success,
error: error
})
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var theUrl = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
showCeleryTaskLog(task_id);
var taskId = data.task;
showCeleryTaskLog(taskId);
};
requestApi({
url: the_url,
url: theUrl,
error: error,
method: 'GET',
success: success
});
})
</script>
{% endblock %}
......@@ -2,11 +2,6 @@
{% 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">
......
......@@ -23,6 +23,8 @@ router.register(r'asset-users', api.AssetUserViewSet, 'asset-user')
router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
router.register(r'system-users-assets-relations', api.SystemUserAssetRelationViewSet, 'system-users-assets-relation')
router.register(r'system-users-nodes-relations', api.SystemUserNodeRelationViewSet, 'system-users-nodes-relation')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
......
......@@ -11,4 +11,4 @@ class FTPLogViewSet(OrgModelViewSet):
model = FTPLog
serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)
http_method_names = ['get', 'post', 'head', 'options']
# Generated by Django 2.2.7 on 2019-12-02 02:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audits', '0006_auto_20190726_1753'),
]
operations = [
migrations.AlterField(
model_name='ftplog',
name='remote_addr',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Remote addr'),
),
migrations.AlterField(
model_name='operatelog',
name='remote_addr',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Remote addr'),
),
migrations.AlterField(
model_name='passwordchangelog',
name='remote_addr',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Remote addr'),
),
]
......@@ -16,7 +16,7 @@ __all__ = [
class FTPLog(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User'))
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
system_user = models.CharField(max_length=128, verbose_name=_("System user"))
operate = models.CharField(max_length=16, verbose_name=_("Operate"))
......@@ -39,7 +39,7 @@ class OperateLog(OrgModelMixin):
action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action"))
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
resource = models.CharField(max_length=128, verbose_name=_("Resource"))
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
datetime = models.DateTimeField(auto_now=True)
def __str__(self):
......@@ -50,7 +50,7 @@ class PasswordChangeLog(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User'))
change_by = models.CharField(max_length=128, verbose_name=_("Change by"))
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
datetime = models.DateTimeField(auto_now=True)
def __str__(self):
......
......@@ -13,8 +13,8 @@ from common.utils import get_request_ip, get_logger, get_syslogger
from users.models import User
from authentication.signals import post_auth_failed, post_auth_success
from terminal.models import Session, Command
from terminal.backends.command.serializers import SessionCommandSerializer
from . import models, serializers
from common.utils.encode import model_to_json
from . import models
from .tasks import write_login_log_async
logger = get_logger(__name__)
......@@ -51,7 +51,10 @@ def create_operate_log(action, sender, resource):
@receiver(post_save, dispatch_uid="my_unique_identifier")
def on_object_created_or_update(sender, instance=None, created=False, **kwargs):
def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs):
if instance._meta.object_name == 'User' and \
update_fields and 'last_login' in update_fields:
return
if created:
action = models.OperateLog.ACTION_CREATE
else:
......@@ -79,27 +82,20 @@ def on_user_change_password(sender, instance=None, **kwargs):
def on_audits_log_create(sender, instance=None, **kwargs):
if sender == models.UserLoginLog:
category = "login_log"
serializer = serializers.LoginLogSerializer
elif sender == models.FTPLog:
serializer = serializers.FTPLogSerializer
category = "ftp_log"
elif sender == models.OperateLog:
category = "operation_log"
serializer = serializers.OperateLogSerializer
elif sender == models.PasswordChangeLog:
category = "password_change_log"
serializer = serializers.PasswordChangeLogSerializer
elif sender == Session:
category = "host_session_log"
serializer = serializers.SessionAuditSerializer
elif sender == Command:
category = "session_command_log"
serializer = SessionCommandSerializer
else:
return
s = serializer(instance=instance)
data = json_render.render(s.data).decode(errors='ignore')
data = model_to_json(instance)
msg = "{} - {}".format(category, data)
sys_logger.info(msg)
......
......@@ -6,7 +6,7 @@ from django.conf import settings
from celery import shared_task
from ops.celery.decorator import register_as_period_task
from .models import UserLoginLog
from .models import UserLoginLog, OperateLog
from .utils import write_login_log
......@@ -22,6 +22,18 @@ def clean_login_log_period():
UserLoginLog.objects.filter(datetime__lt=expired_day).delete()
@register_as_period_task(interval=3600*24)
@shared_task
def clean_operation_log_period():
now = timezone.now()
try:
days = int(settings.LOGIN_LOG_KEEP_DAYS)
except ValueError:
days = 90
expired_day = now - datetime.timedelta(days=days)
OperateLog.objects.filter(datetime__lt=expired_day).delete()
@shared_task
def write_login_log_async(*args, **kwargs):
write_login_log(*args, **kwargs)
......@@ -5,8 +5,6 @@
{% load common_tags %}
{% block custom_head_css_js %}
<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>
<style>
#search_btn {
margin-bottom: 0;
......@@ -14,6 +12,9 @@
.form-control {
height: 30px;
}
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single {
height: 30px !important;
}
</style>
{% endblock %}
......
......@@ -5,8 +5,6 @@
{% load common_tags %}
{% block custom_head_css_js %}
<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>
<style>
#search_btn {
margin-bottom: 0;
......
......@@ -5,8 +5,6 @@
{% load common_tags %}
{% block custom_head_css_js %}
<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>
<style>
#search_btn {
margin-bottom: 0;
......
......@@ -109,7 +109,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
class AccessTokenAuthentication(authentication.BaseAuthentication):
keyword = 'Bearer'
expiration = settings.TOKEN_EXPIRATION or 3600
# expiration = settings.TOKEN_EXPIRATION or 3600
model = get_user_model()
def authenticate(self, request):
......
# coding:utf-8
#
import warnings
import ldap
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django_auth_ldap.backend import _LDAPUser, LDAPBackend
from django_auth_ldap.backend import _LDAPUser, LDAPBackend, LDAPSettings
from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion
from users.utils import construct_user_email
......@@ -17,7 +18,6 @@ class LDAPAuthorizationBackend(LDAPBackend):
"""
Override this class to override _LDAPUser to LDAPUser
"""
@staticmethod
def user_can_authenticate(user):
"""
......@@ -27,18 +27,26 @@ class LDAPAuthorizationBackend(LDAPBackend):
is_valid = getattr(user, 'is_valid', None)
return is_valid or is_valid is None
def authenticate(self, request=None, username=None, password=None, **kwargs):
def pre_check(self, username):
if not settings.AUTH_LDAP:
return False
logger.info('Authentication LDAP backend')
if not username:
logger.info('Authenticate failed: username is None')
return None
return False
if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS:
user_model = self.get_user_model()
exist = user_model.objects.filter(username=username).exists()
if not exist:
msg = 'Authentication failed: user ({}) is not in the user list'
logger.info(msg.format(username))
return None
return False
return True
def authenticate(self, request=None, username=None, password=None, **kwargs):
match = self.pre_check(username)
if not match:
return None
ldap_user = LDAPUser(self, username=username.strip(), request=request)
user = self.authenticate_ldap_user(ldap_user, password)
logger.info('Authenticate user: {}'.format(user))
......
......@@ -25,7 +25,8 @@ __all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
class OpenIDLoginView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
redirect_uri = settings.BASE_SITE_URL + str(settings.LOGIN_COMPLETE_URL)
redirect_uri = settings.BASE_SITE_URL + \
str(settings.AUTH_OPENID_LOGIN_COMPLETE_URL)
nonce = Nonce(
redirect_uri=redirect_uri,
next_path=self.request.GET.get('next')
......
......@@ -141,7 +141,7 @@ class AuthMixin:
)
def check_user_login_confirm_if_need(self, user):
if not settings.CONFIG.LOGIN_CONFIRM_ENABLE:
if not settings.LOGIN_CONFIRM_ENABLE:
return
confirm_setting = user.get_login_confirm_setting()
if self.request.session.get('auth_confirm') or not confirm_setting:
......
......@@ -60,7 +60,8 @@ class UserLoginView(mixins.AuthMixin, FormView):
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
if settings.AUTH_OPENID and not self.request.GET.get('admin', 0):
query_string = request.GET.urlencode()
login_url = "{}?{}".format(settings.LOGIN_URL, query_string)
openid_login_url = reverse_lazy("authentication:openid:openid-login")
login_url = "{}?{}".format(openid_login_url, query_string)
return redirect(login_url)
request.session.set_test_cookie()
return super().get(request, *args, **kwargs)
......
# -*- coding: utf-8 -*-
#
......@@ -7,7 +7,7 @@ from rest_framework.serializers import ValidationError
from django.core.cache import cache
import logging
from . import const
from common import const
__all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"]
......
......@@ -9,7 +9,7 @@ import unicodecsv
from rest_framework.parsers import BaseParser
from rest_framework.exceptions import ParseError
from ..utils import get_logger
from common.utils import get_logger
logger = get_logger(__file__)
......
......@@ -9,7 +9,7 @@ from six import BytesIO
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import encoders, json
from ..utils import get_logger
from common.utils import get_logger
logger = get_logger(__file__)
......
# -*- coding: utf-8 -*-
#
from rest_framework_nested.routers import NestedMixin
from rest_framework_bulk.routes import BulkRouter
__all__ = ['BulkNestDefaultRouter']
class BulkNestDefaultRouter(NestedMixin, BulkRouter):
pass
......@@ -5,7 +5,10 @@ from rest_framework import serializers
from django.utils import six
__all__ = ['StringIDField', 'StringManyToManyField', 'ChoiceDisplayField']
__all__ = [
'StringIDField', 'StringManyToManyField', 'ChoiceDisplayField',
'CustomMetaDictField'
]
class StringIDField(serializers.Field):
......@@ -39,3 +42,63 @@ class DictField(serializers.DictField):
if not value or not isinstance(value, dict):
value = {}
return super().to_representation(value)
class CustomMetaDictField(serializers.DictField):
"""
In use:
RemoteApp params field
CommandStorage meta field
ReplayStorage meta field
"""
type_map_fields = {}
default_type = None
need_convert_key = False
def filter_attribute(self, attribute, instance):
fields = self.type_map_fields.get(instance.type, [])
for field in fields:
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
def convert_value_key(self, dictionary, value):
if not self.need_convert_key:
# remote app
return value
tp = dictionary.get('type')
_value = {}
for k, v in value.items():
prefix = '{}_'.format(tp)
_k = k
if k.lower().startswith(prefix):
_k = k.lower().split(prefix, 1)[1]
_k = _k.upper()
_value[_k] = value[k]
return _value
def filter_value_key(self, dictionary, value):
tp = dictionary.get('type', self.default_type)
fields = self.type_map_fields.get(tp, [])
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.convert_value_key(dictionary, value)
value = self.filter_value_key(dictionary, value)
return value
......@@ -3,11 +3,11 @@
from django.http import JsonResponse
from rest_framework.settings import api_settings
from ..filters import IDSpmFilter, CustomFilter
from common.drf.filters import IDSpmFilter, CustomFilter
__all__ = [
"JSONResponseMixin", "CommonApiMixin",
"IDSpmFilterMixin", "CommonApiMixin",
"IDSpmFilterMixin",
]
......
......@@ -7,10 +7,13 @@ from django.conf import settings
from django.dispatch import receiver
from django.core.signals import request_finished
from django.db import connection
from django.conf import LazySettings
from django.db.utils import ProgrammingError, OperationalError
from common.utils import get_logger
from .local import thread_local
from .signals import django_ready
pattern = re.compile(r'FROM `(\w+)`')
logger = get_logger(__name__)
......@@ -62,7 +65,15 @@ if settings.DEBUG and DEBUG_DB:
request_finished.connect(on_request_finished_logging_db_query)
@receiver(django_ready)
def monkey_patch_settings(sender, **kwargs):
def monkey_patch_getattr(self, name):
val = getattr(self._wrapped, name)
if callable(val):
val = val()
return val
try:
LazySettings.__getattr__ = monkey_patch_getattr
except (ProgrammingError, OperationalError):
pass
from django.core.mail import send_mail
from django.conf import settings
from celery import shared_task
from .utils import get_logger
......
......@@ -9,6 +9,7 @@ import uuid
from functools import wraps
import time
import ipaddress
import psutil
UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}')
......@@ -234,3 +235,10 @@ class lazyproperty:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
def get_disk_usage():
partitions = psutil.disk_partitions()
mount_points = [p.mountpoint for p in partitions]
usages = {p: psutil.disk_usage(p) for p in mount_points}
return usages
......@@ -35,20 +35,3 @@ def date_expired_default():
years = 70
return timezone.now() + timezone.timedelta(days=365*years)
def get_command_storage_setting():
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
value = settings.TERMINAL_COMMAND_STORAGE
if not value:
return default
value.update(default)
return value
def get_replay_storage_setting():
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
value = settings.TERMINAL_REPLAY_STORAGE
if not value:
return default
value.update(default)
return value
# -*- coding: utf-8 -*-
#
import re
import json
from six import string_types
import base64
import os
import time
import hashlib
from io import StringIO
from itertools import chain
import paramiko
import sshpubkeys
......@@ -15,6 +17,8 @@ from itsdangerous import (
BadSignature, SignatureExpired
)
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.fields.files import FileField
from .http import http_date
......@@ -186,3 +190,35 @@ def get_signer():
def ensure_last_char_is_ascii(data):
remain = ''
secret_pattern = re.compile(r'password|secret|key', re.IGNORECASE)
def model_to_dict_pro(instance, fields=None, exclude=None):
from ..fields.model import EncryptMixin
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields):
if not getattr(f, 'editable', False):
continue
if fields and f.name not in fields:
continue
if exclude and f.name in exclude:
continue
if isinstance(f, FileField):
continue
if isinstance(f, EncryptMixin):
continue
if secret_pattern.search(f.name):
continue
value = f.value_from_object(instance)
data[f.name] = value
return data
def model_to_json(instance, sort_keys=True, indent=2, cls=None):
data = model_to_dict_pro(instance)
if cls is None:
cls = DjangoJSONEncoder
return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls)
# -*- coding: utf-8 -*-
#
from .ipdb import *
from .utils import *
# -*- coding: utf-8 -*-
#
import os
from django.utils.translation import ugettext as _
import ipdb
__all__ = ['get_ip_city']
ipip_db = None
def get_ip_city(ip):
global ipip_db
if not ip or not isinstance(ip, str):
return _("Invalid ip")
if ':' in ip:
return 'IPv6'
if ipip_db is None:
ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb')
ipip_db = ipdb.City(ipip_db_path)
info = list(set(ipip_db.find(ip, 'CN')))
if '' in info:
info.remove('')
return ' '.join(info)
\ No newline at end of file
return ' '.join(info)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
"""
配置分类:
1. Django使用的配置文件,写到settings中
2. 程序需要, 用户不需要更改的写到settings中
3. 程序需要, 用户需要更改的写到本config中
"""
import os
import sys
import types
......@@ -8,6 +14,7 @@ import errno
import json
import yaml
from importlib import import_module
from django.urls import reverse_lazy
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(BASE_DIR)
......@@ -29,6 +36,10 @@ def import_string(dotted_path):
) from err
class DoesNotExist(Exception):
pass
class Config(dict):
"""Works exactly like a dict but provides ways to fill it from files
or special dictionaries. There are two common patterns to populate the
......@@ -72,34 +83,233 @@ class Config(dict):
the application's :attr:`~flask.Flask.root_path`.
:param defaults: an optional dictionary of default values
"""
defaults = {
# Django Config
'SECRET_KEY': '',
'BOOTSTRAP_TOKEN': '',
'DEBUG': True,
'SITE_URL': 'http://localhost:8080',
'LOG_LEVEL': 'DEBUG',
'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'),
'DB_ENGINE': 'mysql',
'DB_NAME': 'jumpserver',
'DB_HOST': '127.0.0.1',
'DB_PORT': 3306,
'DB_USER': 'root',
'DB_PASSWORD': '',
'REDIS_HOST': '127.0.0.1',
'REDIS_PORT': 6379,
'REDIS_PASSWORD': '',
'REDIS_DB_CELERY': 3,
'REDIS_DB_CACHE': 4,
'REDIS_DB_SESSION': 5,
'REDIS_DB_WS': 6,
'CAPTCHA_TEST_MODE': None,
'TOKEN_EXPIRATION': 3600 * 24,
'DISPLAY_PER_PAGE': 25,
'DEFAULT_EXPIRED_YEARS': 70,
'SESSION_COOKIE_DOMAIN': None,
'CSRF_COOKIE_DOMAIN': None,
'SESSION_COOKIE_AGE': 3600 * 24,
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
'LOGIN_URL': reverse_lazy('authentication:login'),
# Custom Config
# Auth LDAP settings
'AUTH_LDAP': False,
'AUTH_LDAP_SERVER_URI': 'ldap://localhost:389',
'AUTH_LDAP_BIND_DN': 'cn=admin,dc=jumpserver,dc=org',
'AUTH_LDAP_BIND_PASSWORD': '',
'AUTH_LDAP_SEARCH_OU': 'ou=tech,dc=jumpserver,dc=org',
'AUTH_LDAP_SEARCH_FILTER': '(cn=%(user)s)',
'AUTH_LDAP_START_TLS': False,
'AUTH_LDAP_USER_ATTR_MAP': {"username": "cn", "name": "sn", "email": "mail"},
'AUTH_LDAP_CONNECT_TIMEOUT': 30,
'AUTH_LDAP_SEARCH_PAGED_SIZE': 1000,
'AUTH_LDAP_SYNC_IS_PERIODIC': False,
'AUTH_LDAP_SYNC_INTERVAL': None,
'AUTH_LDAP_SYNC_CRONTAB': None,
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1,
'AUTH_OPENID': False,
'BASE_SITE_URL': 'http://localhost:8080',
'AUTH_OPENID_SERVER_URL': 'http://openid',
'AUTH_OPENID_REALM_NAME': 'jumpserver',
'AUTH_OPENID_CLIENT_ID': 'jumpserver',
'AUTH_OPENID_CLIENT_SECRET': '',
'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True,
'AUTH_OPENID_SHARE_SESSION': True,
'AUTH_RADIUS': False,
'RADIUS_SERVER': 'localhost',
'RADIUS_PORT': 1812,
'RADIUS_SECRET': '',
'RADIUS_ENCRYPT_PASSWORD': True,
'OTP_IN_RADIUS': False,
'OTP_VALID_WINDOW': 2,
'OTP_ISSUER_NAME': 'Jumpserver',
'EMAIL_SUFFIX': 'jumpserver.org',
'TERMINAL_PASSWORD_AUTH': True,
'TERMINAL_PUBLIC_KEY_AUTH': True,
'TERMINAL_HEARTBEAT_INTERVAL': 20,
'TERMINAL_ASSET_LIST_SORT_BY': 'hostname',
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',
'TERMINAL_SESSION_KEEP_DURATION': 9999,
'TERMINAL_HOST_KEY': '',
'TERMINAL_TELNET_REGEX': '',
'TERMINAL_COMMAND_STORAGE': {},
'SECURITY_MFA_AUTH': False,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_VIEW_AUTH_NEED_MFA': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30,
'SECURITY_PASSWORD_EXPIRATION_TIME': 9999,
'SECURITY_PASSWORD_MIN_LENGTH': 6,
'SECURITY_PASSWORD_UPPER_CASE': False,
'SECURITY_PASSWORD_LOWER_CASE': False,
'SECURITY_PASSWORD_NUMBER': False,
'SECURITY_PASSWORD_SPECIAL_CHAR': False,
'HTTP_BIND_HOST': '0.0.0.0',
'HTTP_LISTEN_PORT': 8080,
'WS_LISTEN_PORT': 8070,
'LOGIN_LOG_KEEP_DAYS': 90,
'ASSETS_PERM_CACHE_TIME': 3600 * 24,
'SECURITY_MFA_VERIFY_TTL': 3600,
'ASSETS_PERM_CACHE_ENABLE': False,
'SYSLOG_ADDR': '', # '192.168.0.1:514'
'SYSLOG_FACILITY': 'user',
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
'FLOWER_URL': "127.0.0.1:5555",
'DEFAULT_ORG_SHOW_ALL_USERS': True,
'PERIOD_TASK_ENABLE': True,
'FORCE_SCRIPT_NAME': '',
'LOGIN_CONFIRM_ENABLE': False,
'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False,
}
def __init__(self, root_path=None, defaults=None):
self.defaults = defaults or {}
self.root_path = root_path
super().__init__({})
def convert_type(self, k, v):
default_value = self.defaults.get(k)
if default_value is None:
return v
tp = type(default_value)
# 对bool特殊处理
if tp is bool and isinstance(v, str):
if v in ("true", "True", "1"):
return True
else:
return False
if tp in [list, dict] and isinstance(v, str):
try:
v = json.loads(v)
return v
except json.JSONDecodeError:
return v
def from_envvar(self, variable_name, silent=False):
"""Loads a configuration from an environment variable pointing to
a configuration file. This is basically just a shortcut with nicer
error messages for this line of code::
try:
if tp in [list, dict]:
v = json.loads(v)
else:
v = tp(v)
except Exception:
pass
return v
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
:param variable_name: name of the environment variable
:param silent: set to ``True`` if you want silent failure for missing
files.
:return: bool. ``True`` if able to load config, ``False`` otherwise.
"""
rv = os.environ.get(variable_name)
if not rv:
if silent:
return False
raise RuntimeError('The environment variable %r is not set '
'and as such configuration could not be '
'loaded. Set this variable and make it '
'point to a configuration file' %
variable_name)
return self.from_pyfile(rv, silent=silent)
def get_from_config(self, item):
try:
value = super().__getitem__(item)
except KeyError:
value = None
return value
def get_from_env(self, item):
value = os.environ.get(item, None)
if value is not None:
value = self.convert_type(item, value)
return value
def get(self, item):
# 再从配置文件中获取
value = self.get_from_config(item)
if value is not None:
return value
# 其次从环境变量来
value = self.get_from_env(item)
if value is not None:
return value
return self.defaults.get(item)
def __getitem__(self, item):
return self.get(item)
def __getattr__(self, item):
return self.get(item)
class DynamicConfig:
def __init__(self, static_config):
self.static_config = static_config
self.db_setting = None
def __getitem__(self, item):
return self.dynamic(item)
def __getattr__(self, item):
return self.dynamic(item)
def dynamic(self, item):
return lambda: self.get(item)
def LOGIN_URL(self):
auth_openid = self.get('AUTH_OPENID')
if auth_openid:
return reverse_lazy("authentication:openid:openid-login")
return self.get('LOGIN_URL')
def AUTHENTICATION_BACKENDS(self):
backends = [
'authentication.backends.pubkey.PublicKeyAuthBackend',
'django.contrib.auth.backends.ModelBackend',
]
if self.get('AUTH_LDAP'):
backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend')
if self.get('AUTH_OPENID'):
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend')
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend')
if self.get('AUTH_RADIUS'):
backends.insert(0, 'authentication.backends.radius.RadiusBackend')
return backends
def get_from_db(self, item):
if self.db_setting is not None:
value = self.db_setting.get(item)
if value is not None:
return value
return None
def get(self, item):
# 先从数据库中获取
value = self.get_from_db(item)
if value is not None:
return value
return self.static_config.get(item)
class ConfigManager:
config_class = Config
def __init__(self, root_path=None):
self.root_path = root_path
self.config = self.config_class()
def from_pyfile(self, filename, silent=False):
"""Updates the values in the config from a Python file. This function
......@@ -162,7 +372,7 @@ class Config(dict):
obj = import_string(obj)
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
self.config[key] = getattr(obj, key)
def from_json(self, filename, silent=False):
"""Updates the values in the config from a JSON file. This function
......@@ -224,218 +434,50 @@ class Config(dict):
for mapping in mappings:
for (key, value) in mapping:
if key.isupper():
self[key] = value
self.config[key] = value
return True
def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
"""Returns a dictionary containing a subset of configuration options
that match the specified namespace/prefix. Example usage::
app.config['IMAGE_STORE_TYPE'] = 'fs'
app.config['IMAGE_STORE_PATH'] = '/var/app/images'
app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
image_store_config = app.config.get_namespace('IMAGE_STORE_')
The resulting dictionary `image_store_config` would look like::
{
'types': 'fs',
'path': '/var/app/images',
'base_url': 'http://img.website.com'
}
This is often useful when configuration options map directly to
keyword arguments in functions or class constructors.
:param namespace: a configuration namespace
:param lowercase: a flag indicating if the keys of the resulting
dictionary should be lowercase
:param trim_namespace: a flag indicating if the keys of the resulting
dictionary should not include the namespace
.. versionadded:: 0.11
"""
rv = {}
for k, v in self.items():
if not k.startswith(namespace):
continue
if trim_namespace:
key = k[len(namespace):]
else:
key = k
if lowercase:
key = key.lower()
rv[key] = v
return rv
def convert_type(self, k, v):
default_value = self.defaults.get(k)
if default_value is None:
return v
tp = type(default_value)
# 对bool特殊处理
if tp is bool and isinstance(v, str):
if v in ("true", "True", "1"):
return True
else:
return False
if tp in [list, dict] and isinstance(v, str):
try:
v = json.loads(v)
return v
except json.JSONDecodeError:
return v
def load_from_object(self):
sys.path.insert(0, PROJECT_DIR)
try:
if tp in [list, dict]:
v = json.loads(v)
else:
v = tp(v)
except Exception:
from config import config as c
self.from_object(c)
return self.config
except ImportError:
pass
return v
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
return None
def __getitem__(self, item):
# 先从设置的来
try:
value = super().__getitem__(item)
except KeyError:
value = None
if value is not None:
return value
# 其次从环境变量来
value = os.environ.get(item, None)
if value is not None:
return self.convert_type(item, value)
return self.defaults.get(item)
def __getattr__(self, item):
return self.__getitem__(item)
defaults = {
'SECRET_KEY': '',
'BOOTSTRAP_TOKEN': '',
'DEBUG': True,
'SITE_URL': 'http://localhost',
'LOG_LEVEL': 'DEBUG',
'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'),
'DB_ENGINE': 'mysql',
'DB_NAME': 'jumpserver',
'DB_HOST': '127.0.0.1',
'DB_PORT': 3306,
'DB_USER': 'root',
'DB_PASSWORD': '',
'REDIS_HOST': '127.0.0.1',
'REDIS_PORT': 6379,
'REDIS_PASSWORD': '',
'REDIS_DB_CELERY': 3,
'REDIS_DB_CACHE': 4,
'REDIS_DB_SESSION': 5,
'REDIS_DB_WS': 6,
'CAPTCHA_TEST_MODE': None,
'TOKEN_EXPIRATION': 3600 * 24,
'DISPLAY_PER_PAGE': 25,
'DEFAULT_EXPIRED_YEARS': 70,
'SESSION_COOKIE_DOMAIN': None,
'CSRF_COOKIE_DOMAIN': None,
'SESSION_COOKIE_AGE': 3600 * 24,
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
'AUTH_OPENID': False,
'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True,
'AUTH_OPENID_SHARE_SESSION': True,
'OTP_VALID_WINDOW': 2,
'OTP_ISSUER_NAME': 'Jumpserver',
'EMAIL_SUFFIX': 'jumpserver.org',
'TERMINAL_PASSWORD_AUTH': True,
'TERMINAL_PUBLIC_KEY_AUTH': True,
'TERMINAL_HEARTBEAT_INTERVAL': 20,
'TERMINAL_ASSET_LIST_SORT_BY': 'hostname',
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',
'TERMINAL_SESSION_KEEP_DURATION': 9999,
'TERMINAL_HOST_KEY': '',
'TERMINAL_TELNET_REGEX': '',
'TERMINAL_COMMAND_STORAGE': {},
'SECURITY_MFA_AUTH': False,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_VIEW_AUTH_NEED_MFA': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30,
'SECURITY_PASSWORD_EXPIRATION_TIME': 9999,
'SECURITY_PASSWORD_MIN_LENGTH': 6,
'SECURITY_PASSWORD_UPPER_CASE': False,
'SECURITY_PASSWORD_LOWER_CASE': False,
'SECURITY_PASSWORD_NUMBER': False,
'SECURITY_PASSWORD_SPECIAL_CHAR': False,
'AUTH_RADIUS': False,
'RADIUS_SERVER': 'localhost',
'RADIUS_PORT': 1812,
'RADIUS_SECRET': '',
'RADIUS_ENCRYPT_PASSWORD': True,
'AUTH_LDAP_SEARCH_PAGED_SIZE': 1000,
'AUTH_LDAP_SYNC_IS_PERIODIC': False,
'AUTH_LDAP_SYNC_INTERVAL': None,
'AUTH_LDAP_SYNC_CRONTAB': None,
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1,
'HTTP_BIND_HOST': '0.0.0.0',
'HTTP_LISTEN_PORT': 8080,
'WS_LISTEN_PORT': 8070,
'LOGIN_LOG_KEEP_DAYS': 90,
'ASSETS_PERM_CACHE_TIME': 3600*24,
'SECURITY_MFA_VERIFY_TTL': 3600,
'ASSETS_PERM_CACHE_ENABLE': False,
'SYSLOG_ADDR': '', # '192.168.0.1:514'
'SYSLOG_FACILITY': 'user',
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
'FLOWER_URL': "127.0.0.1:5555",
'DEFAULT_ORG_SHOW_ALL_USERS': True,
'PERIOD_TASK_ENABLE': True,
'FORCE_SCRIPT_NAME': '',
'LOGIN_CONFIRM_ENABLE': False,
'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False,
'OTP_IN_RADIUS': False,
}
def load_from_object(config):
try:
from config import config as c
config.from_object(c)
return True
except ImportError:
pass
return False
def load_from_yml(config):
for i in ['config.yml', 'config.yaml']:
if not os.path.isfile(os.path.join(config.root_path, i)):
continue
loaded = config.from_yaml(i)
if loaded:
return True
return False
def load_user_config():
sys.path.insert(0, PROJECT_DIR)
config = Config(PROJECT_DIR, defaults)
loaded = load_from_object(config)
if not loaded:
loaded = load_from_yml(config)
if not loaded:
def load_from_yml(self):
for i in ['config.yml', 'config.yaml']:
if not os.path.isfile(os.path.join(self.root_path, i)):
continue
loaded = self.from_yaml(i)
if loaded:
return self.config
return False
@classmethod
def load_user_config(cls, root_path=None, config_class=None):
config_class = config_class or Config
cls.config_class = config_class
if not root_path:
root_path = PROJECT_DIR
manager = cls(root_path=root_path)
config = manager.load_from_object()
if config:
return config
config = manager.load_from_yml()
if config:
return config
msg = """
Error: No config file found.
You can run `cp config_example.yml config.yml`, and edit it.
"""
raise ImportError(msg)
return config
@classmethod
def get_dynamic_config(cls, config):
return DynamicConfig(config)
# -*- coding: utf-8 -*-
#
import os
from .conf import ConfigManager
__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC']
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(BASE_DIR)
VERSION = '1.5.5'
CONFIG = ConfigManager.load_user_config()
DYNAMIC = ConfigManager.get_dynamic_config(CONFIG)
......@@ -19,8 +19,8 @@ def jumpserver_processor(request):
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME,
'SECURITY_VIEW_AUTH_NEED_MFA': settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA,
'LOGIN_CONFIRM_ENABLE': settings.CONFIG.LOGIN_CONFIRM_ENABLE,
'SECURITY_VIEW_AUTH_NEED_MFA': settings.SECURITY_VIEW_AUTH_NEED_MFA,
'LOGIN_CONFIRM_ENABLE': settings.LOGIN_CONFIRM_ENABLE,
}
return context
......
# -*- coding: utf-8 -*-
#
from .base import *
from .logging import *
from .libs import *
from .auth import *
from .custom import *
from ._xpack import *
# -*- coding: utf-8 -*-
#
import os
from .. import const
from .base import INSTALLED_APPS, TEMPLATES
XPACK_DIR = os.path.join(const.BASE_DIR, 'xpack')
XPACK_ENABLED = os.path.isdir(XPACK_DIR)
XPACK_TEMPLATES_DIR = []
XPACK_CONTEXT_PROCESSOR = []
if XPACK_ENABLED:
from xpack.utils import get_xpack_templates_dir, get_xpack_context_processor
INSTALLED_APPS.append('xpack.apps.XpackConfig')
XPACK_TEMPLATES_DIR = get_xpack_templates_dir(const.BASE_DIR)
XPACK_CONTEXT_PROCESSOR = get_xpack_context_processor()
TEMPLATES[0]['DIRS'].extend(XPACK_TEMPLATES_DIR)
TEMPLATES[0]['OPTIONS']['context_processors'].extend(XPACK_CONTEXT_PROCESSOR)
# -*- coding: utf-8 -*-
#
import os
import ldap
from django.urls import reverse_lazy
from ..const import CONFIG, DYNAMIC, PROJECT_DIR
# OTP settings
OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME
OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW
# Auth LDAP settings
AUTH_LDAP = DYNAMIC.AUTH_LDAP
AUTH_LDAP_SERVER_URI = DYNAMIC.AUTH_LDAP_SERVER_URI
AUTH_LDAP_BIND_DN = DYNAMIC.AUTH_LDAP_BIND_DN
AUTH_LDAP_BIND_PASSWORD = DYNAMIC.AUTH_LDAP_BIND_PASSWORD
AUTH_LDAP_SEARCH_OU = DYNAMIC.AUTH_LDAP_SEARCH_OU
AUTH_LDAP_SEARCH_FILTER = DYNAMIC.AUTH_LDAP_SEARCH_FILTER
AUTH_LDAP_START_TLS = DYNAMIC.AUTH_LDAP_START_TLS
AUTH_LDAP_USER_ATTR_MAP = DYNAMIC.AUTH_LDAP_USER_ATTR_MAP
AUTH_LDAP_GLOBAL_OPTIONS = {
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
ldap.OPT_REFERRALS: CONFIG.AUTH_LDAP_OPTIONS_OPT_REFERRALS
}
LDAP_CERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ca.pem")
if os.path.isfile(LDAP_CERT_FILE):
AUTH_LDAP_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CACERTFILE] = LDAP_CERT_FILE
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
# AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
# )
AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_TIMEOUT: CONFIG.AUTH_LDAP_CONNECT_TIMEOUT
}
AUTH_LDAP_CACHE_TIMEOUT = 1
AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_SEARCH_PAGED_SIZE
AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC
AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL
AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
# openid
# Auth OpenID settings
BASE_SITE_URL = CONFIG.BASE_SITE_URL
AUTH_OPENID = CONFIG.AUTH_OPENID
AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL
AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME
AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID
AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
AUTH_OPENID_LOGIN_URL = reverse_lazy("authentication:openid:openid-login")
AUTH_OPENID_LOGIN_COMPLETE_URL = reverse_lazy("authentication:openid:openid-login-complete")
# Radius Auth
AUTH_RADIUS = CONFIG.AUTH_RADIUS
AUTH_RADIUS_BACKEND = 'authentication.backends.radius.RadiusBackend'
RADIUS_SERVER = CONFIG.RADIUS_SERVER
RADIUS_PORT = CONFIG.RADIUS_PORT
RADIUS_SECRET = CONFIG.RADIUS_SECRET
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION
LOGIN_CONFIRM_ENABLE = CONFIG.LOGIN_CONFIRM_ENABLE
OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS
"""
Django settings for jumpserver project.
Generated by 'django-admin startproject' using Django 1.10.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
import sys
import ldap
from django.urls import reverse_lazy
from . import const
from .conf import load_user_config
from .. import const
from ..const import CONFIG, DYNAMIC
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(BASE_DIR)
sys.path.append(PROJECT_DIR)
CONFIG = load_user_config()
LOG_DIR = os.path.join(PROJECT_DIR, 'logs')
JUMPSERVER_LOG_FILE = os.path.join(LOG_DIR, 'jumpserver.log')
ANSIBLE_LOG_FILE = os.path.join(LOG_DIR, 'ansible.log')
GUNICORN_LOG_FILE = os.path.join(LOG_DIR, 'gunicorn.log')
VERSION = const.VERSION
if not os.path.isdir(LOG_DIR):
os.makedirs(LOG_DIR)
BASE_DIR = const.BASE_DIR
PROJECT_DIR = const.PROJECT_DIR
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
......@@ -47,7 +23,7 @@ BOOTSTRAP_TOKEN = CONFIG.BOOTSTRAP_TOKEN
DEBUG = CONFIG.DEBUG
# Absolute url for some case, for example email link
SITE_URL = CONFIG.SITE_URL
SITE_URL = DYNAMIC.SITE_URL
# LOG LEVEL
LOG_LEVEL = CONFIG.LOG_LEVEL
......@@ -89,17 +65,6 @@ INSTALLED_APPS = [
]
XPACK_DIR = os.path.join(BASE_DIR, 'xpack')
XPACK_ENABLED = os.path.isdir(XPACK_DIR)
XPACK_TEMPLATES_DIR = []
XPACK_CONTEXT_PROCESSOR = []
if XPACK_ENABLED:
from xpack.utils import get_xpack_templates_dir, get_xpack_context_processor
INSTALLED_APPS.append('xpack.apps.XpackConfig')
XPACK_TEMPLATES_DIR = get_xpack_templates_dir(BASE_DIR)
XPACK_CONTEXT_PROCESSOR = get_xpack_context_processor()
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
......@@ -122,7 +87,7 @@ ROOT_URLCONF = 'jumpserver.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), *XPACK_TEMPLATES_DIR],
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
......@@ -136,14 +101,12 @@ TEMPLATES = [
'django.template.context_processors.media',
'jumpserver.context_processor.jumpserver_processor',
'orgs.context_processor.org_processor',
*XPACK_CONTEXT_PROCESSOR,
],
},
},
]
WSGI_APPLICATION = 'jumpserver.wsgi.application'
ASGI_APPLICATION = 'jumpserver.routing.application'
LOGIN_REDIRECT_URL = reverse_lazy('index')
LOGIN_URL = reverse_lazy('authentication:login')
......@@ -205,112 +168,6 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
# Logging setting
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'main': {
'datefmt': '%Y-%m-%d %H:%M:%S',
'format': '%(asctime)s [%(module)s %(levelname)s] %(message)s',
},
'simple': {
'format': '%(levelname)s %(message)s'
},
'syslog': {
'format': 'jumpserver: %(message)s'
},
'msg': {
'format': '%(message)s'
}
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'main'
},
'file': {
'encoding': 'utf8',
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'maxBytes': 1024*1024*100,
'backupCount': 7,
'formatter': 'main',
'filename': JUMPSERVER_LOG_FILE,
},
'ansible_logs': {
'encoding': 'utf8',
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'main',
'maxBytes': 1024*1024*100,
'backupCount': 7,
'filename': ANSIBLE_LOG_FILE,
},
'syslog': {
'level': 'INFO',
'class': 'logging.NullHandler',
'formatter': 'syslog'
},
},
'loggers': {
'django': {
'handlers': ['null'],
'propagate': False,
'level': LOG_LEVEL,
},
'django.request': {
'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL,
'propagate': False,
},
'django.server': {
'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL,
'propagate': False,
},
'jumpserver': {
'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL,
},
'ops.ansible_api': {
'handlers': ['console', 'ansible_logs'],
'level': LOG_LEVEL,
},
'django_auth_ldap': {
'handlers': ['console', 'file'],
'level': "INFO",
},
'jms.audits': {
'handlers': ['syslog'],
'level': 'INFO'
},
# 'django.db': {
# 'handlers': ['console', 'file'],
# 'level': 'DEBUG'
# }
}
}
SYSLOG_ENABLE = False
if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
host, port = CONFIG.SYSLOG_ADDR.split(':')
SYSLOG_ENABLE = True
LOGGING['handlers']['syslog'].update({
'class': 'logging.handlers.SysLogHandler',
'facility': CONFIG.SYSLOG_FACILITY,
'address': (host, int(port)),
})
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
# LANGUAGE_CODE = 'en'
......@@ -353,66 +210,17 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/'
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ]
# Email config
EMAIL_HOST = 'smtp.jumpserver.org'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'noreply@jumpserver.org'
EMAIL_HOST_PASSWORD = ''
EMAIL_FROM = ''
EMAIL_RECIPIENT = ''
EMAIL_USE_SSL = False
EMAIL_USE_TLS = False
EMAIL_SUBJECT_PREFIX = '[JMS] '
# Email custom content
EMAIL_CUSTOM_USER_CREATED_SUBJECT = ''
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = ''
EMAIL_CUSTOM_USER_CREATED_BODY = ''
EMAIL_CUSTOM_USER_CREATED_SIGNATURE = ''
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': (
'common.permissions.IsOrgAdmin',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'common.renders.JMSCSVRender',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
'common.parsers.JMSCSVParser',
'rest_framework.parsers.FileUploadParser',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
'authentication.backends.api.AccessKeyAuthentication',
'authentication.backends.api.AccessTokenAuthentication',
'authentication.backends.api.PrivateTokenAuthentication',
'authentication.backends.api.SignatureAuthentication',
'authentication.backends.api.SessionAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
),
'DEFAULT_METADATA_CLASS': 'common.drfmetadata.SimpleMetadataWithFilters',
'ORDERING_PARAM': "order",
'SEARCH_PARAM': "search",
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S %z'],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
# 'PAGE_SIZE': 15
}
EMAIL_HOST = DYNAMIC.EMAIL_HOST
EMAIL_PORT = DYNAMIC.EMAIL_PORT
EMAIL_HOST_USER = DYNAMIC.EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = DYNAMIC.EMAIL_HOST_PASSWORD
EMAIL_FROM = DYNAMIC.EMAIL_FROM
EMAIL_RECIPIENT = DYNAMIC.EMAIL_RECIPIENT
EMAIL_USE_SSL = DYNAMIC.EMAIL_USE_SSL
EMAIL_USE_TLS = DYNAMIC.EMAIL_USE_TLS
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'authentication.backends.pubkey.PublicKeyAuthBackend',
]
AUTHENTICATION_BACKENDS = DYNAMIC.AUTHENTICATION_BACKENDS
# Custom User Auth model
AUTH_USER_MODEL = 'users.User'
......@@ -421,106 +229,6 @@ AUTH_USER_MODEL = 'users.User'
FILE_UPLOAD_PERMISSIONS = 0o644
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
# OTP settings
OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME
OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW
# Auth LDAP settings
AUTH_LDAP = False
AUTH_LDAP_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_SEARCH_PAGED_SIZE
AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC
AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL
AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
AUTH_LDAP_SERVER_URI = 'ldap://localhost:389'
AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org'
AUTH_LDAP_BIND_PASSWORD = ''
AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org'
AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)'
AUTH_LDAP_START_TLS = False
AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"}
AUTH_LDAP_GLOBAL_OPTIONS = {
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
ldap.OPT_REFERRALS: CONFIG.AUTH_LDAP_OPTIONS_OPT_REFERRALS
}
LDAP_CERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ca.pem")
if os.path.isfile(LDAP_CERT_FILE):
AUTH_LDAP_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CACERTFILE] = LDAP_CERT_FILE
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
# AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
# )
AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_TIMEOUT: 30
}
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 1
AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_BACKEND = 'authentication.backends.ldap.LDAPAuthorizationBackend'
if AUTH_LDAP:
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
# openid
# Auth OpenID settings
BASE_SITE_URL = CONFIG.BASE_SITE_URL
AUTH_OPENID = CONFIG.AUTH_OPENID
AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL
AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME
AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID
AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
AUTH_OPENID_BACKENDS = [
'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend',
'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend',
]
if AUTH_OPENID:
LOGIN_URL = reverse_lazy("authentication:openid:openid-login")
LOGIN_COMPLETE_URL = reverse_lazy("authentication:openid:openid-login-complete")
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[0])
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[1])
# Radius Auth
AUTH_RADIUS = CONFIG.AUTH_RADIUS
AUTH_RADIUS_BACKEND = 'authentication.backends.radius.RadiusBackend'
RADIUS_SERVER = CONFIG.RADIUS_SERVER
RADIUS_PORT = CONFIG.RADIUS_PORT
RADIUS_SECRET = CONFIG.RADIUS_SECRET
if AUTH_RADIUS:
AUTHENTICATION_BACKENDS.insert(0, AUTH_RADIUS_BACKEND)
# Dump all celery log to here
CELERY_LOG_DIR = os.path.join(PROJECT_DIR, 'data', 'celery')
# Celery using redis as broker
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
'password': CONFIG.REDIS_PASSWORD,
'host': CONFIG.REDIS_HOST,
'port': CONFIG.REDIS_PORT,
'db': CONFIG.REDIS_DB_CELERY,
}
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
CELERY_ACCEPT_CONTENT = ['json', 'pickle']
CELERY_RESULT_EXPIRES = 3600
# CELERY_WORKER_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
# CELERY_WORKER_LOG_FORMAT = '%(message)s'
# CELERY_WORKER_TASK_LOG_FORMAT = '%(task_id)s %(task_name)s %(message)s'
CELERY_WORKER_TASK_LOG_FORMAT = '%(message)s'
# CELERY_WORKER_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
CELERY_WORKER_LOG_FORMAT = '%(message)s'
CELERY_TASK_EAGER_PROPAGATES = True
CELERY_WORKER_REDIRECT_STDOUTS = True
CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO"
# CELERY_WORKER_HIJACK_ROOT_LOGGER = True
# CELERY_WORKER_MAX_TASKS_PER_CHILD = 40
CELERY_TASK_SOFT_TIME_LIMIT = 3600
# Cache use redis
CACHES = {
'default': {
......@@ -534,127 +242,6 @@ CACHES = {
}
}
# Captcha settings, more see https://django-simple-captcha.readthedocs.io/en/latest/advanced.html
CAPTCHA_IMAGE_SIZE = (80, 33)
CAPTCHA_FOREGROUND_COLOR = '#001100'
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE
COMMAND_STORAGE = {
'ENGINE': 'terminal.backends.command.db',
}
DEFAULT_TERMINAL_COMMAND_STORAGE = {
"default": {
"TYPE": "server",
},
}
TERMINAL_COMMAND_STORAGE = CONFIG.TERMINAL_COMMAND_STORAGE
DEFAULT_TERMINAL_REPLAY_STORAGE = {
"default": {
"TYPE": "server",
},
}
TERMINAL_REPLAY_STORAGE = {
}
SECURITY_MFA_AUTH = False
SECURITY_COMMAND_EXECUTION = True
SECURITY_LOGIN_LIMIT_COUNT = 7
SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute
SECURITY_MAX_IDLE_TIME = 30 # Unit: minute
SECURITY_PASSWORD_EXPIRATION_TIME = 9999 # Unit: day
SECURITY_PASSWORD_MIN_LENGTH = 6 # Unit: bit
SECURITY_PASSWORD_UPPER_CASE = False
SECURITY_PASSWORD_LOWER_CASE = False
SECURITY_PASSWORD_NUMBER = False
SECURITY_PASSWORD_SPECIAL_CHAR = False
SECURITY_PASSWORD_RULES = [
'SECURITY_PASSWORD_MIN_LENGTH',
'SECURITY_PASSWORD_UPPER_CASE',
'SECURITY_PASSWORD_LOWER_CASE',
'SECURITY_PASSWORD_NUMBER',
'SECURITY_PASSWORD_SPECIAL_CHAR'
]
SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL
SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH
TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH
TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL
TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY
TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE
TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION
TERMINAL_HOST_KEY = CONFIG.TERMINAL_HOST_KEY
TERMINAL_HEADER_TITLE = CONFIG.TERMINAL_HEADER_TITLE
TERMINAL_TELNET_REGEX = CONFIG.TERMINAL_TELNET_REGEX
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = {
'horizontal_label_class': 'col-md-2',
# Field class to use in horizontal forms
'horizontal_field_class': 'col-md-9',
# Set placeholder attributes to label if no placeholder is provided
'set_placeholder': False,
'success_css_class': '',
'required_css_class': 'required',
}
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
DEFAULT_EXPIRED_YEARS = 70
USER_GUIDE_URL = ""
SWAGGER_SETTINGS = {
'DEFAULT_AUTO_SCHEMA_CLASS': 'jumpserver.swagger.CustomSwaggerAutoSchema',
'USE_SESSION_AUTH': True,
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
},
}
# Default email suffix
EMAIL_SUFFIX = CONFIG.EMAIL_SUFFIX
LOGIN_LOG_KEEP_DAYS = CONFIG.LOGIN_LOG_KEEP_DAYS
# User or user group permission cache time, default 3600 seconds
ASSETS_PERM_CACHE_ENABLE = CONFIG.ASSETS_PERM_CACHE_ENABLE
ASSETS_PERM_CACHE_TIME = CONFIG.ASSETS_PERM_CACHE_TIME
# Asset user auth external backend, default AuthBook backend
BACKEND_ASSET_USER_AUTH_VAULT = False
DEFAULT_ORG_SHOW_ALL_USERS = CONFIG.DEFAULT_ORG_SHOW_ALL_USERS
PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE
WINDOWS_SSH_DEFAULT_SHELL = CONFIG.WINDOWS_SSH_DEFAULT_SHELL
FLOWER_URL = CONFIG.FLOWER_URL
# Django channels support websocket
CHANNEL_REDIS = "redis://:{}@{}:{}/{}".format(
CONFIG.REDIS_PASSWORD, CONFIG.REDIS_HOST, CONFIG.REDIS_PORT,
CONFIG.REDIS_DB_WS,
)
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [CHANNEL_REDIS],
},
},
}
# Enable internal period task
PERIOD_TASK_ENABLED = CONFIG.PERIOD_TASK_ENABLED
FORCE_SCRIPT_NAME = CONFIG.FORCE_SCRIPT_NAME
# -*- coding: utf-8 -*-
#
from ..const import CONFIG, DYNAMIC
# Storage settings
COMMAND_STORAGE = {
'ENGINE': 'terminal.backends.command.db',
}
DEFAULT_TERMINAL_COMMAND_STORAGE = {
"default": {
"TYPE": "server",
},
}
TERMINAL_COMMAND_STORAGE = DYNAMIC.TERMINAL_COMMAND_STORAGE or {}
DEFAULT_TERMINAL_REPLAY_STORAGE = {
"default": {
"TYPE": "server",
},
}
TERMINAL_REPLAY_STORAGE = DYNAMIC.TERMINAL_REPLAY_STORAGE
# Security settings
SECURITY_MFA_AUTH = DYNAMIC.SECURITY_MFA_AUTH
SECURITY_COMMAND_EXECUTION = DYNAMIC.SECURITY_COMMAND_EXECUTION
SECURITY_LOGIN_LIMIT_COUNT = DYNAMIC.SECURITY_LOGIN_LIMIT_COUNT
SECURITY_LOGIN_LIMIT_TIME = DYNAMIC.SECURITY_LOGIN_LIMIT_TIME # Unit: minute
SECURITY_MAX_IDLE_TIME = DYNAMIC.SECURITY_MAX_IDLE_TIME # Unit: minute
SECURITY_PASSWORD_EXPIRATION_TIME = DYNAMIC.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day
SECURITY_PASSWORD_MIN_LENGTH = DYNAMIC.SECURITY_PASSWORD_MIN_LENGTH # Unit: bit
SECURITY_PASSWORD_UPPER_CASE = DYNAMIC.SECURITY_PASSWORD_UPPER_CASE
SECURITY_PASSWORD_LOWER_CASE = DYNAMIC.SECURITY_PASSWORD_LOWER_CASE
SECURITY_PASSWORD_NUMBER = DYNAMIC.SECURITY_PASSWORD_NUMBER
SECURITY_PASSWORD_SPECIAL_CHAR = DYNAMIC.SECURITY_PASSWORD_SPECIAL_CHAR
SECURITY_PASSWORD_RULES = [
'SECURITY_PASSWORD_MIN_LENGTH',
'SECURITY_PASSWORD_UPPER_CASE',
'SECURITY_PASSWORD_LOWER_CASE',
'SECURITY_PASSWORD_NUMBER',
'SECURITY_PASSWORD_SPECIAL_CHAR'
]
SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL
SECURITY_VIEW_AUTH_NEED_MFA = CONFIG.SECURITY_VIEW_AUTH_NEED_MFA
SECURITY_SERVICE_ACCOUNT_REGISTRATION = DYNAMIC.SECURITY_SERVICE_ACCOUNT_REGISTRATION
# Terminal other setting
TERMINAL_PASSWORD_AUTH = DYNAMIC.TERMINAL_PASSWORD_AUTH
TERMINAL_PUBLIC_KEY_AUTH = DYNAMIC.TERMINAL_PUBLIC_KEY_AUTH
TERMINAL_HEARTBEAT_INTERVAL = DYNAMIC.TERMINAL_HEARTBEAT_INTERVAL
TERMINAL_ASSET_LIST_SORT_BY = DYNAMIC.TERMINAL_ASSET_LIST_SORT_BY
TERMINAL_ASSET_LIST_PAGE_SIZE = DYNAMIC.TERMINAL_ASSET_LIST_PAGE_SIZE
TERMINAL_SESSION_KEEP_DURATION = DYNAMIC.TERMINAL_SESSION_KEEP_DURATION
TERMINAL_HOST_KEY = DYNAMIC.TERMINAL_HOST_KEY
TERMINAL_HEADER_TITLE = DYNAMIC.TERMINAL_HEADER_TITLE
TERMINAL_TELNET_REGEX = DYNAMIC.TERMINAL_TELNET_REGEX
# User or user group permission cache time, default 3600 seconds
ASSETS_PERM_CACHE_ENABLE = CONFIG.ASSETS_PERM_CACHE_ENABLE
ASSETS_PERM_CACHE_TIME = CONFIG.ASSETS_PERM_CACHE_TIME
# Asset user auth external backend, default AuthBook backend
BACKEND_ASSET_USER_AUTH_VAULT = False
DEFAULT_ORG_SHOW_ALL_USERS = CONFIG.DEFAULT_ORG_SHOW_ALL_USERS
PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE
WINDOWS_SSH_DEFAULT_SHELL = CONFIG.WINDOWS_SSH_DEFAULT_SHELL
FLOWER_URL = CONFIG.FLOWER_URL
# Enable internal period task
PERIOD_TASK_ENABLED = CONFIG.PERIOD_TASK_ENABLED
# Email custom content
EMAIL_SUBJECT_PREFIX = DYNAMIC.EMAIL_SUBJECT_PREFIX
EMAIL_SUFFIX = DYNAMIC.EMAIL_SUFFIX
EMAIL_CUSTOM_USER_CREATED_SUBJECT = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_SUBJECT
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_HONORIFIC
EMAIL_CUSTOM_USER_CREATED_BODY = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_BODY
EMAIL_CUSTOM_USER_CREATED_SIGNATURE = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_SIGNATURE
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
DEFAULT_EXPIRED_YEARS = 70
USER_GUIDE_URL = DYNAMIC.USER_GUIDE_URL
HTTP_LISTEN_PORT = CONFIG.HTTP_LISTEN_PORT
WS_LISTEN_PORT = CONFIG.WS_LISTEN_PORT
LOGIN_LOG_KEEP_DAYS = DYNAMIC.LOGIN_LOG_KEEP_DAYS
# -*- coding: utf-8 -*-
#
import os
from ..const import CONFIG, PROJECT_DIR
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': (
'common.permissions.IsOrgAdmin',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'common.drf.renders.JMSCSVRender',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
'common.drf.parsers.JMSCSVParser',
'rest_framework.parsers.FileUploadParser',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
'authentication.backends.api.AccessKeyAuthentication',
'authentication.backends.api.AccessTokenAuthentication',
'authentication.backends.api.PrivateTokenAuthentication',
'authentication.backends.api.SignatureAuthentication',
'authentication.backends.api.SessionAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
),
'DEFAULT_METADATA_CLASS': 'common.drf.metadata.SimpleMetadataWithFilters',
'ORDERING_PARAM': "order",
'SEARCH_PARAM': "search",
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S %z'],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
# 'PAGE_SIZE': 15
}
SWAGGER_SETTINGS = {
'DEFAULT_AUTO_SCHEMA_CLASS': 'jumpserver.swagger.CustomSwaggerAutoSchema',
'USE_SESSION_AUTH': True,
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
},
}
# Captcha settings, more see https://django-simple-captcha.readthedocs.io/en/latest/advanced.html
CAPTCHA_IMAGE_SIZE = (80, 33)
CAPTCHA_FOREGROUND_COLOR = '#001100'
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = {
'horizontal_label_class': 'col-md-2',
# Field class to use in horizontal forms
'horizontal_field_class': 'col-md-9',
# Set placeholder attributes to label if no placeholder is provided
'set_placeholder': False,
'success_css_class': '',
'required_css_class': 'required',
}
# Django channels support websocket
CHANNEL_REDIS = "redis://:{}@{}:{}/{}".format(
CONFIG.REDIS_PASSWORD, CONFIG.REDIS_HOST, CONFIG.REDIS_PORT,
CONFIG.REDIS_DB_WS,
)
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [CHANNEL_REDIS],
},
},
}
ASGI_APPLICATION = 'jumpserver.routing.application'
# Dump all celery log to here
CELERY_LOG_DIR = os.path.join(PROJECT_DIR, 'data', 'celery')
# Celery using redis as broker
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
'password': CONFIG.REDIS_PASSWORD,
'host': CONFIG.REDIS_HOST,
'port': CONFIG.REDIS_PORT,
'db': CONFIG.REDIS_DB_CELERY,
}
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
CELERY_ACCEPT_CONTENT = ['json', 'pickle']
CELERY_RESULT_EXPIRES = 3600
# CELERY_WORKER_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
# CELERY_WORKER_LOG_FORMAT = '%(message)s'
# CELERY_WORKER_TASK_LOG_FORMAT = '%(task_id)s %(task_name)s %(message)s'
CELERY_WORKER_TASK_LOG_FORMAT = '%(message)s'
# CELERY_WORKER_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
CELERY_WORKER_LOG_FORMAT = '%(message)s'
CELERY_TASK_EAGER_PROPAGATES = True
CELERY_WORKER_REDIRECT_STDOUTS = True
CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO"
# CELERY_WORKER_HIJACK_ROOT_LOGGER = True
# CELERY_WORKER_MAX_TASKS_PER_CHILD = 40
CELERY_TASK_SOFT_TIME_LIMIT = 3600
# -*- coding: utf-8 -*-
#
import os
from ..const import PROJECT_DIR, CONFIG
LOG_DIR = os.path.join(PROJECT_DIR, 'logs')
JUMPSERVER_LOG_FILE = os.path.join(LOG_DIR, 'jumpserver.log')
ANSIBLE_LOG_FILE = os.path.join(LOG_DIR, 'ansible.log')
GUNICORN_LOG_FILE = os.path.join(LOG_DIR, 'gunicorn.log')
LOG_LEVEL = CONFIG.LOG_LEVEL
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'main': {
'datefmt': '%Y-%m-%d %H:%M:%S',
'format': '%(asctime)s [%(module)s %(levelname)s] %(message)s',
},
'simple': {
'format': '%(levelname)s %(message)s'
},
'syslog': {
'format': 'jumpserver: %(message)s'
},
'msg': {
'format': '%(message)s'
}
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'main'
},
'file': {
'encoding': 'utf8',
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'maxBytes': 1024*1024*100,
'backupCount': 7,
'formatter': 'main',
'filename': JUMPSERVER_LOG_FILE,
},
'ansible_logs': {
'encoding': 'utf8',
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'main',
'maxBytes': 1024*1024*100,
'backupCount': 7,
'filename': ANSIBLE_LOG_FILE,
},
'syslog': {
'level': 'INFO',
'class': 'logging.NullHandler',
'formatter': 'syslog'
},
},
'loggers': {
'django': {
'handlers': ['null'],
'propagate': False,
'level': LOG_LEVEL,
},
'django.request': {
'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL,
'propagate': False,
},
'django.server': {
'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL,
'propagate': False,
},
'jumpserver': {
'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL,
},
'ops.ansible_api': {
'handlers': ['console', 'ansible_logs'],
'level': LOG_LEVEL,
},
'django_auth_ldap': {
'handlers': ['console', 'file'],
'level': "INFO",
},
'jms.audits': {
'handlers': ['syslog'],
'level': 'INFO'
},
# 'django.db': {
# 'handlers': ['console', 'file'],
# 'level': 'DEBUG'
# }
}
}
SYSLOG_ENABLE = CONFIG.SYSLOG_ENABLE
if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
host, port = CONFIG.SYSLOG_ADDR.split(':')
LOGGING['handlers']['syslog'].update({
'class': 'logging.handlers.SysLogHandler',
'facility': CONFIG.SYSLOG_FACILITY,
'address': (host, int(port)),
})
if not os.path.isdir(LOG_DIR):
os.makedirs(LOG_DIR)
......@@ -7,10 +7,7 @@ from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from django.views.i18n import JavaScriptCatalog
# from .views import IndexView, LunaView, I18NView, HealthCheckView, redirect_format_api
from . import views
from .celery_flower import celery_flower_view
from .swagger import get_swagger_view
api_v1 = [
path('users/', include('users.urls.api_urls', namespace='api-users')),
......@@ -44,7 +41,7 @@ app_view_patterns = [
path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('applications/', include('applications.urls.views_urls', namespace='applications')),
path('tickets/', include('tickets.urls.views_urls', namespace='tickets')),
re_path(r'flower/(?P<path>.*)', celery_flower_view, name='flower-view'),
re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'),
]
......@@ -82,19 +79,19 @@ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += js_i18n_patterns
handler404 = 'jumpserver.error_views.handler404'
handler500 = 'jumpserver.error_views.handler500'
handler404 = 'jumpserver.views.handler404'
handler500 = 'jumpserver.views.handler500'
if settings.DEBUG:
urlpatterns += [
re_path('^swagger(?P<format>\.json|\.yaml)$',
get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
path('docs/', get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"),
path('redoc/', get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'),
views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
path('docs/', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"),
path('redoc/', views.get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'),
re_path('^v2/swagger(?P<format>\.json|\.yaml)$',
get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
path('docs/v2/', get_swagger_view("v2").with_ui('swagger', cache_timeout=1), name="docs"),
path('redoc/v2/', get_swagger_view("v2").with_ui('redoc', cache_timeout=1), name='redoc'),
views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
path('docs/v2/', views.get_swagger_view("v2").with_ui('swagger', cache_timeout=1), name="docs"),
path('redoc/v2/', views.get_swagger_view("v2").with_ui('redoc', cache_timeout=1), name='redoc'),
]
# -*- coding: utf-8 -*-
#
from .index import *
from .other import *
from .celery_flower import *
from .swagger import *
from .error_views import *
......@@ -9,6 +9,8 @@ from proxy.views import proxy_view
flower_url = settings.FLOWER_URL
__all__ = ['celery_flower_view']
@csrf_exempt
def celery_flower_view(request, path):
......
......@@ -3,6 +3,8 @@
from django.shortcuts import render
from django.http import JsonResponse
__all__ = ['handler404', 'handler500']
def handler404(request, *args, **argv):
if request.content_type.find('application/json') > -1:
......
import datetime
import re
import time
from django.http import HttpResponseRedirect, JsonResponse
from django.conf import settings
from django.views.generic import TemplateView, View
from django.views.generic import TemplateView
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count
from django.shortcuts import redirect
from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from users.models import User
......@@ -19,7 +12,8 @@ from assets.models import Asset
from terminal.models import Session
from orgs.utils import current_org
from common.permissions import PermissionsMixin, IsValidUser
from common.http import HttpResponseTemporaryRedirect
__all__ = ['IndexView']
class IndexView(PermissionsMixin, TemplateView):
......@@ -186,58 +180,3 @@ class IndexView(PermissionsMixin, TemplateView):
kwargs.update(context)
return super(IndexView, self).get_context_data(**kwargs)
class LunaView(View):
def get(self, request):
msg = _("<div>Luna is a separately deployed program, you need to deploy Luna, koko, configure nginx for url distribution,</div> "
"</div>If you see this page, prove that you are not accessing the nginx listening port. Good luck.</div>")
return HttpResponse(msg)
class I18NView(View):
def get(self, request, lang):
referer_url = request.META.get('HTTP_REFERER', '/')
response = HttpResponseRedirect(referer_url)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
return response
api_url_pattern = re.compile(r'^/api/(?P<app>\w+)/(?P<version>v\d)/(?P<extra>.*)$')
@csrf_exempt
def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode()
matched = api_url_pattern.match(_path)
if matched:
kwargs = matched.groupdict()
kwargs["query"] = query
_path = '/api/{version}/{app}/{extra}?{query}'.format(**kwargs).rstrip("?")
return HttpResponseTemporaryRedirect(_path)
else:
return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404)
class HealthCheckView(APIView):
permission_classes = ()
def get(self, request):
return JsonResponse({"status": 1, "time": int(time.time())})
class WsView(APIView):
ws_port = settings.CONFIG.HTTP_LISTEN_PORT + 1
def get(self, request):
msg = _("Websocket server run on port: {}, you should proxy it on nginx"
.format(self.ws_port))
return JsonResponse({"msg": msg})
class KokoView(View):
def get(self, request):
msg = _(
"<div>Koko is a separately deployed program, you need to deploy Koko, configure nginx for url distribution,</div> "
"</div>If you see this page, prove that you are not accessing the nginx listening port. Good luck.</div>")
return HttpResponse(msg)
# -*- coding: utf-8 -*-
#
import re
import time
from django.http import HttpResponseRedirect, JsonResponse
from django.conf import settings
from django.views.generic import View
from django.utils.translation import ugettext_lazy as _
from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from common.http import HttpResponseTemporaryRedirect
__all__ = [
'LunaView', 'I18NView', 'KokoView', 'WsView', 'HealthCheckView',
'redirect_format_api'
]
class LunaView(View):
def get(self, request):
msg = _("<div>Luna is a separately deployed program, you need to deploy Luna, koko, configure nginx for url distribution,</div> "
"</div>If you see this page, prove that you are not accessing the nginx listening port. Good luck.</div>")
return HttpResponse(msg)
class I18NView(View):
def get(self, request, lang):
referer_url = request.META.get('HTTP_REFERER', '/')
response = HttpResponseRedirect(referer_url)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
return response
api_url_pattern = re.compile(r'^/api/(?P<app>\w+)/(?P<version>v\d)/(?P<extra>.*)$')
@csrf_exempt
def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode()
matched = api_url_pattern.match(_path)
if matched:
kwargs = matched.groupdict()
kwargs["query"] = query
_path = '/api/{version}/{app}/{extra}?{query}'.format(**kwargs).rstrip("?")
return HttpResponseTemporaryRedirect(_path)
else:
return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404)
class HealthCheckView(APIView):
permission_classes = ()
def get(self, request):
return JsonResponse({"status": 1, "time": int(time.time())})
class WsView(APIView):
ws_port = settings.HTTP_LISTEN_PORT + 1
def get(self, request):
msg = _("Websocket server run on port: {}, you should proxy it on nginx"
.format(self.ws_port))
return JsonResponse({"msg": msg})
class KokoView(View):
def get(self, request):
msg = _(
"<div>Koko is a separately deployed program, you need to deploy Koko, configure nginx for url distribution,</div> "
"</div>If you see this page, prove that you are not accessing the nginx listening port. Good luck.</div>")
return HttpResponse(msg)
......@@ -50,7 +50,7 @@ class CustomSwaggerAutoSchema(SwaggerAutoSchema):
def get_swagger_view(version='v1'):
from .urls import api_v1, api_v2
from ..urls import api_v1, api_v2
from django.urls import path, include
api_v1_patterns = [
path('api/v1/', include(api_v1))
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -5,17 +5,20 @@ import os
import re
from django.utils.translation import ugettext as _
from rest_framework import viewsets
from celery.result import AsyncResult
from rest_framework import generics
from django_celery_beat.models import PeriodicTask
from common.permissions import IsValidUser
from common.permissions import IsValidUser, IsSuperUser
from common.api import LogTailApi
from ..models import CeleryTask
from ..serializers import CeleryResultSerializer
from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
from ..celery.utils import get_celery_task_log_path
from common.mixins.api import CommonApiMixin
__all__ = ['CeleryTaskLogApi', 'CeleryResultApi']
__all__ = ['CeleryTaskLogApi', 'CeleryResultApi', 'CeleryPeriodTaskViewSet']
class CeleryTaskLogApi(LogTailApi):
......@@ -62,3 +65,14 @@ class CeleryResultApi(generics.RetrieveAPIView):
pk = self.kwargs.get('pk')
return AsyncResult(pk)
class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):
queryset = PeriodicTask.objects.all()
serializer_class = CeleryPeriodTaskSerializer
permission_classes = (IsSuperUser,)
http_method_names = ('get', 'head', 'options', 'patch')
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.exclude(description='')
return queryset
......@@ -9,51 +9,37 @@ _after_app_shutdown_clean_periodic_tasks = []
def add_register_period_task(task):
_need_registered_period_tasks.append(task)
# key = "__REGISTER_PERIODIC_TASKS"
# value = cache.get(key, [])
# value.append(name)
# cache.set(key, value)
def get_register_period_tasks():
# key = "__REGISTER_PERIODIC_TASKS"
# return cache.get(key, [])
return _need_registered_period_tasks
def add_after_app_shutdown_clean_task(name):
# key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS"
# value = cache.get(key, [])
# value.append(name)
# cache.set(key, value)
_after_app_shutdown_clean_periodic_tasks.append(name)
def get_after_app_shutdown_clean_tasks():
# key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS"
# return cache.get(key, [])
return _after_app_shutdown_clean_periodic_tasks
def add_after_app_ready_task(name):
# key = "__AFTER_APP_READY_RUN_TASKS"
# value = cache.get(key, [])
# value.append(name)
# cache.set(key, value)
_after_app_ready_start_tasks.append(name)
def get_after_app_ready_tasks():
# key = "__AFTER_APP_READY_RUN_TASKS"
# return cache.get(key, [])
return _after_app_ready_start_tasks
def register_as_period_task(crontab=None, interval=None):
def register_as_period_task(
crontab=None, interval=None, name=None,
description=''):
"""
Warning: Task must be have not any args and kwargs
:param crontab: "* * * * *"
:param interval: 60*60*60
:param description: "
:param name: ""
:return:
"""
if crontab is None and interval is None:
......@@ -65,14 +51,16 @@ def register_as_period_task(crontab=None, interval=None):
# Because when this decorator run, the task was not created,
# So we can't use func.name
name = '{func.__module__}.{func.__name__}'.format(func=func)
task = '{func.__module__}.{func.__name__}'.format(func=func)
_name = name if name else task
add_register_period_task({
name: {
'task': name,
_name: {
'task': task,
'interval': interval,
'crontab': crontab,
'args': (),
'enabled': True,
'description': description
}
})
......
# -*- coding: utf-8 -*-
#
import logging
from django.dispatch import receiver
from django.core.cache import cache
from celery import subtask
......@@ -11,6 +12,7 @@ from kombu.utils.encoding import safe_str
from django_celery_beat.models import PeriodicTask
from common.utils import get_logger
from common.signals import django_ready
from .decorator import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks
from .logger import CeleryTaskFileHandler
......
......@@ -10,6 +10,10 @@ from django_celery_beat.models import (
PeriodicTask, IntervalSchedule, CrontabSchedule, PeriodicTasks
)
from common.utils import get_logger
logger = get_logger(__name__)
def create_or_update_celery_periodic_tasks(tasks):
"""
......@@ -21,6 +25,7 @@ def create_or_update_celery_periodic_tasks(tasks):
'args': (16, 16),
'kwargs': {},
'enabled': False,
'description': ''
},
}
:return:
......@@ -35,34 +40,30 @@ def create_or_update_celery_periodic_tasks(tasks):
return None
if isinstance(detail.get("interval"), int):
intervals = IntervalSchedule.objects.filter(
every=detail["interval"], period=IntervalSchedule.SECONDS
kwargs = dict(
every=detail['interval'],
period=IntervalSchedule.SECONDS,
)
if intervals:
interval = intervals[0]
else:
interval = IntervalSchedule.objects.create(
every=detail['interval'],
period=IntervalSchedule.SECONDS,
)
# 不能使用 get_or_create,因为可能会有多个
interval = IntervalSchedule.objects.filter(**kwargs).first()
if interval is None:
interval = IntervalSchedule.objects.create(**kwargs)
elif isinstance(detail.get("crontab"), str):
try:
minute, hour, day, month, week = detail["crontab"].split()
except ValueError:
raise SyntaxError("crontab is not valid")
logger.error("crontab is not valid")
return
kwargs = dict(
minute=minute, hour=hour, day_of_week=week,
day_of_month=day, month_of_year=month, timezone=get_current_timezone()
)
contabs = CrontabSchedule.objects.filter(
**kwargs
)
if contabs:
crontab = contabs[0]
else:
crontab = CrontabSchedule.objects.filter(**kwargs).first()
if crontab is None:
crontab = CrontabSchedule.objects.create(**kwargs)
else:
raise SyntaxError("Schedule is not valid")
logger.error("Schedule is not valid")
return
defaults = dict(
interval=interval,
......@@ -71,8 +72,9 @@ def create_or_update_celery_periodic_tasks(tasks):
task=detail['task'],
args=json.dumps(detail.get('args', [])),
kwargs=json.dumps(detail.get('kwargs', {})),
enabled=detail.get('enabled', True),
description=detail.get('description') or ''
)
print(defaults)
task = PeriodicTask.objects.update_or_create(
defaults=defaults, name=name,
......
# -*- coding: utf-8 -*-
#
from .celery import *
from .adhoc import *
......@@ -3,17 +3,7 @@ from __future__ import unicode_literals
from rest_framework import serializers
from django.shortcuts import reverse
from .models import Task, AdHoc, AdHocRunHistory, CommandExecution
class CeleryResultSerializer(serializers.Serializer):
id = serializers.UUIDField()
result = serializers.JSONField()
state = serializers.CharField(max_length=16)
class CeleryTaskSerializer(serializers.Serializer):
pass
from ..models import Task, AdHoc, AdHocRunHistory, CommandExecution
class TaskSerializer(serializers.ModelSerializer):
......@@ -87,3 +77,4 @@ class CommandExecutionSerializer(serializers.ModelSerializer):
@staticmethod
def get_log_url(obj):
return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id})
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from rest_framework import serializers
from django_celery_beat.models import PeriodicTask
__all__ = [
'CeleryResultSerializer', 'CeleryTaskSerializer',
'CeleryPeriodTaskSerializer'
]
class CeleryResultSerializer(serializers.Serializer):
id = serializers.UUIDField()
result = serializers.JSONField()
state = serializers.CharField(max_length=16)
class CeleryTaskSerializer(serializers.Serializer):
pass
class CeleryPeriodTaskSerializer(serializers.ModelSerializer):
class Meta:
model = PeriodicTask
fields = [
'name', 'task', 'enabled', 'description',
'last_run_at', 'total_run_count'
]
# coding: utf-8
import os
import subprocess
import datetime
import time
from django.conf import settings
from celery import shared_task, subtask
from celery.exceptions import SoftTimeLimitExceeded
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, get_object_or_none
from common.utils import get_logger, get_object_or_none, get_disk_usage
from .celery.decorator import (
register_as_period_task, after_app_shutdown_clean_periodic,
after_app_ready_start
)
from .celery.utils import create_or_update_celery_periodic_tasks
from .models import Task, CommandExecution, CeleryTask
from .utils import send_server_performance_mail
logger = get_logger(__file__)
......@@ -59,7 +60,7 @@ def run_command_execution(cid, **kwargs):
@shared_task
@after_app_shutdown_clean_periodic
@register_as_period_task(interval=3600*24)
@register_as_period_task(interval=3600*24, description=_("Clean task history period"))
def clean_tasks_adhoc_period():
logger.debug("Start clean task adhoc and run history")
tasks = Task.objects.all()
......@@ -72,7 +73,7 @@ def clean_tasks_adhoc_period():
@shared_task
@after_app_shutdown_clean_periodic
@register_as_period_task(interval=3600*24)
@register_as_period_task(interval=3600*24, description=_("Clean celery log period"))
def clean_celery_tasks_period():
expire_days = 30
logger.debug("Start clean celery task history")
......@@ -103,6 +104,19 @@ def create_or_update_registered_periodic_tasks():
create_or_update_celery_periodic_tasks(task)
@shared_task
@register_as_period_task(interval=3600)
def check_server_performance_period():
usages = get_disk_usage()
usages = {path: usage for path, usage in usages.items()
if not path.startswith('/etc')}
for path, usage in usages.items():
if usage.percent > 80:
send_server_performance_mail(path, usage, usages)
return
@shared_task(queue="ansible")
def hello(name, callback=None):
import time
......
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
......
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
......
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
......
......@@ -11,8 +11,6 @@
rel="stylesheet">
<link href="{% static 'css/plugins/codemirror/ambiance.css' %}"
rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}"
rel="stylesheet">
<script type="text/javascript"
src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script type="text/javascript"
......@@ -22,7 +20,6 @@
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
<script src="{% static 'js/plugins/codemirror/codemirror.js' %}"></script>
<script src="{% static 'js/plugins/codemirror/mode/shell/shell.js' %}"></script>
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<style type="text/css">
.xterm .xterm-screen canvas {
position: absolute;
......
......@@ -6,8 +6,6 @@
{% 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>
<style>
.form-control {
height: 30px;
......@@ -16,6 +14,11 @@
#search_btn {
margin-bottom: 0;
}
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single {
height: 30px !important;
}
</style>
{% endblock %}
......
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
......
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/sweetalert/sweetalert.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
{% endblock %}
......
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
......
......@@ -13,6 +13,7 @@ router.register(r'tasks', api.TaskViewSet, 'task')
router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
router.register(r'history', api.AdHocRunHistoryViewSet, 'history')
router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution')
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
urlpatterns = [
path('tasks/<uuid:pk>/run/', api.TaskRun.as_view(), name='task-run'),
......
# ~*~ coding: utf-8 ~*~
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, get_object_or_none
from common.tasks import send_mail_async
from orgs.utils import set_to_root_org
from .models import Task, AdHoc
......@@ -56,4 +58,14 @@ def update_or_create_ansible_task(
return task, created
def send_server_performance_mail(path, usage, usages):
from users.models import User
subject = _("Disk used more than 80%: {} => {}").format(path, usage.percent)
message = subject
admins = User.objects.filter(role=User.ROLE_ADMIN)
recipient_list = [u.email for u in admins if u.email]
logger.info(subject)
send_mail_async(subject, message, recipient_list, html_message=message)
......@@ -17,6 +17,6 @@ class CeleryTaskLogView(PermissionsMixin, TemplateView):
context = super().get_context_data(**kwargs)
context.update({
'task_id': self.kwargs.get('pk'),
'ws_port': settings.CONFIG.WS_LISTEN_PORT
'ws_port': settings.WS_LISTEN_PORT
})
return context
......@@ -80,7 +80,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
'action': _('Command execution'),
'form': self.get_form(),
'system_users': system_users,
'ws_port': settings.CONFIG.WS_LISTEN_PORT
'ws_port': settings.WS_LISTEN_PORT
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......
......@@ -46,7 +46,11 @@ class OrgModelViewSet(CommonApiMixin, OrgQuerySetMixin, ModelViewSet):
class OrgBulkModelViewSet(CommonApiMixin, OrgQuerySetMixin, BulkModelViewSet):
def allow_bulk_destroy(self, qs, filtered):
if qs.count() <= filtered.count():
qs_count = qs.count()
filtered_count = filtered.count()
if filtered_count == 1:
return True
if qs_count <= filtered_count:
return False
if self.request.query_params.get('spm', ''):
return True
......
......@@ -95,6 +95,14 @@ class Organization(models.Model):
def get_org_admins(self):
return self.org_admins
def org_id(self):
if self.is_real():
return self.id
elif self.is_root():
return None
else:
return ''
@lazyproperty
def org_auditors(self):
from users.models import User
......
......@@ -3,6 +3,7 @@
from .asset_permission import *
from .user_permission import *
from .asset_permission_relation import *
from .user_group_permission import *
from .remote_app_permission import *
from .user_remote_app_permission import *
......@@ -2,12 +2,9 @@
#
from django.db.models import Q
from rest_framework.views import Response
from django.shortcuts import get_object_or_404
from common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgModelViewSet
from orgs.mixins import generics
from common.utils import get_object_or_none
from ..models import AssetPermission
from ..hands import (
......@@ -17,9 +14,7 @@ from .. import serializers
__all__ = [
'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi',
'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi',
'AssetPermissionAddAssetApi', 'AssetPermissionAssetsApi',
'AssetPermissionViewSet',
]
......@@ -29,6 +24,7 @@ class AssetPermissionViewSet(OrgModelViewSet):
"""
model = AssetPermission
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
serializer_display_class = serializers.AssetPermissionListSerializer
filter_fields = ['name']
permission_classes = (IsOrgAdmin,)
......@@ -38,11 +34,9 @@ class AssetPermissionViewSet(OrgModelViewSet):
)
return queryset
def get_serializer_class(self):
if self.action in ("list", 'retrieve') and \
self.request.query_params.get("display"):
return serializers.AssetPermissionListSerializer
return self.serializer_class
def is_query_all(self):
query_all = self.request.query_params.get('all', '1') == '1'
return query_all
def filter_valid(self, queryset):
valid_query = self.request.query_params.get('is_valid', None)
......@@ -81,7 +75,10 @@ class AssetPermissionViewSet(OrgModelViewSet):
if not _nodes:
return queryset.none()
nodes = set()
if not self.is_query_all():
queryset = queryset.filter(nodes__in=_nodes)
return queryset
nodes = set(_nodes)
for node in _nodes:
nodes |= set(node.get_ancestors(with_self=True))
queryset = queryset.filter(nodes__in=nodes)
......@@ -101,6 +98,9 @@ class AssetPermissionViewSet(OrgModelViewSet):
return queryset
if not assets:
return queryset.none()
if not self.is_query_all():
queryset = queryset.filter(assets__in=assets)
return queryset
inherit_all_nodes = set()
inherit_nodes_keys = assets.all().values_list('nodes__key', flat=True)
......@@ -117,7 +117,6 @@ class AssetPermissionViewSet(OrgModelViewSet):
def filter_user(self, queryset):
user_id = self.request.query_params.get('user_id')
username = self.request.query_params.get('username')
query_group = self.request.query_params.get('all')
if user_id:
user = get_object_or_none(User, pk=user_id)
elif username:
......@@ -126,14 +125,14 @@ class AssetPermissionViewSet(OrgModelViewSet):
return queryset
if not user:
return queryset.none()
kwargs = {}
args = []
if query_group:
groups = user.groups.all()
args.append(Q(users=user) | Q(user_groups__in=groups))
else:
kwargs["users"] = user
return queryset.filter(*args, **kwargs).distinct()
if not self.is_query_all():
queryset = queryset.filter(users=user)
return queryset
groups = user.groups.all()
queryset = queryset.filter(
Q(users=user) | Q(user_groups__in=groups)
).distinct()
return queryset
def filter_user_group(self, queryset):
user_group_id = self.request.query_params.get('user_group_id')
......@@ -167,99 +166,3 @@ class AssetPermissionViewSet(OrgModelViewSet):
queryset = self.filter_user_group(queryset)
queryset = queryset.distinct()
return queryset
class AssetPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
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))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAddUserApi(generics.RetrieveUpdateAPIView):
model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
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))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionRemoveAssetApi(generics.RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
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))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAddAssetApi(generics.RetrieveUpdateAPIView):
model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
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))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionAssetsSerializer
filter_fields = ("hostname", "ip")
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(AssetPermission, pk=pk)
def get_queryset(self):
perm = self.get_object()
assets = perm.get_all_assets().only(
*self.serializer_class.Meta.only_fields
)
return assets
# -*- coding: utf-8 -*-
#
from rest_framework import generics
from django.db.models import F, Value
from django.db.models.functions import Concat
from django.shortcuts import get_object_or_404
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
from common.permissions import IsOrgAdmin
from .. import serializers
from .. import models
__all__ = [
'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet',
'AssetPermissionAssetRelationViewSet', 'AssetPermissionNodeRelationViewSet',
'AssetPermissionSystemUserRelationViewSet', 'AssetPermissionAllAssetListApi',
'AssetPermissionAllUserListApi',
]
class RelationMixin(OrgBulkModelViewSet):
def get_queryset(self):
queryset = self.model.objects.all()
org_id = current_org.org_id()
if org_id is not None:
queryset = queryset.filter(assetpermission__org_id=org_id)
queryset = queryset.annotate(assetpermission_display=F('assetpermission__name'))
return queryset
class AssetPermissionUserRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionUserRelationSerializer
model = models.AssetPermission.users.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', "user", "assetpermission",
]
search_fields = ("user__name", "user__username", "assetpermission__name")
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(user_display=F('user__name'))
return queryset
class AssetPermissionAllUserListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionAllUserSerializer
filter_fields = ("username", "name")
search_fields = filter_fields
def get_queryset(self):
pk = self.kwargs.get("pk")
perm = get_object_or_404(models.AssetPermission, pk=pk)
users = perm.get_all_users().only(
*self.serializer_class.Meta.only_fields
)
return users
class AssetPermissionUserGroupRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionUserGroupRelationSerializer
model = models.AssetPermission.user_groups.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', "usergroup", "assetpermission"
]
search_fields = ["usergroup__name", "assetpermission__name"]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(usergroup_display=F('usergroup__name'))
return queryset
class AssetPermissionAssetRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionAssetRelationSerializer
model = models.AssetPermission.assets.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'asset', 'assetpermission',
]
search_fields = ["id", "asset__hostname", "asset__ip", "assetpermission__name"]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(asset_display=F('asset__hostname'))
return queryset
class AssetPermissionAllAssetListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionAllAssetSerializer
filter_fields = ("hostname", "ip")
search_fields = filter_fields
def get_queryset(self):
pk = self.kwargs.get("pk")
perm = get_object_or_404(models.AssetPermission, pk=pk)
assets = perm.get_all_assets().only(
*self.serializer_class.Meta.only_fields
)
return assets
class AssetPermissionNodeRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionNodeRelationSerializer
model = models.AssetPermission.nodes.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'node', 'assetpermission',
]
search_fields = ["node__value", "assetpermission__name"]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(node_key=F('node__key'))
return queryset
class AssetPermissionSystemUserRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionSystemUserRelationSerializer
model = models.AssetPermission.system_users.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'systemuser', 'assetpermission',
]
search_fields = [
"assetpermission__name", "systemuser__name", "systemuser__username"
]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(systemuser_display=Concat(
F('systemuser__name'), Value('('), F('systemuser__username'),
Value(')')
))
return queryset
......@@ -80,9 +80,10 @@ class BasePermission(OrgModelMixin):
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))
from users.models import User
users_id = self.users.all().values_list('id', flat=True)
groups_id = self.user_groups.all().values_list('id', flat=True)
users = User.objects.filter(
Q(id__in=users_id) | Q(groups__id__in=groups_id)
).distinct()
return users
......@@ -4,3 +4,4 @@
from .asset_permission import *
from .user_permission import *
from .remote_app_permission import *
from .asset_permission_relation import *
......@@ -6,12 +6,10 @@ from rest_framework import serializers
from common.fields import StringManyToManyField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from perms.models import AssetPermission, Action
from assets.models import Asset
__all__ = [
'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer',
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'ActionsField', 'AssetPermissionAssetsSerializer',
'ActionsField',
]
......@@ -59,23 +57,4 @@ class AssetPermissionListSerializer(BulkOrgResourceModelSerializer):
fields = '__all__'
class AssetPermissionUpdateUserSerializer(serializers.ModelSerializer):
class Meta:
model = AssetPermission
fields = ['id', 'users']
class AssetPermissionUpdateAssetSerializer(serializers.ModelSerializer):
class Meta:
model = AssetPermission
fields = ['id', 'assets']
class AssetPermissionAssetsSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
only_fields = ['id', 'hostname', 'ip']
fields = tuple(only_fields)
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from assets.models import Asset, Node
from ..models import AssetPermission
__all__ = [
'AssetPermissionUserRelationSerializer',
'AssetPermissionUserGroupRelationSerializer',
"AssetPermissionAssetRelationSerializer",
'AssetPermissionNodeRelationSerializer',
'AssetPermissionSystemUserRelationSerializer',
'AssetPermissionAllAssetSerializer',
'AssetPermissionAllUserSerializer',
]
class CurrentAssetPermission(object):
permission = None
def set_context(self, serializer_field):
self.permission = serializer_field.context['permission']
def __call__(self):
return self.permission
class RelationMixin(BulkSerializerMixin, serializers.Serializer):
assetpermission_display = serializers.ReadOnlyField()
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['assetpermission', "assetpermission_display"])
return fields
class Meta:
list_serializer_class = AdaptedBulkListSerializer
class AssetPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
user_display = serializers.ReadOnlyField()
class Meta(RelationMixin.Meta):
model = AssetPermission.users.through
fields = [
'id', 'user', 'user_display',
]
class AssetPermissionAllUserSerializer(serializers.ModelSerializer):
user = serializers.UUIDField(read_only=True, source='id')
user_display = serializers.SerializerMethodField()
class Meta:
model = Asset
only_fields = ['id', 'username', 'name']
fields = ['user', 'user_display']
@staticmethod
def get_user_display(obj):
return str(obj)
class AssetPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
usergroup_display = serializers.ReadOnlyField()
class Meta(RelationMixin.Meta):
model = AssetPermission.user_groups.through
fields = [
'id', 'usergroup', "usergroup_display",
]
class AssetPermissionAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
asset_display = serializers.ReadOnlyField()
class Meta(RelationMixin.Meta):
model = AssetPermission.assets.through
fields = [
'id', "asset", "asset_display",
]
class AssetPermissionAllAssetSerializer(serializers.ModelSerializer):
asset = serializers.UUIDField(read_only=True, source='id')
asset_display = serializers.SerializerMethodField()
class Meta:
model = Asset
only_fields = ['id', 'hostname', 'ip']
fields = ['asset', 'asset_display']
@staticmethod
def get_asset_display(obj):
return str(obj)
class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
node_display = serializers.SerializerMethodField()
class Meta(RelationMixin.Meta):
model = AssetPermission.nodes.through
fields = [
'id', 'node', "node_display",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tree = Node.tree()
def get_node_display(self, obj):
if hasattr(obj, 'node_key'):
return self.tree.get_node_full_tag(obj.node_key)
else:
return obj.node.full_value
class AssetPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
systemuser_display = serializers.ReadOnlyField()
class Meta(RelationMixin.Meta):
model = AssetPermission.system_users.through
fields = [
'id', 'systemuser', 'systemuser_display'
]
......@@ -2,10 +2,6 @@
{% 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">
......@@ -54,8 +50,8 @@
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
......@@ -75,7 +71,7 @@
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select assets' %}" class="select2" id="id_assets" style="width: 100%" multiple="" tabindex="4">
<select data-placeholder="{% trans 'Select assets' %}" class="select2 assets-select2" id="id_assets" style="width: 100%" multiple="" tabindex="4">
</select>
</td>
</tr>
......@@ -112,10 +108,46 @@
</form>
{% for node in asset_permission.nodes.all %}
<tr>
<tr {% if forloop.counter == 1 %} class="no-borders-tr" {% endif %} >
<td ><b class="bdg_group" data-gid={{ node.id }}>{{ node.full_value }}</b></td>
<td>
<button class="btn btn-danger btn-xs btn-remove-node" type="button" data-gid="{{ node.id }}" style="float: right;"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger btn-xs btn-remove-node" type="button" data-uid="{{ node.id }}" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'System user' %}
</div>
<div class="panel-body">
<table class="table" id="system-user-table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select system users' %}" class="select2 system-users" id="system_users_select2" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_remain %}
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-warning btn-sm" id="btn-add-system-user">{% trans 'Add' %}</button>
</td>
</tr>
</form>
{% for system_user in object.system_users.all %}
<tr {% if forloop.counter == 1 %} class="no-borders-tr" {% endif %} >
<td ><b class="bdg-system-user" data-uid={{ system_user.id }}>{{ system_user|truncatechars:21}}</b></td>
<td>
<button class="btn btn-danger btn-xs btn-remove-sys-user" data-uid="{{ system_user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
......@@ -129,69 +161,83 @@
</div>
</div>
</div>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
function addAssets(assets) {
var the_url = "{% url 'api-perms:asset-permission-add-asset' pk=asset_permission.id %}";
var body = {
assets: assets
};
var success = function(data) {
location.reload();
};
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function removeAssets(assets) {
var the_url = "{% url 'api-perms:asset-permission-remove-asset' pk=asset_permission.id %}";
var body = {
assets: assets
};
var success = function(data) {
location.reload();
};
var assetsRelationUrl = "{% url 'api-perms:asset-permissions-assets-relation-list' %}";
var nodesRelationUrl = "{% url 'api-perms:asset-permissions-nodes-relation-list' %}";
var systemUsersRelationUrl = "{% url 'api-perms:asset-permissions-system-users-relation-list' %}";
function getRelationUrl(type) {
var theUrl = "";
switch (type) {
case "asset":
theUrl = assetsRelationUrl;
break;
case "node":
theUrl = nodesRelationUrl;
break;
case "systemuser":
theUrl = systemUsersRelationUrl;
break;
}
return theUrl;
}
function addObjects(objectsId, type) {
if (!objectsId || objectsId.length === 0) {
return
}
var theUrl = getRelationUrl(type);
var body = [];
objectsId.forEach(function (v) {
var data = {assetpermission: "{{ object.id }}"};
data[type] = v;
body.push(data)
});
requestApi({
url: the_url,
url: theUrl,
body: JSON.stringify(body),
success: success
method: "POST",
success: reloadPage
});
}
function updateNodes(nodes, success) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}";
var body = {
nodes: nodes
};
function removeObject(objectId, type) {
if (!objectId) {
return
}
var theUrl = getRelationUrl(type);
theUrl = setUrlParam(theUrl, 'assetpermission', "{{ object.id }}");
theUrl = setUrlParam(theUrl, type, objectId);
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
url: theUrl,
method: "DELETE",
success: reloadPage
});
}
var table;
function initAssetTable() {
var options = {
ele: $('#asset_list_table'),
toggle: true,
columnDefs: [
{
targets: 0, createdCell: function (td, cellData, rowData) {
var html = '<input type="checkbox" class="text-center ipt_check" id="id_' + cellData + '">';
$(td).html(html);
}
},
{targets: 2, createdCell: function (td, cellData, rowData) {
var removeBtn = '<a class="btn btn-xs btn-danger m-l-xs btn-remove-asset" disabled data-uid="{{ DEFAULT_PK }}"><i class="fa fa-minus"></i></a>'
.replace('{{ DEFAULT_PK }}', cellData);
if (assetDirectRelated.indexOf(cellData) !== -1) {
removeBtn = removeBtn.replace('disabled', '')
}
$(td).html(removeBtn);
}}
],
ajax_url: "{% url 'api-perms:asset-permission-assets' pk=object.id %}",
ajax_url: "{% url 'api-perms:asset-permission-all-assets' pk=object.id %}",
columns: [
{data: "id"}, {data: "hostname"}, {data: "ip"}
{data: "asset"}, {data: "asset_display"}, {data: "asset", width: "30px"}
],
op_html: $('#actions').html()
};
......@@ -199,76 +245,38 @@ function initAssetTable() {
return table
}
var assetDirectRelated = {{ assets | safe }};
$(document).ready(function () {
$('.select2').select2();
table = initAssetTable();
nodesSelect2Init(".nodes-select2");
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
}
})
initAssetTable();
initAssetTreeModel('#id_assets');
})
.on('click', '.btn-add-assets', function () {
var assets_selected = $("#id_assets option:selected").map(function () {
return $(this).attr('value');
}).get();
if (assets_selected.length === 0) {
return false;
}
addAssets(assets_selected);
var assetsId = $("#id_assets").val();
addObjects(assetsId, "asset");
})
.on('click', '.btn-remove-asset', function () {
var asset_id = $(this).data("gid");
if (asset_id === "") {
return
}
var assets = [asset_id];
removeAssets(assets)
var assetId = $(this).data("uid");
removeObject(assetId, "asset")
})
.on('click', '#btn-add-node', function () {
var nodes_selected = {};
$("#nodes_select2 option:selected").each(function (i, data) {
nodes_selected[$(data).attr('value')] = $(data).text();
});
if (Object.keys(nodes_selected).length === 0) {
return false;
}
var nodes_origin = $('.bdg_group').map(function() {
return $(this).data('gid');
}).get();
var nodes = nodes_origin.concat(Object.keys(nodes_selected));
var success = function () {
$.map(nodes_selected, function(name, id) {
$('#opt_' + id).remove();
$('.group_edit tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-gid="' + id + '">' + 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>'
)
});
};
updateNodes(nodes, success);
})
var nodesId = $("#nodes_select2").val();
addObjects(nodesId, "node");
})
.on('click', '.btn-remove-node', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var nodes = $('.bdg_group').map(function() {
if ($(this).data('gid') !== $this.data('gid')){
return $(this).data('gid');
}
}).get();
var success = function () {
$tr.remove()
};
updateNodes(nodes, success);
var nodeId = $(this).data("uid");
removeObject(nodeId, "node")
})
.on('click', '#btn-add-system-user', function () {
var systemUsersId = $("#system_users_select2").val();
addObjects(systemUsersId, "systemuser");
})
.on('click', '.btn-remove-sys-user', function () {
var systemUserId = $(this).data("uid");
removeObject(systemUserId, "systemuser")
})
</script>
{% endblock %}
......@@ -3,8 +3,6 @@
{% 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 %}
......@@ -111,21 +109,14 @@ $(document).ready(function () {
initDateRangePicker('#date_start');
initDateRangePicker('#date_expired');
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
}
})
initAssetTreeModel('#id_assets');
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-perms:asset-permission-list' %}';
var theUrl = '{% url 'api-perms:asset-permission-list' %}';
var method = "POST";
{% if api_action == "update" %}
the_url = '{% url 'api-perms:asset-permission-detail' pk=object.id %}';
theUrl = '{% url 'api-perms:asset-permission-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var redirect_to = '{% url "perms:asset-permission-list" %}';
......@@ -135,7 +126,7 @@ $(document).ready(function () {
objectAttrsIsDatetime(data, ['date_start', 'date_expired']);
objectAttrsIsBool(data, ['is_active']);
var props = {
url: the_url,
url: theUrl,
data: data,
method: method,
form: form,
......
......@@ -2,11 +2,6 @@
{% 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">
......@@ -135,42 +130,7 @@
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'System user' %}
</div>
<div class="panel-body">
<table class="table" id="system-user-table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select system users' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_remain %}
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-info btn-small" id="btn-add-system-user">{% trans 'Add' %}</button>
</td>
</tr>
</form>
{% for system_user in object.system_users.all %}
<tr {% if forloop.counter == 1 %} class="no-borders-tr" {% endif %} >
<td ><b class="bdg-system-user" data-uid={{ system_user.id }}>{{ system_user }}</b></td>
<td>
<button class="btn btn-danger btn-xs btn-remove-user" data-uid="{{ system_user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
......@@ -182,17 +142,7 @@
<script>
jumpserver.system_users_selected = {};
function updateSystemUser(system_users) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=object.id %}";
var body = {
system_users: Object.assign([], system_users)
};
requestApi({
url: the_url,
body: JSON.stringify(body),
success: function () {window.location.reload()}
});
}
$(document).ready(function () {
$('.select2').select2()
......@@ -213,36 +163,7 @@ $(document).ready(function () {
var redirect_url = "{% url 'perms:asset-permission-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
.on('click', '#btn-add-system-user', function () {
if (Object.keys(jumpserver.system_users_selected).length === 0) {
return false;
}
var system_users = $('.bdg-system-user').map(function() {
return $(this).data('uid');
}).get();
$.map(jumpserver.system_users_selected, function(name, index) {
system_users.push(index);
$('#opt_' + index).remove();
$('.group_edit tbody').append(
'<tr>' +
'<td><b class="bdg-system-user" data-gid="' + index + '">' + name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-user" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
updateSystemUser(system_users);
}).on('click', '.btn-remove-user', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var system_users = $('.bdg-system-user').map(function() {
if ($(this).data('uid') !== $this.data('uid')){
return $(this).data('uid');
}
}).get();
updateSystemUser(system_users);
$tr.remove()
}).on('click', '#is_active', function () {
.on('click', '#is_active', function () {
var the_url = '{% url "api-perms:asset-permission-detail" pk=object.id %}';
var checked = $(this).prop('checked');
var body = {
......
......@@ -5,8 +5,6 @@
{% 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/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
......@@ -96,9 +94,6 @@ function beforeNodeAsync(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 = "";
......@@ -176,7 +171,7 @@ function initTable() {
$(td).html(update_btn + del_btn);
}}
],
ajax_url: '{% url "api-perms:asset-permission-list" %}?display=1',
ajax_url: '{% url "api-perms:asset-permission-list" %}',
columns: [
{data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "assets", orderable: false},
......@@ -213,6 +208,10 @@ $(document).ready(function(){
{title: "{% trans 'Hostname' %}", value: "hostname"},
{title: "{% trans 'Node' %}", value: "node"},
{title: "{% trans 'System user' %}", value: "system_user"},
{title: "{% trans 'Inherit' %}", value: "all", submenu: [
{title: "{% trans 'Include' %}", value: "1"},
{title: "{% trans 'Exclude' %}", value: "0"},
]},
];
initTableFilterDropdown('#permission_list_table_filter input', filterMenu)
})
......
......@@ -2,10 +2,6 @@
{% 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">
......@@ -48,29 +44,19 @@
</div>
</div>
<div class="ibox-content">
<table class="table table-hover">
<table class="table table-hover" id="user_list_table">
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Username' %}</th>
<th></th>
<th class="text-center">{% trans 'Action' %}</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>
......@@ -85,10 +71,7 @@
<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 data-placeholder="{% trans 'Select user' %}" class="select2 users-select2" style="width: 100%" multiple="" tabindex="4">
</select>
</td>
</tr>
......@@ -113,7 +96,7 @@
<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">
<select data-placeholder="{% trans 'Select user groups' %}" class="select2 user-groups-select2" 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 %}
......@@ -128,7 +111,7 @@
</form>
{% for user_group in asset_permission.user_groups.all %}
<tr>
<tr {% if forloop.counter == 1 %} class="no-borders-tr" {% endif %}>
<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>
......@@ -145,120 +128,124 @@
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.users_selected = {};
jumpserver.nodes_selected = {};
var usersDirectRelated = {{ users | safe }};
function addUsers(users) {
var the_url = "{% url 'api-perms:asset-permission-add-user' pk=asset_permission.id %}";
var body = {
users: users
var table;
function initUsersTable() {
var options = {
ele: $('#user_list_table'),
toggle: true,
columnDefs: [
{targets: 2, createdCell: function (td, cellData, rowData) {
var removeBtn = '<a class="btn btn-xs btn-danger m-l-xs btn-remove-user" disabled data-uid="{{ DEFAULT_PK }}"><i class="fa fa-minus"></i></a>'
.replace('{{ DEFAULT_PK }}', cellData);
if (usersDirectRelated.indexOf(cellData) !== -1) {
removeBtn = removeBtn.replace('disabled', '')
}
$(td).html(removeBtn);
}}
],
ajax_url: "{% url 'api-perms:asset-permission-all-users' pk=object.id %}",
columns: [
{data: "user"}, {data: "user_display"}, {data: "user", width: "30px"}
],
op_html: $('#actions').html()
};
table = jumpserver.initServerSideDataTable(options);
return table
}
function addUsers(usersId) {
if (!usersId || usersId.length === 0) {
return
}
var theUrl = "{% url 'api-perms:asset-permissions-users-relation-list' %}";
var body = [];
usersId.forEach(function (v, i) {
body.push({
"user": v,
"assetpermission": "{{ object.id }}"
})
});
var success = function(data) {
location.reload();
};
requestApi({
url: the_url,
url: theUrl,
body: JSON.stringify(body),
method: "POST",
success: success
});
}
function removeUser(users) {
var the_url = "{% url 'api-perms:asset-permission-remove-user' pk=asset_permission.id %}";
var body = {
users: users
function removeUser(userId) {
var theUrl = "{% url 'api-perms:asset-permissions-users-relation-list' %}?user=userId&assetpermission={{ object.id }}";
theUrl = theUrl.replace("userId", userId);
var success = function(data) {
location.reload();
};
requestApi({
url: theUrl,
method: "DELETE",
success: success
});
}
function addGroups(groupsId) {
if (!groupsId || groupsId.length === 0) {
return
}
var theUrl = "{% url 'api-perms:asset-permissions-user-groups-relation-list' %}";
var body = [];
groupsId.forEach(function (v, i) {
body.push({
"usergroup": v,
"assetpermission": "{{ object.id }}"
})
});
var success = function(data) {
location.reload();
};
requestApi({
url: the_url,
url: theUrl,
body: JSON.stringify(body),
method: "POST",
success: success
});
}
function updateGroup(groups) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}";
var body = {
user_groups: groups
function removeGroup(groupId) {
var theUrl = "{% url 'api-perms:asset-permissions-user-groups-relation-list' %}?assetpermission={{ object.id }}";
theUrl = theUrl.replace("groupId", groupId);
var success = function(data) {
location.reload();
};
requestApi({
url: the_url,
body: JSON.stringify(body)
url: theUrl,
method: "DELETE",
success: success
});
}
$(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.nodes_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id]
})
$(".select2").select2();
initUsersTable();
usersSelect2Init(".users-select2", null, usersDirectRelated);
}).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);
var usersSelected = $(".users-select2").val();
addUsers(usersSelected);
}).on('click', '.btn-remove-user', function () {
var user_id = $(this).data("gid");
if (user_id === "") {
return
}
var users = [user_id];
removeUser(users)
var userId = $(this).data("uid");
removeUser(userId)
}).on('click', '#btn-add-group', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var groups = $('.bdg_group').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_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);
var groupsSelected = $(".user-groups-select2").val();
addGroups(groupsSelected);
}).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()
var groupId = $(this).data("gid");
removeGroup(groupId);
})
</script>
{% endblock %}
......@@ -3,8 +3,6 @@
{% 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 %}
......
......@@ -2,11 +2,6 @@
{% 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">
......@@ -244,4 +239,4 @@ $(document).ready(function () {
});
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,10 +2,6 @@
{% 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">
......@@ -161,4 +157,4 @@
removeRemoteApps(remote_apps)
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -2,10 +2,6 @@
{% 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">
......@@ -253,4 +249,4 @@
$tr.remove()
})
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
# coding:utf-8
from django.urls import path, re_path
from rest_framework import routers
from django.urls import re_path
from common import api as capi
from .. import api
from .asset_permission import asset_permission_urlpatterns
from .application_permission import remote_app_permission_urlpatterns
app_name = 'perms'
router = routers.DefaultRouter()
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission')
asset_permission_urlpatterns = [
# Assets
path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
# Assets as tree
path('users/<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
path('users/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
# Nodes
path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
# Node children
path('users/<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
path('users/nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
# Node as tree
path('users/<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
path('users/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
# Node with assets as tree
path('users/<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
path('users/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
# Node children as tree
path('users/<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
path('users/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
# Node children with assets as tree
path('users/<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
path('users/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
# Node assets
path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
path('users/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
# Asset System users
path('users/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'),
path('users/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
# 查询某个用户组授权的资产和资产组
path('user-groups/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
path('user-groups/<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
path('user-groups/<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
path('user-groups/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
path('user-groups/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
# 用户和资产授权变更
path('asset-permissions/<uuid:pk>/users/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'),
path('asset-permissions/<uuid:pk>/users/add/', api.AssetPermissionAddUserApi.as_view(), name='asset-permission-add-user'),
path('asset-permissions/<uuid:pk>/assets/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'),
path('asset-permissions/<uuid:pk>/assets/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'),
# 授权规则中授权的资产
path('asset-permissions/<uuid:pk>/assets/', api.AssetPermissionAssetsApi.as_view(), name='asset-permission-assets'),
# 验证用户是否有某个资产和系统用户的权限
path('asset-permissions/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
# 刷新缓存
path('asset-permissions/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
]
remote_app_permission_urlpatterns = [
# 查询用户授权的RemoteApp
path('users/<uuid:pk>/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'),
path('users/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'),
# 获取用户授权的RemoteApp树
path('users/<uuid:pk>/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='user-remote-apps-as-tree'),
path('users/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='my-remote-apps-as-tree'),
# 查询用户组授权的RemoteApp
path('user-groups/<uuid:pk>/remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'),
# RemoteApp System users
path('users/<uuid:pk>/remote-apps/<uuid:remote_app_id>/system-users/', api.UserGrantedRemoteAppSystemUsersApi.as_view(), name='user-remote-app-system-users'),
path('users/remote-apps/<uuid:remote_app_id>/system-users/', api.UserGrantedRemoteAppSystemUsersApi.as_view(), name='my-remote-app-system-users'),
# 校验用户对RemoteApp的权限
path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'),
# 用户和RemoteApp变更
path('remote-app-permissions/<uuid:pk>/users/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'),
path('remote-app-permissions/<uuid:pk>/users/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'),
path('remote-app-permissions/<uuid:pk>/remote-apps/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'),
path('remote-app-permissions/<uuid:pk>/remote-apps/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
]
old_version_urlpatterns = [
re_path('(?P<resource>user|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api)
]
urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns + old_version_urlpatterns
urlpatterns += router.urls
urlpatterns = asset_permission_urlpatterns + \
remote_app_permission_urlpatterns \
+ old_version_urlpatterns
# coding:utf-8
from django.urls import path
from rest_framework_bulk.routes import BulkRouter
from .. import api
router = BulkRouter()
router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission')
remote_app_permission_urlpatterns = [
# 查询用户授权的RemoteApp
path('users/<uuid:pk>/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'),
path('users/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'),
# 获取用户授权的RemoteApp树
path('users/<uuid:pk>/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='user-remote-apps-as-tree'),
path('users/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='my-remote-apps-as-tree'),
# 查询用户组授权的RemoteApp
path('user-groups/<uuid:pk>/remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'),
# RemoteApp System users
path('users/<uuid:pk>/remote-apps/<uuid:remote_app_id>/system-users/', api.UserGrantedRemoteAppSystemUsersApi.as_view(), name='user-remote-app-system-users'),
path('users/remote-apps/<uuid:remote_app_id>/system-users/', api.UserGrantedRemoteAppSystemUsersApi.as_view(), name='my-remote-app-system-users'),
# 校验用户对RemoteApp的权限
path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'),
# 用户和RemoteApp变更
path('remote-app-permissions/<uuid:pk>/users/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'),
path('remote-app-permissions/<uuid:pk>/users/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'),
path('remote-app-permissions/<uuid:pk>/remote-apps/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'),
path('remote-app-permissions/<uuid:pk>/remote-apps/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
]
remote_app_permission_urlpatterns += router.urls
# coding:utf-8
from django.urls import path, include
from rest_framework_bulk.routes import BulkRouter
from .. import api
router = BulkRouter()
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, 'asset-permissions-users-relation')
router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation')
router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation')
router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation')
router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation')
user_permission_urlpatterns = [
path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
# Assets as tree
path('users/<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
path('users/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
# Nodes
path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
# Node children
path('users/<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
path('users/nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
# Node as tree
path('users/<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
path('users/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
# Node with assets as tree
path('users/<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
path('users/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
# Node children as tree
path('users/<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
path('users/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
# Node children with assets as tree
path('users/<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
path('users/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
# Node assets
path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
path('users/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
# Asset System users
path('users/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'),
path('users/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
]
user_group_permission_urlpatterns = [
# 查询某个用户组授权的资产和资产组
path('user-groups/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
path('user-groups/<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
path('user-groups/<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
path('user-groups/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
path('user-groups/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
]
asset_permission_urlpatterns = [
# Assets
path('', include(user_permission_urlpatterns)),
# 授权规则中授权的资产
path('asset-permissions/<uuid:pk>/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'),
path('asset-permissions/<uuid:pk>/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'),
# 验证用户是否有某个资产和系统用户的权限
path('asset-permissions/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
# 刷新缓存
path('asset-permissions/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
]
asset_permission_urlpatterns += router.urls
......@@ -98,9 +98,7 @@ class AssetPermissionDetailView(PermissionsMixin, DetailView):
context = {
'app': _('Perms'),
'action': _('Asset permission detail'),
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -131,14 +129,13 @@ class AssetPermissionUserView(PermissionsMixin,
return queryset
def get_context_data(self, **kwargs):
user_remain = current_org.get_org_members(exclude=('Auditor',)).exclude(
assetpermission=self.object)
users = [str(i) for i in self.object.users.all().values_list('id', flat=True)]
user_groups_remain = UserGroup.objects.exclude(
assetpermission=self.object)
context = {
'app': _('Perms'),
'action': _('Asset permission user list'),
'users_remain': user_remain,
'users': users,
'user_groups_remain': user_groups_remain,
}
kwargs.update(context)
......@@ -163,13 +160,15 @@ class AssetPermissionAssetView(PermissionsMixin,
return queryset
def get_context_data(self, **kwargs):
nodes_remain = Node.objects.exclude(
id__in=self.object.nodes.all().values_list('id', flat=True)
).only('key')
assets = self.object.assets.all().values_list('id', flat=True)
assets = [str(i) for i in assets]
context = {
'app': _('Perms'),
'assets': assets,
'action': _('Asset permission asset list'),
'nodes_remain': nodes_remain,
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
\ No newline at end of file
return super().get_context_data(**kwargs)
......@@ -245,87 +245,6 @@ class LDAPCacheRefreshAPI(generics.RetrieveAPIView):
return Response(data={'msg': 'success'})
class ReplayStorageCreateAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_data = request.data
if storage_data.get('TYPE') == 'ceph':
port = storage_data.get('PORT')
if port.isdigit():
storage_data['PORT'] = int(storage_data.get('PORT'))
storage_name = storage_data.pop('NAME')
data = {storage_name: storage_data}
if not self.is_valid(storage_data):
return Response({
"error": _("Error: Account invalid (Please make sure the "
"information such as Access key or Secret key is correct)")},
status=401
)
Setting.save_storage('TERMINAL_REPLAY_STORAGE', data)
return Response({"msg": _('Create succeed')}, status=200)
@staticmethod
def is_valid(storage_data):
if storage_data.get('TYPE') == 'server':
return True
storage = jms_storage.get_object_storage(storage_data)
target = 'tests.py'
src = os.path.join(settings.BASE_DIR, 'common', target)
return storage.is_valid(src, target)
class ReplayStorageDeleteAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_REPLAY_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200)
class CommandStorageCreateAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_data = request.data
storage_name = storage_data.pop('NAME')
data = {storage_name: storage_data}
if not self.is_valid(storage_data):
return Response(
{"error": _("Error: Account invalid (Please make sure the "
"information such as Access key or Secret key is correct)")},
status=401
)
Setting.save_storage('TERMINAL_COMMAND_STORAGE', data)
return Response({"msg": _('Create succeed')}, status=200)
@staticmethod
def is_valid(storage_data):
if storage_data.get('TYPE') == 'server':
return True
try:
storage = jms_storage.get_log_storage(storage_data)
except Exception:
return False
return storage.ping()
class CommandStorageDeleteAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200)
class PublicSettingApi(generics.RetrieveAPIView):
permission_classes = ()
serializer_class = PublicSettingSerializer
......
# coding: utf-8
#
from .base import *
from .basic import *
from .email import *
from .ldap import *
from .security import *
from .terminal import *
# coding: utf-8
#
import json
from django import forms
from django.db import transaction
from django.conf import settings
from ..models import Setting
from common.fields import FormEncryptMixin
__all__ = ['BaseForm']
class BaseForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
value = getattr(settings, name, None)
if value is None: # and django_value is None:
continue
if value is not None:
if isinstance(value, dict):
value = json.dumps(value)
initial_value = value
else:
initial_value = ''
field.initial = initial_value
def save(self, category="default"):
if not self.is_bound:
raise ValueError("Form is not bound")
# db_settings = Setting.objects.all()
if not self.is_valid():
raise ValueError(self.errors)
with transaction.atomic():
for name, value in self.cleaned_data.items():
field = self.fields[name]
if isinstance(field.widget, forms.PasswordInput) and not value:
continue
# if value == getattr(settings, name):
# continue
encrypted = True if isinstance(field, FormEncryptMixin) else False
try:
setting = Setting.objects.get(name=name)
except Setting.DoesNotExist:
setting = Setting()
setting.name = name
setting.category = category
setting.encrypted = encrypted
setting.cleaned_value = value
setting.save()
# coding: utf-8
#
from django import forms
from django.utils.translation import ugettext_lazy as _
from .base import BaseForm
__all__ = ['BasicSettingForm']
class BasicSettingForm(BaseForm):
SITE_URL = forms.URLField(
label=_("Current SITE URL"),
help_text="eg: http://jumpserver.abc.com:8080"
)
USER_GUIDE_URL = forms.URLField(
label=_("User Guide URL"), required=False,
help_text=_("User first login update profile done redirect to it")
)
EMAIL_SUBJECT_PREFIX = forms.CharField(
max_length=1024, label=_("Email Subject Prefix"),
help_text=_("Tips: Some word will be intercept by mail provider")
)
# coding: utf-8
#
from django import forms
from django.utils.translation import ugettext_lazy as _
from common.fields import FormEncryptCharField
from .base import BaseForm
__all__ = ['EmailSettingForm', 'EmailContentSettingForm']
class EmailSettingForm(BaseForm):
EMAIL_HOST = forms.CharField(
max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org'
)
EMAIL_PORT = forms.CharField(max_length=5, label=_("SMTP port"), initial=25)
EMAIL_HOST_USER = forms.CharField(
max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org'
)
EMAIL_HOST_PASSWORD = FormEncryptCharField(
max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput,
required=False,
help_text=_("Tips: Some provider use token except password")
)
EMAIL_FROM = forms.CharField(
max_length=128, label=_("Send user"), initial='', required=False,
help_text=_(
"Tips: Send mail account, default SMTP account as the send account"
)
)
EMAIL_RECIPIENT = forms.CharField(
max_length=128, label=_("Test recipient"), initial='', required=False,
help_text=_("Tips: Used only as a test mail recipient")
)
EMAIL_USE_SSL = forms.BooleanField(
label=_("Use SSL"), initial=False, required=False,
help_text=_("If SMTP port is 465, may be select")
)
EMAIL_USE_TLS = forms.BooleanField(
label=_("Use TLS"), initial=False, required=False,
help_text=_("If SMTP port is 587, may be select")
)
class EmailContentSettingForm(BaseForm):
EMAIL_CUSTOM_USER_CREATED_SUBJECT = forms.CharField(
max_length=1024, required=False, label=_("Create user email subject"),
help_text=_("Tips: When creating a user, send the subject of the email"
" (eg:Create account successfully)")
)
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = forms.CharField(
max_length=1024, required=False, label=_("Create user honorific"),
help_text=_("Tips: When creating a user, send the honorific of the "
"email (eg:Hello)")
)
EMAIL_CUSTOM_USER_CREATED_BODY = forms.CharField(
max_length=4096, required=False, widget=forms.Textarea(),
label=_('Create user email content'),
help_text=_('Tips:When creating a user, send the content of the email')
)
EMAIL_CUSTOM_USER_CREATED_SIGNATURE = forms.CharField(
max_length=512, required=False, label=_("Signature"),
help_text=_("Tips: Email signature (eg:jumpserver)")
)
# coding: utf-8
#
from django import forms
from django.utils.translation import ugettext_lazy as _
from common.fields import FormDictField, FormEncryptCharField
from .base import BaseForm
__all__ = ['LDAPSettingForm']
class LDAPSettingForm(BaseForm):
AUTH_LDAP_SERVER_URI = forms.CharField(
label=_("LDAP server"),
)
AUTH_LDAP_BIND_DN = forms.CharField(
required=False, label=_("Bind DN"),
)
AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField(
label=_("Password"),
widget=forms.PasswordInput, required=False
)
AUTH_LDAP_SEARCH_OU = forms.CharField(
label=_("User OU"),
help_text=_("Use | split User OUs"),
required=False,
)
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
label=_("User search filter"),
help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)")
)
AUTH_LDAP_USER_ATTR_MAP = FormDictField(
label=_("User attr map"),
help_text=_(
"User attr map present how to map LDAP user attr to jumpserver, "
"username,name,email is jumpserver attr"
),
)
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
# AUTH_LDAP_START_TLS = forms.BooleanField(
# label=_("Use SSL"), required=False
# )
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False)
# -*- coding: utf-8 -*-
# coding: utf-8
#
import json
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.db import transaction
from .models import Setting, settings
from common.fields import (
FormDictField, FormEncryptCharField, FormEncryptMixin
)
class BaseForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
value = getattr(settings, name, None)
if value is None: # and django_value is None:
continue
if value is not None:
if isinstance(value, dict):
value = json.dumps(value)
initial_value = value
else:
initial_value = ''
field.initial = initial_value
def save(self, category="default"):
if not self.is_bound:
raise ValueError("Form is not bound")
# db_settings = Setting.objects.all()
if not self.is_valid():
raise ValueError(self.errors)
with transaction.atomic():
for name, value in self.cleaned_data.items():
field = self.fields[name]
if isinstance(field.widget, forms.PasswordInput) and not value:
continue
# if value == getattr(settings, name):
# continue
from .base import BaseForm
encrypted = True if isinstance(field, FormEncryptMixin) else False
try:
setting = Setting.objects.get(name=name)
except Setting.DoesNotExist:
setting = Setting()
setting.name = name
setting.category = category
setting.encrypted = encrypted
setting.cleaned_value = value
setting.save()
class BasicSettingForm(BaseForm):
SITE_URL = forms.URLField(
label=_("Current SITE URL"),
help_text="eg: http://jumpserver.abc.com:8080"
)
USER_GUIDE_URL = forms.URLField(
label=_("User Guide URL"), required=False,
help_text=_("User first login update profile done redirect to it")
)
EMAIL_SUBJECT_PREFIX = forms.CharField(
max_length=1024, label=_("Email Subject Prefix"),
help_text=_("Tips: Some word will be intercept by mail provider")
)
class EmailSettingForm(BaseForm):
EMAIL_HOST = forms.CharField(
max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org'
)
EMAIL_PORT = forms.CharField(max_length=5, label=_("SMTP port"), initial=25)
EMAIL_HOST_USER = forms.CharField(
max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org'
)
EMAIL_HOST_PASSWORD = FormEncryptCharField(
max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput,
required=False,
help_text=_("Tips: Some provider use token except password")
)
EMAIL_FROM = forms.CharField(
max_length=128, label=_("Send user"), initial='', required=False,
help_text=_(
"Tips: Send mail account, default SMTP account as the send account"
)
)
EMAIL_RECIPIENT = forms.CharField(
max_length=128, label=_("Test recipient"), initial='', required=False,
help_text=_("Tips: Used only as a test mail recipient")
)
EMAIL_USE_SSL = forms.BooleanField(
label=_("Use SSL"), initial=False, required=False,
help_text=_("If SMTP port is 465, may be select")
)
EMAIL_USE_TLS = forms.BooleanField(
label=_("Use TLS"), initial=False, required=False,
help_text=_("If SMTP port is 587, may be select")
)
class LDAPSettingForm(BaseForm):
AUTH_LDAP_SERVER_URI = forms.CharField(
label=_("LDAP server"),
)
AUTH_LDAP_BIND_DN = forms.CharField(
required=False, label=_("Bind DN"),
)
AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField(
label=_("Password"),
widget=forms.PasswordInput, required=False
)
AUTH_LDAP_SEARCH_OU = forms.CharField(
label=_("User OU"),
help_text=_("Use | split User OUs"),
required=False,
)
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
label=_("User search filter"),
help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)")
)
AUTH_LDAP_USER_ATTR_MAP = FormDictField(
label=_("User attr map"),
help_text=_(
"User attr map present how to map LDAP user attr to jumpserver, "
"username,name,email is jumpserver attr"
),
)
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
# AUTH_LDAP_START_TLS = forms.BooleanField(
# label=_("Use SSL"), required=False
# )
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False)
class TerminalSettingForm(BaseForm):
SORT_BY_CHOICES = (
('hostname', _('Hostname')),
('ip', _('IP')),
)
PAGE_SIZE_CHOICES = (
('all', _('All')),
('auto', _('Auto')),
(10, 10),
(15, 15),
(25, 25),
(50, 50),
)
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
required=False, label=_("Password auth")
)
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
required=False, label=_("Public key auth")
)
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
min_value=5, max_value=99999, label=_("Heartbeat interval"),
help_text=_("Units: seconds")
)
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
choices=SORT_BY_CHOICES, label=_("List sort by")
)
TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField(
choices=PAGE_SIZE_CHOICES, label=_("List page size"),
)
TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField(
min_value=1, max_value=99999, label=_("Session keep duration"),
help_text=_("Units: days, Session, record, command will be delete "
"if more than duration, only in database")
)
TERMINAL_TELNET_REGEX = forms.CharField(
required=False, label=_("Telnet login regex"),
help_text=_("ex: Last\s*login|success|成功")
)
class TerminalCommandStorage(BaseForm):
pass
__all__ = ['SecuritySettingForm']
class SecuritySettingForm(BaseForm):
......@@ -263,27 +91,3 @@ class SecuritySettingForm(BaseForm):
help_text=_('After opening, the user password changes '
'and resets must contain special characters')
)
class EmailContentSettingForm(BaseForm):
EMAIL_CUSTOM_USER_CREATED_SUBJECT = forms.CharField(
max_length=1024, required=False, label=_("Create user email subject"),
help_text=_("Tips: When creating a user, send the subject of the email"
" (eg:Create account successfully)")
)
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = forms.CharField(
max_length=1024, required=False, label=_("Create user honorific"),
help_text=_("Tips: When creating a user, send the honorific of the "
"email (eg:Hello)")
)
EMAIL_CUSTOM_USER_CREATED_BODY = forms.CharField(
max_length=4096, required=False, widget=forms.Textarea(),
label=_('Create user email content'),
help_text=_('Tips:When creating a user, send the content of the email')
)
EMAIL_CUSTOM_USER_CREATED_SIGNATURE = forms.CharField(
max_length=512, required=False, label=_("Signature"),
help_text=_("Tips: Email signature (eg:jumpserver)")
)
# coding: utf-8
#
from django import forms
from django.utils.translation import ugettext_lazy as _
from .base import BaseForm
__all__ = ['TerminalSettingForm']
class TerminalSettingForm(BaseForm):
SORT_BY_CHOICES = (
('hostname', _('Hostname')),
('ip', _('IP')),
)
PAGE_SIZE_CHOICES = (
('all', _('All')),
('auto', _('Auto')),
(10, 10),
(15, 15),
(25, 25),
(50, 50),
)
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
required=False, label=_("Password auth")
)
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
required=False, label=_("Public key auth")
)
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
min_value=5, max_value=99999, label=_("Heartbeat interval"),
help_text=_("Units: seconds")
)
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
choices=SORT_BY_CHOICES, label=_("List sort by")
)
TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField(
choices=PAGE_SIZE_CHOICES, label=_("List page size"),
)
TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField(
min_value=1, max_value=99999, label=_("Session keep duration"),
help_text=_("Units: days, Session, record, command will be delete "
"if more than duration, only in database")
)
TERMINAL_TELNET_REGEX = forms.CharField(
required=False, label=_("Telnet login regex"),
help_text=_("ex: Last\s*login|success|成功")
)
import json
from django.db import models
from django.core.cache import cache
from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.core.cache import cache
from common.utils import get_signer
......@@ -34,12 +33,28 @@ class Setting(models.Model):
comment = models.TextField(verbose_name=_("Comment"))
objects = SettingManager()
cache_key_prefix = '_SETTING_'
def __str__(self):
return self.name
def __getattr__(self, item):
return cache.get(item)
@classmethod
def get(cls, item):
cached = cls.get_from_cache(item)
if cached is not None:
return cached
instances = cls.objects.filter(name=item)
if len(instances) == 1:
s = instances[0]
s.refresh_setting()
return s.cleaned_value
return None
@classmethod
def get_from_cache(cls, item):
key = cls.cache_key_prefix + item
cached = cache.get(key)
return cached
@property
def cleaned_value(self):
......@@ -64,44 +79,6 @@ class Setting(models.Model):
except json.JSONDecodeError as e:
raise ValueError("Json dump error: {}".format(str(e)))
@classmethod
def save_storage(cls, name, data):
"""
:param name: TERMINAL_REPLAY_STORAGE or TERMINAL_COMMAND_STORAGE
:param data: {}
:return: Setting object
"""
obj = cls.objects.filter(name=name).first()
if not obj:
obj = cls()
obj.name = name
obj.encrypted = True
obj.cleaned_value = data
else:
value = obj.cleaned_value
if value is None:
value = {}
value.update(data)
obj.cleaned_value = value
obj.save()
return obj
@classmethod
def delete_storage(cls, name, storage_name):
"""
:param name: TERMINAL_REPLAY_STORAGE or TERMINAL_COMMAND_STORAGE
:param storage_name: ""
:return: bool
"""
obj = cls.objects.filter(name=name).first()
if not obj:
return False
value = obj.cleaned_value
value.pop(storage_name, '')
obj.cleaned_value = value
obj.save()
return True
@classmethod
def refresh_all_settings(cls):
try:
......@@ -112,16 +89,8 @@ class Setting(models.Model):
pass
def refresh_setting(self):
setattr(settings, self.name, self.cleaned_value)
if self.name == "AUTH_LDAP":
if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
old_setting = settings.AUTHENTICATION_BACKENDS
old_setting.insert(0, settings.AUTH_LDAP_BACKEND)
settings.AUTHENTICATION_BACKENDS = old_setting
elif not self.cleaned_value and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
old_setting = settings.AUTHENTICATION_BACKENDS
old_setting.remove(settings.AUTH_LDAP_BACKEND)
settings.AUTHENTICATION_BACKENDS = old_setting
key = self.cache_key_prefix + self.name
cache.set(key, self.cleaned_value, None)
class Meta:
db_table = "settings_setting"
......
# coding: utf-8
#
from .email import *
from .ldap import *
from .public import *
# coding: utf-8
#
from rest_framework import serializers
__all__ = ['MailTestSerializer']
class MailTestSerializer(serializers.Serializer):
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
EMAIL_PORT = serializers.IntegerField(default=25)
EMAIL_HOST_USER = serializers.CharField(max_length=1024)
EMAIL_HOST_PASSWORD = serializers.CharField(required=False, allow_blank=True)
EMAIL_FROM = serializers.CharField(required=False, allow_blank=True)
EMAIL_RECIPIENT = serializers.CharField(required=False, allow_blank=True)
EMAIL_USE_SSL = serializers.BooleanField(default=False)
EMAIL_USE_TLS = serializers.BooleanField(default=False)
from rest_framework import serializers
# coding: utf-8
#
from rest_framework import serializers
class MailTestSerializer(serializers.Serializer):
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
EMAIL_PORT = serializers.IntegerField(default=25)
EMAIL_HOST_USER = serializers.CharField(max_length=1024)
EMAIL_HOST_PASSWORD = serializers.CharField(required=False, allow_blank=True)
EMAIL_FROM = serializers.CharField(required=False, allow_blank=True)
EMAIL_RECIPIENT = serializers.CharField(required=False, allow_blank=True)
EMAIL_USE_SSL = serializers.BooleanField(default=False)
EMAIL_USE_TLS = serializers.BooleanField(default=False)
__all__ = ['LDAPTestSerializer', 'LDAPUserSerializer']
class LDAPTestSerializer(serializers.Serializer):
......@@ -29,6 +23,3 @@ class LDAPUserSerializer(serializers.Serializer):
email = serializers.CharField()
existing = serializers.BooleanField(read_only=True)
class PublicSettingSerializer(serializers.Serializer):
data = serializers.DictField(read_only=True)
# coding: utf-8
#
from rest_framework import serializers
__all__ = ['PublicSettingSerializer']
class PublicSettingSerializer(serializers.Serializer):
data = serializers.DictField(read_only=True)
......@@ -4,9 +4,6 @@ import json
from django.dispatch import receiver
from django.db.models.signals import post_save, pre_save
from django.conf import LazySettings, empty, global_settings
from django.db.utils import ProgrammingError, OperationalError
from django.core.cache import cache
from jumpserver.utils import current_request
from common.utils import get_logger, ssh_key_gen
......@@ -23,56 +20,9 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
@receiver(django_ready)
def monkey_patch_settings(sender, **kwargs):
logger.debug("Monkey patch settings")
cache_key_prefix = '_SETTING_'
custom_need_cache_settings = [
'AUTHENTICATION_BACKENDS', 'TERMINAL_HOST_KEY',
]
custom_no_cache_settings = [
'BASE_DIR', 'VERSION', 'AUTH_OPENID',
]
django_settings = dir(global_settings)
uncached_settings = [i for i in django_settings if i.isupper()]
uncached_settings = [i for i in uncached_settings if not i.startswith('EMAIL')]
uncached_settings = [i for i in uncached_settings if not i.startswith('SESSION_REDIS')]
uncached_settings = [i for i in uncached_settings if i not in custom_need_cache_settings]
uncached_settings.extend(custom_no_cache_settings)
def monkey_patch_getattr(self, name):
if name not in uncached_settings:
key = cache_key_prefix + name
cached = cache.get(key)
if cached is not None:
return cached
if self._wrapped is empty:
self._setup(name)
val = getattr(self._wrapped, name)
return val
def monkey_patch_setattr(self, name, value):
key = cache_key_prefix + name
cache.set(key, value, None)
if name == '_wrapped':
self.__dict__.clear()
else:
self.__dict__.pop(name, None)
super(LazySettings, self).__setattr__(name, value)
def monkey_patch_delattr(self, name):
super(LazySettings, self).__delattr__(name)
self.__dict__.pop(name, None)
key = cache_key_prefix + name
cache.delete(key)
try:
cache.delete_pattern(cache_key_prefix+'*')
LazySettings.__getattr__ = monkey_patch_getattr
LazySettings.__setattr__ = monkey_patch_setattr
LazySettings.__delattr__ = monkey_patch_delattr
Setting.refresh_all_settings()
except (ProgrammingError, OperationalError):
pass
def on_django_ready_add_db_config(sender, **kwargs):
from django.conf import settings
settings.DYNAMIC.db_setting = Setting
@receiver(django_ready)
......
{#{% extends 'base.html' %}#}
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% 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 action="" method="POST" class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label>
<div class="col-md-9">
<select id="id_type" class="selector form-control">
<option value ="server" selected="selected">server</option>
<option value ="es">es (elasticsearch)</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label>
<div class="col-md-9">
<input id="id_name" class="form-control" type="text" name="NAME" value="">
<div id="id_error" style="color: red;"></div>
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_hosts">{% trans "Hosts" %}</label>
<div class="col-md-9">
<input id="id_hosts" class="form-control" type="text" name="HOSTS" value="">
<div class="help-block">{% trans 'Tips: If there are multiple hosts, separate them with a comma (,)' %}</div>
<div class="help-block">eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com</div>
</div>
</div>
{# <div class="form-group" style="display: none;" >#}
{# <label class="col-md-2 control-label" for="id_other">{% trans "Other" %}</label>#}
{# <div class="col-md-9">#}
{# <input id="id_other" class="form-control" type="text" name="OTHER" value="">#}
{# </div>#}
{# </div>#}
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_bucket">{% trans "Index" %}</label>
<div class="col-md-9">
<input id="id_index" class="form-control" type="text" name="INDEX" value="jumpserver">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_doc_type">{% trans "Doc type" %}</label>
<div class="col-md-9">
<input id="id_doc_type" class="form-control" type="text" name="DOC_TYPE" value="command_store">
</div>
</div>
<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>
<a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var field_of_all, need_get_field_of_server, need_get_field_of_es;
function showField(field){
$.each(field, function(index, value){
$(value).parent('div').parent('div').css('display', '');
});
}
function hiddenField(field){
$.each(field, function(index, value){
$(value).parent('div').parent('div').css('display', 'none');
})
}
function getFieldByType(type){
if(type === 'server'){
return need_get_field_of_server
}
else if(type === 'es'){
return need_get_field_of_es
}
}
function ajaxAPI(url, data, success, error){
$.ajax({
url: url,
data: data,
method: 'POST',
contentType: 'application/json; charset=utf-8',
success: success,
error: error
})
}
$(document).ready(function() {
var name_id = '#id_name';
var hosts_id = '#id_hosts';
{#var other_id = '#id_other';#}
var index_id = '#id_index';
var doc_type_id = '#id_doc_type';
field_of_all = [name_id, hosts_id, index_id, doc_type_id];
need_get_field_of_server = [name_id];
need_get_field_of_es = [name_id, hosts_id, index_id, doc_type_id];
})
.on('change', '.selector', function(){
var type = $('.selector').val();
console.log(type);
hiddenField(field_of_all);
var field = getFieldByType(type);
showField(field)
})
.on('click', '#id_submit_button', function(){
var type = $('.selector').val();
var field = getFieldByType(type);
var data = {'TYPE': type};
$.each(field, function(index, id_field){
var name = $(id_field).attr('name');
var value = $(id_field).val();
if(name === 'HOSTS'){
data[name] = value.split(',');
}
else{
data[name] = value
}
});
var url = "{% url 'api-settings:command-storage-create' %}";
var success = function(data, textStatus) {
console.log(data, textStatus);
location = "{% url 'settings:terminal-setting' %}";
};
var error = function(data, textStatus) {
var error_msg = data.responseJSON.error;
$('#id_error').html(error_msg)
};
ajaxAPI(url, JSON.stringify(data), success, error)
})
</script>
{% endblock %}
{#{% extends 'base.html' %}#}
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% 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 action="" method="POST" class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label>
<div class="col-md-9">
<select id="id_type" class="selector form-control">
<option value ="server" selected="selected">server</option>
<option value ="s3">s3</option>
<option value="oss">oss</option>
<option value ="azure">azure</option>
{# <option value="ceph">ceph</option>#}
</select>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label>
<div class="col-md-9">
<input id="id_name" class="form-control" type="text" name="NAME" value="">
<div id="id_error" style="color: red;"></div>
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_host">{% trans "Host" %}</label>
<div class="col-md-9">
<input id="id_host" class="form-control" type="text" name="HOSTNAME" value="" placeholder="Host">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_port">{% trans "Port" %}</label>
<div class="col-md-9">
<input id="id_port" class="form-control" type="text" name="PORT" value="" placeholder="7480">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_bucket">{% trans "Bucket" %}</label>
<div class="col-md-9">
<input id="id_bucket" class="form-control" type="text" name="BUCKET" value="" placeholder="Bucket">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_access_key">{% trans "Access key" %}</label>
<div class="col-md-9">
<input id="id_access_key" class="form-control" type="text" name="ACCESS_KEY" value="" placeholder="Access key">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_secret_key">{% trans "Secret key" %}</label>
<div class="col-md-9">
<input id="id_secret_key" class="form-control" type="text" name="SECRET_KEY" value="", placeholder="Secret key">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_container_name">{% trans "Container name" %}</label>
<div class="col-md-9">
<input id="id_container_name" class="form-control" type="text" name="CONTAINER_NAME" value="" placeholder="Container">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_account_name">{% trans "Account name" %}</label>
<div class="col-md-9">
<input id="id_account_name" class="form-control" type="text" name="ACCOUNT_NAME" value="" placeholder="Account name">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_account_key">{% trans "Account key" %}</label>
<div class="col-md-9">
<input id="id_account_key" class="form-control" type="text" name="ACCOUNT_KEY" value="" placeholder="Account key">
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_endpoint">{% trans "Endpoint" %}</label>
<div class="col-md-9">
<input id="id_endpoint" class="form-control" type="text" name="ENDPOINT" value="" placeholder="Endpoint">
<div id="endpoint_error" style="color: red;"></div>
<div class="help-block">
<span class="oss">
{% trans 'OSS: http://{REGION_NAME}.aliyuncs.com' %}
<br>
{% trans 'Example: http://oss-cn-hangzhou.aliyuncs.com' %}
</span>
<span class="s3">{% trans 'S3: http://s3.{REGION_NAME}.amazonaws.com' %}<br></span>
<span class="s3">{% trans 'S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn' %}<br></span>
<span class="s3">{% trans 'Example: http://s3.cn-north-1.amazonaws.com.cn' %}<br></span>
</div>
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_endpoint_suffix">{% trans "Endpoint suffix" %}</label>
<div class="col-md-9">
<select id="id_endpoint_suffix" name="ENDPOINT_SUFFIX" class="endpoint-suffix-selector form-control">
<option value="core.chinacloudapi.cn" selected="selected">core.chinacloudapi.cn</option>
<option value="core.windows.net">core.windows.net</option>
</select>
</div>
</div>
<div class="form-group" style="display: none;" >
<label class="col-md-2 control-label" for="id_region">{% trans "Region" %}</label>
<div class="col-md-9">
<input id="id_region" class="form-control" type="text" name="REGION" value="" placeholder="">
<div class="help-block">
<span class="s3">
{% trans 'Beijing: cn-north-1' %}
{% trans 'Ningxia: cn-northwest-1' %}
<a href="https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html">{% trans 'More' %}</a>
</span>
</div>
</div>
</div>
<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>
<a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var field_of_all, need_get_field_of_server, need_get_field_of_s3,
need_get_field_of_oss, need_get_field_of_azure, need_get_field_of_ceph;
function showField(field){
$.each(field, function(index, value){
$(value).parent('div').parent('div').css('display', '');
});
}
function hiddenField(field){
$.each(field, function(index, value){
$(value).parent('div').parent('div').css('display', 'none');
})
}
function getFieldByType(type){
if(type === 'server'){
return need_get_field_of_server
}
else if(type === 's3'){
return need_get_field_of_s3
}
else if(type === 'oss'){
return need_get_field_of_oss
}
else if(type === 'azure'){
return need_get_field_of_azure
}
else if(type === 'ceph'){
return need_get_field_of_ceph
}
}
function ajaxAPI(url, data, success, error){
$.ajax({
url: url,
data: data,
method: 'POST',
contentType: 'application/json; charset=utf-8',
success: success,
error: error
})
}
$(document).ready(function() {
var name_id = '#id_name';
var host_id = '#id_host';
var port_id = '#id_port';
var bucket_id = '#id_bucket';
var access_key_id = '#id_access_key';
var secret_key_id = '#id_secret_key';
var container_name_id = '#id_container_name';
var account_name_id = '#id_account_name';
var account_key_id = '#id_account_key';
var endpoint_id = '#id_endpoint';
var endpoint_suffix_id = '#id_endpoint_suffix';
var region_id = '#id_region';
field_of_all = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, container_name_id, account_name_id, account_key_id, endpoint_id, endpoint_suffix_id, region_id];
need_get_field_of_server = [name_id];
need_get_field_of_s3 = [name_id, bucket_id, access_key_id, secret_key_id, endpoint_id];
need_get_field_of_oss = [name_id, bucket_id, access_key_id, secret_key_id, endpoint_id];
need_get_field_of_azure = [name_id, container_name_id, account_name_id, account_key_id, endpoint_suffix_id];
need_get_field_of_ceph = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, region_id];
})
.on('change', '.selector', function(){
var type = $('.selector').val();
$("." + type).show();
hiddenField(field_of_all);
$('.help-block').children().hide();
$('.help-block ' + '.' + type).show();
var field = getFieldByType(type);
showField(field)
})
.on('click', '#id_submit_button', function(){
$('#id_error').html('');
var submitBtn = $("#id_submit_button");
var origin_text = submitBtn.html();
submitBtn.addClass('disabled');
submitBtn.html("{% trans 'Submitting' %}");
var type = $('.selector').val();
var field = getFieldByType(type);
var data = {'TYPE': type};
$.each(field, function(index, id_field){
var name = $(id_field).attr('name');
data[name] = $(id_field).val();
});
if (data['ENDPOINT'] && data['ENDPOINT'].indexOf('http') === -1) {
var msg = "{% trans 'Endpoint need contain protocol, ex: http' %}";
$("#endpoint_error").html(msg);
submitBtn.removeClass('disabled');
submitBtn.html(origin_text);
return
}
var url = "{% url 'api-settings:replay-storage-create' %}";
var success = function(data, textStatus) {
location = "{% url 'settings:terminal-setting' %}";
submitBtn.removeClass('disabled');
submitBtn.html(origin_text);
};
var error = function(data, textStatus) {
var error_msg = data.responseJSON.error;
$('#id_error').html(error_msg);
submitBtn.removeClass('disabled');
submitBtn.html(origin_text);
};
ajaxAPI(url, JSON.stringify(data), success, error);
})
</script>
{% endblock %}
......@@ -44,7 +44,7 @@
<h3>{% trans "Security setting" %}</h3>
{% for field in form %}
{% if forloop.counter == 6 %}
{% if forloop.counter == 8 %}
<div class="hr-line-dashed"></div>
<h3>{% trans "Password check rule" %}</h3>
{% endif %}
......
......@@ -3,6 +3,11 @@
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% block help_message %}
{% trans "Command and Replay storage configuration migrated to" %}
{% trans "Sessions -> Terminal -> Storage configuration" %}
<b><a href="{% url 'terminal:replay-storage-list' %}">{% trans 'Here' %}</a></b>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......@@ -65,7 +70,7 @@
</div>
{% endif %}
{% endfor %}
<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>
......@@ -73,53 +78,6 @@
type="submit">{% trans 'Submit' %}</button>
</div>
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans "Command storage" %}</h3>
<table class="table table-hover " id="task-history-list-table">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
{% for name, setting in command_storage.items %}
<tr>
<td>{{ name }}</td>
<td>{{ setting.TYPE }}</td>
<td><a class="btn btn-xs btn-danger m-l-xs btn-del-command" {% if setting.TYPE == 'server' and name == 'default' %} disabled {% endif %} data-name="{{ name }}">{% trans 'Delete' %}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{% url 'settings:command-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a>
<div class="hr-line-dashed"></div>
<h3>{% trans "Replay storage" %}</h3>
<table class="table table-hover " id="task-history-list-table">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
{% for name, setting in replay_storage.items %}
<tr>
<td>{{ name }}</td>
<td>{{ setting.TYPE }}</td>
<td><a class="btn btn-xs btn-danger m-l-xs btn-del-replay" {% if setting.TYPE == 'server' and name == 'default' %} disabled {% endif %} data-name="{{ name }}">{% trans 'Delete' %}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{% url 'settings:replay-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a>
<div class="hr-line-dashed"></div>
</form>
</div>
</div>
......@@ -131,60 +89,7 @@
{% endblock %}
{% block custom_foot_js %}
<script>
function ajaxAPI(url, data, success, error, method){
$.ajax({
url: url,
data: data,
method: method,
contentType: 'application/json; charset=utf-8',
success: success,
error: error
})
}
function deleteStorage($this, the_url){
var name = $this.data('name');
function doDelete(){
console.log('delete storage');
var data = {"name": name};
var method = 'POST';
var success = function(){
$this.parent().parent().remove();
toastr.success("{% trans 'Delete succeed' %}");
};
var error = function(){
toastr.error("{% trans 'Delete failed' %}");
};
ajaxAPI(the_url, JSON.stringify(data), success, error, method);
}
swal({
title: "{% trans 'Are you sure about deleting it?' %}",
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#ed5565",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: true
}, function () {
doDelete()
});
}
$(document).ready(function () {
})
.on('click', '.btn-del-replay', function(){
var $this = $(this);
var the_url = "{% url 'api-settings:replay-storage-delete' %}";
deleteStorage($this, the_url);
})
.on('click', '.btn-del-command', function() {
var $this = $(this);
var the_url = "{% url 'api-settings:command-storage-delete' %}";
deleteStorage($this, the_url)
});
</script>
{% endblock %}
......@@ -12,9 +12,6 @@ urlpatterns = [
path('ldap/users/', api.LDAPUserListApi.as_view(), name='ldap-user-list'),
path('ldap/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'),
path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'),
path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'),
path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'),
path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'),
path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'),
path('public/', api.PublicSettingApi.as_view(), name='public-setting'),
]
......@@ -12,7 +12,5 @@ urlpatterns = [
url(r'^email-content/$', views.EmailContentSettingView.as_view(), name='email-content-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
url(r'^terminal/replay-storage/create$', views.ReplayStorageCreateView.as_view(), name='replay-storage-create'),
url(r'^terminal/command-storage/create$', views.CommandStorageCreateView.as_view(), name='command-storage-create'),
url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
]
......@@ -4,7 +4,6 @@ from django.contrib import messages
from django.utils.translation import ugettext as _
from common.permissions import PermissionsMixin, IsSuperUser
from common import utils
from .utils import LDAPSyncUtil
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm
......@@ -98,8 +97,9 @@ class TerminalSettingView(PermissionsMixin, TemplateView):
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs):
command_storage = utils.get_command_storage_setting()
replay_storage = utils.get_replay_storage_setting()
from terminal.models import CommandStorage, ReplayStorage
command_storage = CommandStorage.objects.all()
replay_storage = ReplayStorage.objects.all()
context = {
'app': _('Settings'),
......@@ -124,32 +124,6 @@ class TerminalSettingView(PermissionsMixin, TemplateView):
return render(request, self.template_name, context)
class ReplayStorageCreateView(PermissionsMixin, TemplateView):
template_name = 'settings/replay_storage_create.html'
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs):
context = {
'app': _('Settings'),
'action': _('Create replay storage')
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandStorageCreateView(PermissionsMixin, TemplateView):
template_name = 'settings/command_storage_create.html'
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs):
context = {
'app': _('Settings'),
'action': _('Create command storage')
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class SecuritySettingView(PermissionsMixin, TemplateView):
form_class = SecuritySettingForm
template_name = "settings/security_setting.html"
......
......@@ -413,7 +413,7 @@ $.fn.serializeObject = function () {
};
function makeLabel(data) {
return "<label class='detail-key'><b>" + data[0] + ": </b></label>" + data[1] + "</br>"
return "<label class='detail-key'><b>" + data[0] + ": </b></label> " + data[1] + "</br>"
}
function parseTableFilter(value) {
......@@ -600,6 +600,7 @@ jumpserver.initServerSideDataTable = function (options) {
// op_html: 'div.btn-group?',
// paging: true,
// paging_numbers_length: 5;
// hideDefaultDefs: false;
// }
var pagingNumbersLength = 5;
if (options.paging_numbers_length){
......@@ -613,7 +614,8 @@ jumpserver.initServerSideDataTable = function (options) {
orderable: false,
width: "20px",
createdCell: function (td, cellData) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
var data = '<input type="checkbox" class="text-center ipt_check" id=Id>'.replace('Id', cellData);
$(td).html(data);
}
},
{
......@@ -622,6 +624,9 @@ jumpserver.initServerSideDataTable = function (options) {
render: $.fn.dataTable.render.text()
}
];
if (options.hideDefaultDefs) {
columnDefs = [];
}
var select_style = options.select_style || 'multi';
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var select = {
......@@ -635,7 +640,7 @@ jumpserver.initServerSideDataTable = function (options) {
pageLength: options.pageLength || 15,
// dom: options.dom || '<"#uc.pull-left">fltr<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
// dom: options.dom || '<"#uc.pull-left"><"pull-right"<"inline"l><"#fb.inline"><"inline"<"table-filter"f>><"#fa.inline">>tr<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
dom: dom,
dom: options.dom || dom,
order: options.order || [],
buttons: [],
columnDefs: columnDefs,
......@@ -1241,10 +1246,28 @@ function readFile(ref) {
return ref
}
function nodesSelect2Init(selector, url) {
if (!url) {
url = '/api/v1/assets/nodes/'
function select2AjaxInit(option) {
/*
{
selector:
url: ,
disabledData: ,
displayFormat,
idFormat,
}
*/
var selector = option.selector;
var url = option.url;
var disabledData = option.disabledData;
var displayFormat = option.displayFormat || function (data) {
return data.name;
};
var idFormat = option.idFormat || function (data) {
return data.id;
};
return $(selector).select2({
closeOnSelect: false,
ajax: {
......@@ -1260,43 +1283,53 @@ function nodesSelect2Init(selector, url) {
},
processResults: function (data) {
var results = $.map(data.results, function (v, i) {
return {id: v.id, text: v.full_value}
var display = displayFormat(v);
var id = idFormat(v);
var d = {id: id, text: display};
if (disabledData && disabledData.indexOf(v.id) !== -1) {
d.disabled = true;
}
return d;
});
var more = !!data.next;
return {results: results, pagination: {"more": more}}
}
},
})
}
function usersSelect2Init(selector, url) {
function usersSelect2Init(selector, url, disabledData) {
if (!url) {
url = '/api/v1/users/users/'
}
return $(selector).select2({
closeOnSelect: false,
ajax: {
url: url,
data: function (params) {
var page = params.page || 1;
var query = {
search: params.term,
offset: (page - 1) * 10,
limit: 10
};
return query
},
processResults: function (data) {
var results = $.map(data.results, function (v, i) {
var display = v.name + '(' + v.username +')';
return {id: v.id, text: display}
});
var more = !!data.next;
return {results: results, pagination: {"more": more}}
}
},
})
function displayFormat(v) {
return v.name + '(' + v.username +')';
}
var option = {
url: url,
selector: selector,
disabledData: disabledData,
displayFormat: displayFormat
};
return select2AjaxInit(option)
}
function nodesSelect2Init(selector, url, disabledData) {
if (!url) {
url = '/api/v1/assets/nodes/'
}
function displayFormat(v) {
return v.full_value;
}
var option = {
url: url,
selector: selector,
disabledData: disabledData,
displayFormat: displayFormat
};
return select2AjaxInit(option)
}
function showCeleryTaskLog(taskId) {
......@@ -1324,7 +1357,7 @@ function initDateRangePicker(selector, options) {
timePicker24Hour: true,
autoApply: true,
};
var userLang = navigator.language || navigator.userLanguage;;
var userLang = navigator.language || navigator.userLanguage;
if (userLang.indexOf('zh') !== -1) {
defaultOption.locale = zhLocale;
}
......
/*! Select2 4.0.12 | https://github.com/select2/select2/blob/master/LICENSE.md */
!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(n){return"请删除"+(n.input.length-n.maximum)+"个字符"},inputTooShort:function(n){return"请再输入至少"+(n.minimum-n.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(n){return"最多只能选择"+n.maximum+"个项目"},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),n.define,n.require}();
......@@ -3,8 +3,6 @@
{% 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>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
{% block custom_head_css_js_create %} {% endblock %}
{% endblock %}
......
{% 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">
......
......@@ -8,6 +8,8 @@
<script src="{% static "js/inspinia.js" %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}?v=5"></script>
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static 'js/plugins/select2/i18n/zh-CN.js' %}"></script>
<script>
activeNav("{{ FORCE_SCRIPT_NAME }}");
$(document).ready(function(){
......@@ -16,5 +18,9 @@ $(document).ready(function(){
if ($('.tooltip')[0]) {
$('.tooltip').tooltip();
}
var userLang = navigator.language || navigator.userLanguage;
if (userLang.indexOf('zh') !== -1) {
$.fn.select2.defaults.set('language', 'zh-CN')
}
});
</script>
......@@ -13,3 +13,5 @@
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
......@@ -477,4 +477,4 @@ $(document).ready(function(){
);
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -4,3 +4,4 @@ from .terminal import *
from .session import *
from .command import *
from .task import *
from .storage import *
......@@ -12,7 +12,7 @@ import jms_storage
from common.utils import is_uuid, get_logger
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor
from common.filters import DatetimeRangeFilter
from common.drf.filters import DatetimeRangeFilter
from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import SystemUser
from ..models import Session
......
# coding: utf-8
#
from rest_framework import viewsets, generics, status
from rest_framework.response import Response
from django.utils.translation import ugettext_lazy as _
from common.permissions import IsSuperUser
from ..models import CommandStorage, ReplayStorage
from ..serializers import CommandStorageSerializer, ReplayStorageSerializer
__all__ = [
'CommandStorageViewSet', 'CommandStorageTestConnectiveApi',
'ReplayStorageViewSet', 'ReplayStorageTestConnectiveApi'
]
class BaseStorageViewSetMixin:
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if not instance.can_delete():
data = {'msg': _('Deleting the default storage is not allowed')}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
return super().destroy(request, *args, **kwargs)
class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet):
filter_fields = ('name', 'type',)
search_fields = filter_fields
queryset = CommandStorage.objects.all()
serializer_class = CommandStorageSerializer
permission_classes = (IsSuperUser,)
class ReplayStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet):
filter_fields = ('name', 'type',)
search_fields = filter_fields
queryset = ReplayStorage.objects.all()
serializer_class = ReplayStorageSerializer
permission_classes = (IsSuperUser,)
class BaseStorageTestConnectiveMixin:
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
try:
is_valid = instance.is_valid()
except Exception as e:
is_valid = False
msg = _("Test failure: {}".format(str(e)))
else:
if is_valid:
msg = _("Test successful")
else:
msg = _("Test failure: Account invalid")
data = {
'is_valid': is_valid,
'msg': msg
}
return Response(data)
class CommandStorageTestConnectiveApi(BaseStorageTestConnectiveMixin,
generics.RetrieveAPIView):
queryset = CommandStorage.objects.all()
class ReplayStorageTestConnectiveApi(BaseStorageTestConnectiveMixin,
generics.RetrieveAPIView):
queryset = ReplayStorage.objects.all()
from importlib import import_module
from django.conf import settings
from .command.serializers import SessionCommandSerializer
from ..const import COMMAND_STORAGE_TYPE_SERVER
from common import utils
TYPE_ENGINE_MAPPING = {
'elasticsearch': 'terminal.backends.command.es',
......@@ -18,19 +18,18 @@ def get_command_storage():
def get_terminal_command_storages():
from ..models import CommandStorage
storage_list = {}
command_storage = utils.get_command_storage_setting()
for name, params in command_storage.items():
tp = params['TYPE']
if tp == 'server':
for s in CommandStorage.objects.all():
tp = s.type
if tp == COMMAND_STORAGE_TYPE_SERVER:
storage = get_command_storage()
else:
if not TYPE_ENGINE_MAPPING.get(tp):
continue
engine_class = import_module(TYPE_ENGINE_MAPPING[tp])
storage = engine_class.CommandStore(params)
storage_list[name] = storage
storage = engine_class.CommandStore(s.config)
storage_list[s.name] = storage
return storage_list
......
......@@ -5,3 +5,86 @@ ASSETS_CACHE_KEY = "terminal__session__assets"
USERS_CACHE_KEY = "terminal__session__users"
SYSTEM_USER_CACHE_KEY = "terminal__session__system_users"
# Replay Storage
REPLAY_STORAGE_TYPE_NULL = 'null'
REPLAY_STORAGE_TYPE_SERVER = 'server'
REPLAY_STORAGE_TYPE_S3 = 's3'
REPLAY_STORAGE_TYPE_OSS = 'oss'
REPLAY_STORAGE_TYPE_AZURE = 'azure'
REPLAY_STORAGE_TYPE_EMPTY_FIELDS = []
REPLAY_STORAGE_TYPE_S3_FIELDS = [
{'name': 'BUCKET'},
{'name': 'ACCESS_KEY', 'write_only': True},
{'name': 'SECRET_KEY', 'write_only': True},
{'name': 'ENDPOINT'}
]
REPLAY_STORAGE_TYPE_OSS_FIELDS = [
{'name': 'BUCKET'},
{'name': 'ACCESS_KEY', 'write_only': True},
{'name': 'SECRET_KEY', 'write_only': True},
{'name': 'ENDPOINT'}
]
REPLAY_STORAGE_TYPE_AZURE_FIELDS = [
{'name': 'CONTAINER_NAME'},
{'name': 'ACCOUNT_NAME'},
{'name': 'ACCOUNT_KEY', 'write_only': True},
{'name': 'ENDPOINT_SUFFIX'}
]
REPLAY_STORAGE_TYPE_MAP_FIELDS = {
REPLAY_STORAGE_TYPE_NULL: REPLAY_STORAGE_TYPE_EMPTY_FIELDS,
REPLAY_STORAGE_TYPE_SERVER: REPLAY_STORAGE_TYPE_EMPTY_FIELDS,
REPLAY_STORAGE_TYPE_S3: REPLAY_STORAGE_TYPE_S3_FIELDS,
REPLAY_STORAGE_TYPE_OSS: REPLAY_STORAGE_TYPE_OSS_FIELDS,
REPLAY_STORAGE_TYPE_AZURE: REPLAY_STORAGE_TYPE_AZURE_FIELDS
}
REPLAY_STORAGE_TYPE_CHOICES_DEFAULT = [
(REPLAY_STORAGE_TYPE_NULL, 'Null'),
(REPLAY_STORAGE_TYPE_SERVER, 'Server'),
]
REPLAY_STORAGE_TYPE_CHOICES_EXTENDS = [
(REPLAY_STORAGE_TYPE_S3, 'S3'),
(REPLAY_STORAGE_TYPE_OSS, 'OSS'),
(REPLAY_STORAGE_TYPE_AZURE, 'Azure')
]
REPLAY_STORAGE_TYPE_CHOICES = REPLAY_STORAGE_TYPE_CHOICES_DEFAULT + \
REPLAY_STORAGE_TYPE_CHOICES_EXTENDS
# Command Storage
COMMAND_STORAGE_TYPE_NULL = 'null'
COMMAND_STORAGE_TYPE_SERVER = 'server'
COMMAND_STORAGE_TYPE_ES = 'es'
COMMAND_STORAGE_TYPE_EMPTY_FIELDS = []
COMMAND_STORAGE_TYPE_ES_FIELDS = [
{'name': 'HOSTS'},
{'name': 'INDEX'},
{'name': 'DOC_TYPE'}
]
COMMAND_STORAGE_TYPE_MAP_FIELDS = {
COMMAND_STORAGE_TYPE_NULL: COMMAND_STORAGE_TYPE_EMPTY_FIELDS,
COMMAND_STORAGE_TYPE_SERVER: COMMAND_STORAGE_TYPE_EMPTY_FIELDS,
COMMAND_STORAGE_TYPE_ES: COMMAND_STORAGE_TYPE_ES_FIELDS,
}
COMMAND_STORAGE_TYPE_CHOICES_DEFAULT = [
(COMMAND_STORAGE_TYPE_NULL, 'Null'),
(COMMAND_STORAGE_TYPE_SERVER, 'Server'),
]
COMMAND_STORAGE_TYPE_CHOICES_EXTENDS = [
(COMMAND_STORAGE_TYPE_ES, 'Elasticsearch')
]
COMMAND_STORAGE_TYPE_CHOICES = COMMAND_STORAGE_TYPE_CHOICES_DEFAULT + \
COMMAND_STORAGE_TYPE_CHOICES_EXTENDS
# coding: utf-8
#
from .terminal import *
from .storage import *
# coding: utf-8
#
from django import forms
from django.utils.translation import ugettext_lazy as _
from terminal.models import ReplayStorage, CommandStorage
__all__ = [
'ReplayStorageAzureForm', 'ReplayStorageOSSForm', 'ReplayStorageS3Form',
'CommandStorageTypeESForm',
]
class BaseStorageForm(forms.Form):
def __init__(self, *args, **kwargs):
super(BaseStorageForm, self).__init__(*args, **kwargs)
self.fields['type'].widget.attrs['disabled'] = True
self.fields.move_to_end('comment')
class BaseReplayStorageForm(BaseStorageForm, forms.ModelForm):
class Meta:
model = ReplayStorage
fields = ['name', 'type', 'comment']
class BaseCommandStorageForm(BaseStorageForm, forms.ModelForm):
class Meta:
model = CommandStorage
fields = ['name', 'type', 'comment']
class ReplayStorageAzureForm(BaseReplayStorageForm):
azure_container_name = forms.CharField(
max_length=128, label=_('Container name'), required=False
)
azure_account_name = forms.CharField(
max_length=128, label=_('Account name'), required=False
)
azure_account_key = forms.CharField(
max_length=128, label=_('Account key'), required=False,
widget=forms.PasswordInput
)
azure_endpoint_suffix = forms.ChoiceField(
choices=(
('core.chinacloudapi.cn', 'core.chinacloudapi.cn'),
('core.windows.net', 'core.windows.net')
),
label=_('Endpoint suffix'), required=False,
)
class ReplayStorageOSSForm(BaseReplayStorageForm):
oss_bucket = forms.CharField(
max_length=128, label=_('Bucket'), required=False
)
oss_access_key = forms.CharField(
max_length=128, label=_('Access key'), required=False,
widget=forms.PasswordInput
)
oss_secret_key = forms.CharField(
max_length=128, label=_('Secret key'), required=False,
widget=forms.PasswordInput
)
oss_endpoint = forms.CharField(
max_length=128, label=_('Endpoint'), required=False,
help_text=_(
"""
OSS: http://{REGION_NAME}.aliyuncs.com <br>
Example: http://oss-cn-hangzhou.aliyuncs.com
"""
)
)
class ReplayStorageS3Form(BaseReplayStorageForm):
s3_bucket = forms.CharField(
max_length=128, label=_('Bucket'), required=False
)
s3_access_key = forms.CharField(
max_length=128, label=_('Access key'), required=False,
widget=forms.PasswordInput
)
s3_secret_key = forms.CharField(
max_length=128, label=_('Secret key'), required=False,
widget=forms.PasswordInput
)
s3_endpoint = forms.CharField(
max_length=128, label=_('Endpoint'), required=False,
help_text=_(
"""
S3: http://s3.{REGION_NAME}.amazonaws.com <br>
S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn <br>
Example: http://s3.cn-north-1.amazonaws.com.cn
"""
)
)
class CommandStorageTypeESForm(BaseCommandStorageForm):
es_hosts = forms.CharField(
max_length=128, label=_('Hosts'), required=False,
help_text=_(
"""
Tips: If there are multiple hosts, separate them with a comma (,)
<br>
eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com
"""
)
)
es_index = forms.CharField(
max_length=128, label=_('Index'), required=False
)
es_doc_type = forms.CharField(
max_length=128, label=_('Doc type'), required=False
)
# ~*~ coding: utf-8 ~*~
# coding: utf-8
#
__all__ = ['TerminalForm']
from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import Terminal
from ..models import Terminal, ReplayStorage, CommandStorage
def get_all_command_storage():
from common import utils
command_storage = utils.get_command_storage_setting()
for k, v in command_storage.items():
yield (k, k)
for c in CommandStorage.objects.all():
yield (c.name, c.name)
def get_all_replay_storage():
from common import utils
replay_storage = utils.get_replay_storage_setting()
for k, v in replay_storage.items():
yield (k, k)
for r in ReplayStorage.objects.all():
yield (r.name, r.name)
class TerminalForm(forms.ModelForm):
......
# Generated by Django 2.2.5 on 2019-11-22 10:07
import common.fields.model
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('terminal', '0015_auto_20190923_1529'),
]
operations = [
migrations.CreateModel(
name='CommandStorage',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('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')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('name', models.CharField(max_length=32, unique=True, verbose_name='Name')),
('type', models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('es', 'Elasticsearch')], default='server', max_length=16, verbose_name='Type')),
('meta', common.fields.model.EncryptJsonDictTextField(default={})),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='ReplayStorage',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('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')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('name', models.CharField(max_length=32, unique=True, verbose_name='Name')),
('type', models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('oss', 'OSS'), ('azure', 'Azure')], default='server', max_length=16, verbose_name='Type')),
('meta', common.fields.model.EncryptJsonDictTextField(default={})),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
],
options={
'abstract': False,
},
),
]
# Generated by Django 2.2.5 on 2019-11-25 01:31
from django.db import migrations
def get_storage_data(s):
from common.utils import get_signer
import json
signer = get_signer()
value = s.value
encrypted = s.encrypted
if encrypted:
value = signer.unsign(value)
try:
value = json.loads(value)
except:
value = {}
return value
def get_setting(apps, schema_editor, key):
model = apps.get_model('settings', 'Setting')
db_alias = schema_editor.connection.alias
setting = model.objects.using(db_alias).filter(name=key)
if not setting:
return
return setting[0]
def init_storage_data(model):
model.objects.update_or_create(
name='null', type='null',
defaults={
'name': 'null', 'type': 'null',
'comment': "Do not save"
}
)
model.objects.update_or_create(
name='default', type='server',
defaults={
'name': 'default', 'type': 'server',
'comment': "Store locally"
}
)
def migrate_command_storage(apps, schema_editor):
model = apps.get_model("terminal", "CommandStorage")
init_storage_data(model)
setting = get_setting(apps, schema_editor, "TERMINAL_COMMAND_STORAGE")
if not setting:
return
values = get_storage_data(setting)
for name, meta in values.items():
tp = meta.pop("TYPE")
if not tp or name in ['default', 'null']:
continue
model.objects.create(name=name, type=tp, meta=meta)
def migrate_replay_storage(apps, schema_editor):
model = apps.get_model("terminal", "ReplayStorage")
init_storage_data(model)
setting = get_setting(apps, schema_editor, "TERMINAL_REPLAY_STORAGE")
if not setting:
return
values = get_storage_data(setting)
for name, meta in values.items():
tp = meta.pop("TYPE", None)
if not tp or name in ['default', 'null']:
continue
model.objects.create(name=name, type=tp, meta=meta)
class Migration(migrations.Migration):
dependencies = [
('terminal', '0016_commandstorage_replaystorage'),
]
operations = [
migrations.RunPython(migrate_command_storage),
migrations.RunPython(migrate_replay_storage),
]
# Generated by Django 2.2.7 on 2019-12-02 02:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0017_auto_20191125_0931'),
]
operations = [
migrations.RemoveField(
model_name='session',
name='date_last_active',
),
migrations.AlterField(
model_name='session',
name='remote_addr',
field=models.CharField(blank=True, max_length=128, null=True,
verbose_name='Remote addr'),
),
migrations.AddField(
model_name='session',
name='asset_id',
field=models.CharField(blank=True, db_index=True, default='',
max_length=36),
),
migrations.AddField(
model_name='session',
name='system_user_id',
field=models.CharField(blank=True, db_index=True, default='',
max_length=36),
),
migrations.AddField(
model_name='session',
name='user_id',
field=models.CharField(blank=True, db_index=True, default='',
max_length=36),
),
migrations.AlterField(
model_name='session',
name='asset',
field=models.CharField(db_index=True, max_length=1024,
verbose_name='Asset'),
),
migrations.AlterField(
model_name='session',
name='protocol',
field=models.CharField(
choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('vnc', 'vnc'),
('telnet', 'telnet')], db_index=True, default='ssh',
max_length=8),
),
migrations.AlterField(
model_name='session',
name='system_user',
field=models.CharField(db_index=True, max_length=128,
verbose_name='System user'),
),
migrations.AlterField(
model_name='session',
name='user',
field=models.CharField(db_index=True, max_length=128,
verbose_name='User'),
),
]
......@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import os
import uuid
import jms_storage
from django.db import models
from django.db.models.signals import post_save
......@@ -13,9 +14,11 @@ from django.core.cache import cache
from users.models import User
from orgs.mixins.models import OrgModelMixin
from common.utils import get_command_storage_setting, get_replay_storage_setting
from common.mixins import CommonModelMixin
from common.fields.model import EncryptJsonDictTextField
from .backends import get_multi_command_storage
from .backends.command.models import AbstractSessionCommand
from . import const
class Terminal(models.Model):
......@@ -55,21 +58,21 @@ class Terminal(models.Model):
self.user.is_active = active
self.user.save()
def get_command_storage_setting(self):
storage_all = get_command_storage_setting()
if self.command_storage in storage_all:
storage = storage_all.get(self.command_storage)
def get_command_storage_config(self):
s = CommandStorage.objects.filter(name=self.command_storage).first()
if s:
config = s.config
else:
storage = storage_all.get('default')
return {"TERMINAL_COMMAND_STORAGE": storage}
config = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
return {"TERMINAL_COMMAND_STORAGE": config}
def get_replay_storage_setting(self):
storage_all = get_replay_storage_setting()
if self.replay_storage in storage_all:
storage = storage_all.get(self.replay_storage)
def get_replay_storage_config(self):
s = ReplayStorage.objects.filter(name=self.replay_storage).first()
if s:
config = s.config
else:
storage = storage_all.get('default')
return {"TERMINAL_REPLAY_STORAGE": storage}
config = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
return {"TERMINAL_REPLAY_STORAGE": config}
@property
def config(self):
......@@ -78,8 +81,8 @@ class Terminal(models.Model):
if not k.startswith('TERMINAL'):
continue
configs[k] = getattr(settings, k)
configs.update(self.get_command_storage_setting())
configs.update(self.get_replay_storage_setting())
configs.update(self.get_command_storage_config())
configs.update(self.get_replay_storage_config())
configs.update({
'SECURITY_MAX_IDLE_TIME': settings.SECURITY_MAX_IDLE_TIME
})
......@@ -153,17 +156,19 @@ class Session(OrgModelMixin):
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_("User"))
asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
system_user = models.CharField(max_length=128, verbose_name=_("System user"))
user = models.CharField(max_length=128, verbose_name=_("User"), db_index=True)
user_id = models.CharField(blank=True, default='', max_length=36, db_index=True)
asset = models.CharField(max_length=1024, verbose_name=_("Asset"), db_index=True)
asset_id = models.CharField(blank=True, default='', max_length=36, db_index=True)
system_user = models.CharField(max_length=128, verbose_name=_("System user"), db_index=True)
system_user_id = models.CharField(blank=True, default='', max_length=36, db_index=True)
login_from = models.CharField(max_length=2, choices=LOGIN_FROM_CHOICES, default="ST")
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
is_finished = models.BooleanField(default=False)
has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
has_command = models.BooleanField(default=False, verbose_name=_("Command"))
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL)
protocol = models.CharField(choices=PROTOCOL_CHOICES, default='ssh', max_length=8)
date_last_active = models.DateTimeField(verbose_name=_("Date last active"), default=timezone.now)
protocol = models.CharField(choices=PROTOCOL_CHOICES, default='ssh', max_length=8, db_index=True)
date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now)
date_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
......@@ -282,3 +287,80 @@ class Command(AbstractSessionCommand):
class Meta:
db_table = "terminal_command"
ordering = ('-timestamp',)
class CommandStorage(CommonModelMixin):
TYPE_CHOICES = const.COMMAND_STORAGE_TYPE_CHOICES
TYPE_DEFAULTS = dict(const.REPLAY_STORAGE_TYPE_CHOICES_DEFAULT).keys()
TYPE_SERVER = const.COMMAND_STORAGE_TYPE_SERVER
name = models.CharField(max_length=32, verbose_name=_("Name"), unique=True)
type = models.CharField(
max_length=16, choices=TYPE_CHOICES, verbose_name=_('Type'),
default=TYPE_SERVER
)
meta = EncryptJsonDictTextField(default={})
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
def __str__(self):
return self.name
@property
def config(self):
config = self.meta
config.update({'TYPE': self.type})
return config
def in_defaults(self):
return self.type in self.TYPE_DEFAULTS
def is_valid(self):
if self.in_defaults():
return True
storage = jms_storage.get_log_storage(self.config)
return storage.ping()
def can_delete(self):
return not self.in_defaults()
class ReplayStorage(CommonModelMixin):
TYPE_CHOICES = const.REPLAY_STORAGE_TYPE_CHOICES
TYPE_SERVER = const.REPLAY_STORAGE_TYPE_SERVER
TYPE_DEFAULTS = dict(const.REPLAY_STORAGE_TYPE_CHOICES_DEFAULT).keys()
name = models.CharField(max_length=32, verbose_name=_("Name"), unique=True)
type = models.CharField(
max_length=16, choices=TYPE_CHOICES, verbose_name=_('Type'),
default=TYPE_SERVER
)
meta = EncryptJsonDictTextField(default={})
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
def __str__(self):
return self.name
@property
def config(self):
config = self.meta
config.update({'TYPE': self.type})
return config
def in_defaults(self):
return self.type in self.TYPE_DEFAULTS
def is_valid(self):
if self.in_defaults():
return True
storage = jms_storage.get_object_storage(self.config)
target = 'tests.py'
src = os.path.join(settings.BASE_DIR, 'common', target)
return storage.is_valid(src, target)
def can_delete(self):
return not self.in_defaults()
# -*- coding: utf-8 -*-
#
from .v1 import *
from .terminal import *
from .session import *
from .storage import *
from rest_framework import serializers
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer
from ..models import Session
class SessionSerializer(BulkOrgResourceModelSerializer):
command_amount = serializers.IntegerField(read_only=True)
org_id = serializers.CharField(allow_blank=True)
class Meta:
model = Session
list_serializer_class = AdaptedBulkListSerializer
fields = [
"id", "user", "asset", "system_user", "login_from",
"login_from_display", "remote_addr", "is_finished",
"has_replay", "can_replay", "protocol", "date_start", "date_end",
"terminal", "command_amount",
]
class ReplaySerializer(serializers.Serializer):
file = serializers.FileField(allow_empty_file=True)
# -*- coding: utf-8 -*-
#
import copy
from rest_framework import serializers
from common.fields.serializer import CustomMetaDictField
from ..models import ReplayStorage, CommandStorage
from .. import const
class ReplayStorageMetaDictField(CustomMetaDictField):
type_map_fields = const.REPLAY_STORAGE_TYPE_MAP_FIELDS
default_type = const.REPLAY_STORAGE_TYPE_SERVER
need_convert_key = True
class BaseStorageSerializerMixin:
type_map_fields = None
def process_meta(self, instance, validated_data):
new_meta = copy.deepcopy(validated_data.get('meta', {}))
tp = validated_data.get('type', '')
if tp != instance.type:
return new_meta
old_meta = instance.meta
fields = self.type_map_fields.get(instance.type, [])
for field in fields:
if not field.get('write_only', False):
continue
field_name = field['name']
new_value = new_meta.get(field_name, '')
old_value = old_meta.get(field_name, '')
field_value = new_value if new_value else old_value
new_meta[field_name] = field_value
return new_meta
def update(self, instance, validated_data):
meta = self.process_meta(instance, validated_data)
validated_data['meta'] = meta
return super().update(instance, validated_data)
class ReplayStorageSerializer(BaseStorageSerializerMixin,
serializers.ModelSerializer):
meta = ReplayStorageMetaDictField()
type_map_fields = const.REPLAY_STORAGE_TYPE_MAP_FIELDS
class Meta:
model = ReplayStorage
fields = ['id', 'name', 'type', 'meta', 'comment']
class CommandStorageMetaDictField(CustomMetaDictField):
type_map_fields = const.COMMAND_STORAGE_TYPE_MAP_FIELDS
default_type = const.COMMAND_STORAGE_TYPE_SERVER
need_convert_key = True
class CommandStorageSerializer(BaseStorageSerializerMixin,
serializers.ModelSerializer):
meta = CommandStorageMetaDictField()
type_map_fields = const.COMMAND_STORAGE_TYPE_MAP_FIELDS
class Meta:
model = CommandStorage
fields = ['id', 'name', 'type', 'meta', 'comment']
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Terminal, Status, Session, Task
from ..models import (
Terminal, Status, Session, Task
)
class TerminalSerializer(serializers.ModelSerializer):
......@@ -25,21 +24,6 @@ class TerminalSerializer(serializers.ModelSerializer):
return Session.objects.filter(terminal=obj, is_finished=False).count()
class SessionSerializer(BulkOrgResourceModelSerializer):
command_amount = serializers.IntegerField(read_only=True)
org_id = serializers.CharField(allow_blank=True)
class Meta:
model = Session
list_serializer_class = AdaptedBulkListSerializer
fields = [
"id", "user", "asset", "system_user", "login_from",
"login_from_display", "remote_addr", "is_finished",
"has_replay", "can_replay", "protocol", "date_start", "date_end",
"terminal", "command_amount",
]
class StatusSerializer(serializers.ModelSerializer):
class Meta:
fields = ['id', 'terminal']
......@@ -52,9 +36,3 @@ class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):
fields = '__all__'
model = Task
list_serializer_class = AdaptedBulkListSerializer
class ReplaySerializer(serializers.Serializer):
file = serializers.FileField(allow_empty_file=True)
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="StorageForm" method="post" class="form-horizontal">
{% bootstrap_form form 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 storageCreateUrl, storageUpdateUrl, storageListUrl = null;
var type_id = '#' + '{{ form.type.id_for_label }}';
var api_action = "{{ api_action }}";
function getFormDataType(){
return $(type_id+ " option:selected").val();
}
function getFormData(form){
var data = form.serializeObject();
data['type'] = getFormDataType();
data['meta'] = constructFormDataMeta(data);
return data
}
$(document).ready(function () {
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = storageCreateUrl;
var method = "POST";
{% if api_action == "update" %}
the_url = storageUpdateUrl.replace('{{ DEFAULT_PK }}', '{{ object.id }}');
method = "PUT";
{% endif %}
var redirect_to = storageListUrl;
var form = $("form");
var data = getFormData(form);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}
{% extends 'base.html' %}
{% load i18n static %}
{% block content %}
<div class="wrapper wrapper-content animated fadeIn">
<div class="col-lg-12">
<div class="tabs-container">
<ul class="nav nav-tabs">
<li {% if is_replay %}class="active" {% endif %}><a href="{% url 'terminal:replay-storage-list' %}"> {% trans 'Replay storage' %}</a></li>
<li {% if is_command %}class="active" {% endif %}><a href="{% url 'terminal:command-storage-list' %}" >{% trans 'Command storage' %}</a></li>
</ul>
<div class="tab-content">
<div id="my-tickets" class="tab-pane active">
<div class="panel-body">
<div class="btn-group uc pull-left m-r-5">
<button class="btn btn-sm btn-primary">
{% block create_storage_info %}{% endblock %}
</button>
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
<ul class="dropdown-menu">
{% for key, value in type_choices %}
<li><a class="" href="{% block create_storage_url %}{% endblock %}?type={{ key }}">{{ value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover" id="storage_list_table" >
<thead>
<tr>
<th class="text-center">
<input id="" type="checkbox" class="ipt_check_all">
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center" style="width: 150px">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
var storage_table , storageTableAjaxUrl , storageUpdateUrl , storageDeleteUrl , storageTestConnectiveUrl = null ;
function initTable() {
var options = {
ele: $('#storage_list_table'),
columnDefs: [
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(rowData.name);
var del_btn, update_btn;
if (['server', 'null'].indexOf(rowData.type) === -1){
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', name);
update_btn = '<a class="btn btn-xs m-l-xs btn-info btn-update" data-uid="{{ DEFAULT_PK }}">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
}
else{
del_btn = '<a class="btn btn-xs btn-danger disabled m-l-xs btn-del" data-uid="{{ DEFAULT_PK }}" mark=1 data-name="99991938">{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', name);
update_btn = '<a class="btn btn-xs m-l-xs disabled btn-info btn-update" data-uid="{{ DEFAULT_PK }}">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
}
var test_btn = '<a class="btn btn-xs btn-primary m-l-xs btn-test-connective" data-uid="{{ DEFAULT_PK }}">{% trans 'Test' %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn + test_btn);
}
}
],
ajax_url: storageTableAjaxUrl,
columns: [
{data: "id"}, {data: "name" }, {data: "type"}, {data: "comment"},
{data: "id", orderable: false,}
],
op_html: $('#actions').html()
};
storage_table = jumpserver.initServerSideDataTable(options);
return storage_table
}
$(document).ready(function(){
initTable()
})
.on('click', '.btn-update', function (){
var $this = $(this);
var uid = $this.data('uid');
window.location.href = storageUpdateUrl.replace('{{ DEFAULT_PK }}', uid);
})
.on('click', '.btn-del', function () {
var $this = $(this);
var uid = $this.data('uid');
var name = $this.data('name');
var the_url = storageDeleteUrl.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
})
.on('click', '.btn-test-connective', function () {
var $this = $(this);
var uid = $this.data('uid');
var the_url = storageTestConnectiveUrl.replace('{{ DEFAULT_PK }}', uid);
var error = function (data) {
toastr.error(data)
};
var success = function(data) {
var isValid = data.is_valid;
if (isValid){
toastr.success(data.msg)
}
else{
toastr.error(data.msg)
}
};
requestApi({
url: the_url,
error: error,
method: 'GET',
success: success,
flash_message: false
});
})
</script>
{% endblock %}
{% extends 'terminal/base_storage_create_update.html' %}
{% load i18n static %}
{% block custom_foot_js %}
{{ block.super }}
<script type="text/javascript">
storageCreateUrl = "{% url 'api-terminal:command-storage-list' %}";
storageUpdateUrl = "{% url 'api-terminal:command-storage-detail' pk=DEFAULT_PK %}";
storageListUrl = "{% url "terminal:command-storage-list" %}";
function constructFormDataMeta(data){
var meta = {};
var type = data.type;
for (var k in data){
if (k.startsWith(type)){
var v = data[k];
if (k === 'es_hosts'){
v = v.split(',');
}
meta[k] = v;
delete data[k]
}
}
return meta
}
</script>
{% endblock %}
{% extends 'terminal/base_storage_list.html' %}
{% load i18n static %}
{% block create_storage_url %}{% url "terminal:command-storage-create" %}{% endblock%}
{% block create_storage_info %}{% trans 'Create command storage' %}{% endblock %}
{% block custom_foot_js %}
{{ block.super }}
<script>
storageTableAjaxUrl = '{% url "api-terminal:command-storage-list" %}';
storageUpdateUrl = '{% url "terminal:command-storage-update" pk=DEFAULT_PK %}';
storageDeleteUrl = '{% url "api-terminal:command-storage-detail" pk=DEFAULT_PK %}';
storageTestConnectiveUrl = '{% url "api-terminal:command-storage-test-connective" pk=DEFAULT_PK %}';
</script>
{% endblock %}
{% extends 'terminal/base_storage_create_update.html' %}
{% load i18n static %}
{% block custom_foot_js %}
{{ block.super }}
<script type="text/javascript">
storageCreateUrl = "{% url 'api-terminal:replay-storage-list' %}";
storageUpdateUrl = "{% url 'api-terminal:replay-storage-detail' pk=DEFAULT_PK %}";
storageListUrl = "{% url "terminal:replay-storage-list" %}";
function constructFormDataMeta(data){
var meta = {};
var type = data.type;
for (var k in data){
if (k.startsWith(type)){
meta[k] = data[k];
delete data[k]
}
}
return meta
}
</script>
{% endblock %}
{% extends 'terminal/base_storage_list.html' %}
{% load i18n static %}
{% block create_storage_url %}{% url "terminal:replay-storage-create" %}{% endblock%}
{% block create_storage_info %}{% trans 'Create replay storage' %}{% endblock %}
{% block custom_foot_js %}
{{ block.super }}
<script>
storageTableAjaxUrl = '{% url "api-terminal:replay-storage-list" %}';
storageUpdateUrl = '{% url "terminal:replay-storage-update" pk=DEFAULT_PK %}';
storageDeleteUrl = '{% url "api-terminal:replay-storage-detail" pk=DEFAULT_PK %}';
storageTestConnectiveUrl = '{% url "api-terminal:replay-storage-test-connective" pk=DEFAULT_PK %}';
</script>
{% endblock %}
......@@ -5,8 +5,6 @@
{% load common_tags %}
{% block custom_head_css_js %}
<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>
{% endblock %}
{% block content_left_head %}
......
......@@ -18,6 +18,7 @@
{% block table_search %}{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5"><a href="{% url "terminal:replay-storage-list" %}" class="btn btn-sm btn-primary"> {% trans "Storage configuration" %} </a></div>
<table class="table table-striped table-bordered table-hover " id="terminal_list_table" >
<thead>
<tr>
......
......@@ -3,8 +3,6 @@
{% 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 %}
......
......@@ -18,6 +18,8 @@ router.register(r'terminals', api.TerminalViewSet, 'terminal')
router.register(r'tasks', api.TaskViewSet, 'tasks')
router.register(r'commands', api.CommandViewSet, 'command')
router.register(r'status', api.StatusViewSet, 'status')
router.register(r'replay-storages', api.ReplayStorageViewSet, 'replay-storage')
router.register(r'command-storages', api.CommandStorageViewSet, 'command-storage')
urlpatterns = [
path('sessions/<uuid:pk>/replay/',
......@@ -27,7 +29,9 @@ urlpatterns = [
path('terminals/<uuid:terminal>/access-key/', api.TerminalTokenApi.as_view(),
name='terminal-access-key'),
path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'),
path('commands/export/', api.CommandExportApi.as_view(), name="command-export")
path('commands/export/', api.CommandExportApi.as_view(), name="command-export"),
path('replay-storages/<uuid:pk>/test-connective/', api.ReplayStorageTestConnectiveApi.as_view(), name='replay-storage-test-connective'),
path('command-storages/<uuid:pk>/test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective')
# v2: get session's replay
# path('v2/sessions/<uuid:pk>/replay/',
# api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
......
......@@ -26,4 +26,14 @@ urlpatterns = [
# Command view
path('command/', views.CommandListView.as_view(), name='command-list'),
# replay-storage
path('terminal/replay-storage/', views.ReplayStorageListView.as_view(), name='replay-storage-list'),
path('terminal/replay-storage/create/', views.ReplayStorageCreateView.as_view(), name='replay-storage-create'),
path('terminal/replay-storage/<uuid:pk>/update/', views.ReplayStorageUpdateView.as_view(), name='replay-storage-update'),
# command-storage
path('terminal/command-storage/', views.CommandStorageListView.as_view(), name='command-storage-list'),
path('terminal/command-storage/create/', views.CommandStorageCreateView.as_view(), name='command-storage-create'),
path('terminal/command-storage/<uuid:pk>/update/', views.CommandStorageUpdateView.as_view(), name='command-storage-update'),
]
......@@ -3,3 +3,7 @@
from .terminal import *
from .session import *
from .command import *
from .storage import *
# from .replay_storage import *
# from .command_storage import *
# coding: utf-8
#
from django.http import Http404
from django.views.generic import TemplateView
from django.views.generic.edit import CreateView, UpdateView
from django.utils.translation import ugettext as _
from common.permissions import PermissionsMixin, IsSuperUser
from terminal.models import ReplayStorage, CommandStorage
from .. import forms, const
__all__ = [
'ReplayStorageListView', 'ReplayStorageCreateView',
'ReplayStorageUpdateView', 'CommandStorageListView',
'CommandStorageCreateView', 'CommandStorageUpdateView'
]
class ReplayStorageListView(PermissionsMixin, TemplateView):
template_name = 'terminal/replay_storage_list.html'
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs):
context = {
'app': _('Terminal'),
'action': _('Replay storage list'),
'is_replay': True,
'type_choices': const.REPLAY_STORAGE_TYPE_CHOICES_EXTENDS,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandStorageListView(PermissionsMixin, TemplateView):
template_name = 'terminal/command_storage_list.html'
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs):
context = {
'app': _('Terminal'),
'action': _('Command storage list'),
'type_choices': const.COMMAND_STORAGE_TYPE_CHOICES_EXTENDS,
'is_command': True,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class BaseStorageCreateUpdateViewMixin:
permission_classes = [IsSuperUser]
default_type = None
form_class = None
form_class_choices = {}
def get_initial(self):
return {'type': self.get_type()}
def get_type(self):
return self.default_type
def get_form_class(self):
tp = self.get_type()
form_class = self.form_class_choices.get(tp)
if not form_class:
raise Http404()
return form_class
class ReplayStorageCreateUpdateViewMixin(BaseStorageCreateUpdateViewMixin):
model = ReplayStorage
default_type = const.REPLAY_STORAGE_TYPE_S3
form_class = forms.ReplayStorageS3Form
form_class_choices = {
const.REPLAY_STORAGE_TYPE_S3: forms.ReplayStorageS3Form,
const.REPLAY_STORAGE_TYPE_OSS: forms.ReplayStorageOSSForm,
const.REPLAY_STORAGE_TYPE_AZURE: forms.ReplayStorageAzureForm
}
class ReplayStorageCreateView(ReplayStorageCreateUpdateViewMixin,
PermissionsMixin, CreateView):
template_name = 'terminal/replay_storage_create_update.html'
def get_type(self):
tp = self.request.GET.get("type")
if tp:
return tp.lower()
return super().get_type()
def get_context_data(self, **kwargs):
context = {
'app': _('Terminal'),
'action': _('Create replay storage'),
'api_action': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class ReplayStorageUpdateView(ReplayStorageCreateUpdateViewMixin,
PermissionsMixin, UpdateView):
template_name = 'terminal/replay_storage_create_update.html'
def get_initial(self):
initial_data = super().get_initial()
for k, v in self.object.meta.items():
_k = "{}_{}".format(self.object.type, k.lower())
initial_data[_k] = v
return initial_data
def get_type(self):
return self.object.type
def get_context_data(self, **kwargs):
context = {
'app': _('Terminal'),
'action': _('Update replay storage'),
'api_action': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandStorageCreateUpdateViewMixin(BaseStorageCreateUpdateViewMixin):
model = CommandStorage
default_type = const.COMMAND_STORAGE_TYPE_ES
form_class = forms.CommandStorageTypeESForm
form_class_choices = {
const.COMMAND_STORAGE_TYPE_ES: forms.CommandStorageTypeESForm
}
class CommandStorageCreateView(CommandStorageCreateUpdateViewMixin,
PermissionsMixin, CreateView):
template_name = 'terminal/command_storage_create_update.html'
def get_type(self):
tp = self.request.GET.get("type")
if tp:
return tp.lower()
return super().get_type()
def get_context_data(self, **kwargs):
context = {
'app': _('Terminal'),
'action': _('Create command storage'),
'api_action': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandStorageUpdateView(CommandStorageCreateUpdateViewMixin,
PermissionsMixin, UpdateView):
template_name = 'terminal/command_storage_create_update.html'
def get_initial(self):
initial_data = super().get_initial()
for k, v in self.object.meta.items():
_k = "{}_{}".format(self.object.type, k.lower())
if k == 'HOSTS':
v = ','.join(v)
initial_data[_k] = v
return initial_data
def get_type(self):
return self.object.type
def get_context_data(self, **kwargs):
context = {
'app': _('Terminal'),
'action': _('Update command storage'),
'api_action': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -32,8 +32,6 @@ class TicketSerializer(serializers.ModelSerializer):
if action and user not in instance.assignees.all():
error = {"action": "Only assignees can update"}
raise serializers.ValidationError(error)
print(validated_data)
print(instance.status)
if instance.status == instance.STATUS_CLOSED:
validated_data.pop('action')
instance = super().update(instance, validated_data)
......
......@@ -19,8 +19,8 @@ class UserUserGroupRelationViewSet(BulkModelViewSet):
def get_queryset(self):
queryset = User.groups.through.objects.all()\
.annotate(user_name=F('user__name'))\
.annotate(usergroup_name=F('usergroup__name'))
.annotate(user_display=F('user__name'))\
.annotate(usergroup_display=F('usergroup__name'))
return queryset
def allow_bulk_destroy(self, qs, filtered):
......
......@@ -384,7 +384,7 @@ class MFAMixin:
@staticmethod
def mfa_is_otp():
if settings.CONFIG.OTP_IN_RADIUS:
if settings.OTP_IN_RADIUS:
return False
return True
......@@ -401,7 +401,7 @@ class MFAMixin:
return check_otp_code(self.otp_secret_key, code)
def check_mfa(self, code):
if settings.CONFIG.OTP_IN_RADIUS:
if settings.OTP_IN_RADIUS:
return self.check_radius(code)
else:
return self.check_otp(code)
......@@ -540,6 +540,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
return True
return False
def set_avatar(self, f):
self.avatar.save(self.username, f)
def avatar_url(self):
admin_default = settings.STATIC_URL + "img/avatar/admin.png"
user_default = settings.STATIC_URL + "img/avatar/user.png"
......
......@@ -8,9 +8,11 @@ __all__ = ['UserUserGroupRelationSerializer']
class UserUserGroupRelationSerializer(serializers.ModelSerializer):
user_name = serializers.CharField(read_only=True)
usergroup_name = serializers.CharField(read_only=True)
user_display = serializers.CharField(read_only=True)
usergroup_display = serializers.CharField(read_only=True)
class Meta:
model = User.groups.through
fields = ['id', 'user', 'user_name', 'usergroup', 'usergroup_name']
fields = [
'id', 'user', 'user_display', 'usergroup', 'usergroup_display'
]
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/sweetalert/sweetalert.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 'users:user-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
</li>
<li>
<a href="{% url 'users:user-granted-asset' pk=object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
</li>
<li class="active">
<a href="{% url 'users:user-asset-permission' pk=object.id %}" class="text-center"><i class="fa fa-edit"></i> {% trans 'Asset permission' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-10" 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">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover"
id="permission_list_table"
style="width: 100%">
<thead>
<tr>
<th></th>
<th>{% trans 'Name' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Node' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include '_filter_dropdown.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.nodes_selected = {};
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(", ")])
}
if (d.actions.length > 0) {
data += makeLabel(["{% trans 'Action' %}", d.actions.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) {
cellData = htmlEscape(cellData);
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 name = htmlEscape(rowData.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 }}" mark=1 data-name="99991938">{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', 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" %}?user_id={{ object.id }}',
columns: [
{data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "assets", orderable: false},
{data: "nodes", orderable: false}, {data: "system_users", orderable: false},
{data: "is_valid", orderable: false}, {data: "id", orderable: false, width: "100px"}
],
select: {},
op_html: $('#actions').html()
};
table = jumpserver.initServerSideDataTable(options);
return table
}
$(document).ready(function() {
initTable();
var filterMenu = [
{title: "{% trans 'Name' %}", value: "name"},
{title: "{% trans 'Validity' %}", value: "is_valid"},
{title: "{% trans 'IP' %}", value: "ip"},
{title: "{% trans 'Hostname' %}", value: "hostname"},
{title: "{% trans 'Node' %}", value: "node"},
{title: "{% trans 'System user' %}", value: "system_user"},
{title: "{% trans 'Inherit' %}", value: "all", submenu: [
{title: "{% trans 'Include' %}", value: "1"},
{title: "{% trans 'Exclude' %}", value: "0"},
]},
];
initTableFilterDropdown('#permission_list_table_filter input', filterMenu)
})
.on('click', '#is_active', function() {
var the_url = "{% url 'api-users:user-detail' pk=object.id %}";
var checked = $(this).prop('checked');
var body = {
'is_active': checked
};
var success = '{% trans "Update successfully!" %}';
requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
});
})
.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');
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
row.child.hide();
// Remove from the 'open' array
detailRows.splice(idx, 1);
}
else {
tr.addClass('details');
$(this).children('i:first-child').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>
{% endblock %}
......@@ -3,9 +3,7 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
......@@ -21,6 +19,9 @@
<li>
<a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
</li>
<li>
<a href="{% url 'users:user-asset-permission' pk=user_object.id %}" class="text-center"><i class="fa fa-edit"></i> {% trans 'Asset permission' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline {% if can_update %} btn-default {% else %} disabled {% endif %}" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
......
......@@ -20,6 +20,9 @@
<li class="active">
<a href="{% url 'users:user-granted-asset' pk=object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
</li>
<li>
<a href="{% url 'users:user-asset-permission' pk=object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset permission' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
......
......@@ -2,10 +2,6 @@
{% load static %}
{% load i18n %}
{% 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>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......
......@@ -3,13 +3,8 @@
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/datatables/datatables.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css" %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
<script src="{% static "js/plugins/datatables/datatables.min.js" %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......@@ -98,7 +93,7 @@
<tr>
<td ><b class="bdg_user" data-uid={{ user.id }}>{{ user.name }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn_remove_user" type="button"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger pull-right btn-xs btn-remove-user" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
......@@ -132,7 +127,7 @@ function updateGroupMember(users) {
$('.user_edit tbody').append(
'<tr>' +
'<td><b class="bdg_user" data-uid="' + index + '">' + user_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_remove_user" type="button"><i class="fa fa-minus"></i></button></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-user" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
......@@ -156,7 +151,7 @@ $(document).ready(function () {
delete jumpserver.users_selected[data.id]
});
usersSelect2Init('#slct_users')
}).on('click', '.btn_remove_user', function() {
}).on('click', '.btn-remove-user', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_user');
......
......@@ -35,6 +35,7 @@ urlpatterns = [
path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
path('user/<uuid:pk>/', views.UserDetailView.as_view(), name='user-detail'),
path('user/<uuid:pk>/assets/', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
path('user/<uuid:pk>/asset-permissions/', views.UserAssetPermissionListView.as_view(), name='user-asset-permission'),
path('user/<uuid:pk>/login-history/', views.UserDetailView.as_view(), name='user-login-history'),
# User group view
......
......@@ -2,4 +2,5 @@
from .login import *
from .user import *
from .profile import *
from .group import *
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django.contrib.auth import authenticate
from django.core.cache import cache
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse_lazy, reverse
from django.utils.translation import ugettext as _
from django.views import View
from django.views.generic.base import TemplateView
from django.views.generic.edit import (
UpdateView, FormView
)
from django.contrib.auth import logout as auth_logout
from common.utils import get_logger, ssh_key_gen
from common.permissions import (
PermissionsMixin, IsValidUser,
UserCanUpdatePassword, UserCanUpdateSSHKey,
)
from .. import forms
from ..models import User
from ..utils import generate_otp_uri, check_otp_code, \
get_user_or_tmp_user, get_password_check_rules, check_password_rules
__all__ = [
'UserProfileView',
'UserProfileUpdateView', 'UserPasswordUpdateView',
'UserPublicKeyUpdateView', 'UserPublicKeyGenerateView',
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
'UserOtpDisableAuthenticationView', 'UserOtpUpdateView',
]
logger = get_logger(__name__)
class UserProfileView(PermissionsMixin, TemplateView):
template_name = 'users/user_profile.html'
permission_classes = [IsValidUser]
def get_context_data(self, **kwargs):
mfa_setting = settings.SECURITY_MFA_AUTH
context = {
'action': _('Profile'),
'mfa_setting': mfa_setting if mfa_setting is not None else False,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserProfileUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_profile_update.html'
model = User
permission_classes = [IsValidUser]
form_class = forms.UserProfileForm
success_url = reverse_lazy('users:user-profile')
def get_object(self, queryset=None):
return self.request.user
def get_context_data(self, **kwargs):
context = {
'app': _('User'),
'action': _('Profile setting'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserPasswordUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_password_update.html'
model = User
form_class = forms.UserPasswordForm
success_url = reverse_lazy('users:user-profile')
permission_classes = [IsValidUser, UserCanUpdatePassword]
def get_object(self, queryset=None):
return self.request.user
def get_context_data(self, **kwargs):
check_rules = get_password_check_rules()
context = {
'app': _('Users'),
'action': _('Password update'),
'password_check_rules': check_rules,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_url(self):
auth_logout(self.request)
return super().get_success_url()
def form_valid(self, form):
password = form.cleaned_data.get('new_password')
is_ok = check_password_rules(password)
if not is_ok:
form.add_error(
"new_password",
_("* Your password does not meet the requirements")
)
return self.form_invalid(form)
return super().form_valid(form)
class UserPublicKeyUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_pubkey_update.html'
model = User
form_class = forms.UserPublicKeyForm
permission_classes = [IsValidUser, UserCanUpdateSSHKey]
success_url = reverse_lazy('users:user-profile')
def get_object(self, queryset=None):
return self.request.user
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('Public key update'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserPublicKeyGenerateView(PermissionsMixin, View):
permission_classes = [IsValidUser]
def get(self, request, *args, **kwargs):
private, public = ssh_key_gen(username=request.user.username, hostname='jumpserver')
request.user.public_key = public
request.user.save()
response = HttpResponse(private, content_type='text/plain')
filename = "{0}-jumpserver.pem".format(request.user.username)
response['Content-Disposition'] = 'attachment; filename={}'.format(filename)
return response
class UserOtpEnableAuthenticationView(FormView):
template_name = 'users/user_password_authentication.html'
form_class = forms.UserCheckPasswordForm
def get_form(self, form_class=None):
user = get_user_or_tmp_user(self.request)
form = super().get_form(form_class=form_class)
form['username'].initial = user.username
return form
def get_context_data(self, **kwargs):
user = get_user_or_tmp_user(self.request)
context = {
'user': user
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def form_valid(self, form):
user = get_user_or_tmp_user(self.request)
password = form.cleaned_data.get('password')
user = authenticate(username=user.username, password=password)
if not user:
form.add_error("password", _("Password invalid"))
return self.form_invalid(form)
return redirect(self.get_success_url())
def get_success_url(self):
return reverse('users:user-otp-enable-install-app')
class UserOtpEnableInstallAppView(TemplateView):
template_name = 'users/user_otp_enable_install_app.html'
def get_context_data(self, **kwargs):
user = get_user_or_tmp_user(self.request)
context = {
'user': user
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserOtpEnableBindView(TemplateView, FormView):
template_name = 'users/user_otp_enable_bind.html'
form_class = forms.UserCheckOtpCodeForm
success_url = reverse_lazy('users:user-otp-settings-success')
def get_context_data(self, **kwargs):
user = get_user_or_tmp_user(self.request)
otp_uri, otp_secret_key = generate_otp_uri(self.request)
context = {
'otp_uri': otp_uri,
'otp_secret_key': otp_secret_key,
'user': user
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def form_valid(self, form):
otp_code = form.cleaned_data.get('otp_code')
otp_secret_key = cache.get(self.request.session.session_key+'otp_key', '')
if check_otp_code(otp_secret_key, otp_code):
self.save_otp(otp_secret_key)
return super().form_valid(form)
else:
form.add_error("otp_code", _("MFA code invalid, or ntp sync server time"))
return self.form_invalid(form)
def save_otp(self, otp_secret_key):
user = get_user_or_tmp_user(self.request)
user.enable_otp()
user.otp_secret_key = otp_secret_key
user.save()
class UserOtpDisableAuthenticationView(FormView):
template_name = 'users/user_otp_authentication.html'
form_class = forms.UserCheckOtpCodeForm
success_url = reverse_lazy('users:user-otp-settings-success')
def form_valid(self, form):
user = self.request.user
otp_code = form.cleaned_data.get('otp_code')
otp_secret_key = user.otp_secret_key
if check_otp_code(otp_secret_key, otp_code):
user.disable_otp()
user.save()
return super().form_valid(form)
else:
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
return super().form_invalid(form)
class UserOtpUpdateView(UserOtpDisableAuthenticationView):
success_url = reverse_lazy('users:user-otp-enable-bind')
class UserOtpSettingsSuccessView(TemplateView):
template_name = 'flash_message_standalone.html'
# def get(self, request, *args, **kwargs):
# return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
title, describe = self.get_title_describe()
context = {
'title': title,
'messages': describe,
'interval': 1,
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_title_describe(self):
user = get_user_or_tmp_user(self.request)
if self.request.user.is_authenticated:
auth_logout(self.request)
title = _('MFA enable success')
describe = _('MFA enable success, return login page')
if not user.otp_enabled:
title = _('MFA disable success')
describe = _('MFA disable success, return login page')
return title, describe
......@@ -4,48 +4,35 @@ from __future__ import unicode_literals
from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse_lazy, reverse
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.views import View
from django.views.generic.base import TemplateView
from django.views.generic.edit import (
CreateView, UpdateView, FormView
CreateView, UpdateView
)
from django.views.generic.detail import DetailView
from django.contrib.auth import logout as auth_logout
from common.const import (
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
)
from common.utils import get_logger, ssh_key_gen
from common.utils import get_logger
from common.permissions import (
PermissionsMixin, IsOrgAdmin, IsValidUser,
UserCanUpdatePassword, UserCanUpdateSSHKey,
PermissionsMixin, IsOrgAdmin,
CanUpdateDeleteUser,
)
from orgs.utils import current_org
from .. import forms
from ..models import User, UserGroup
from ..utils import generate_otp_uri, check_otp_code, \
get_user_or_tmp_user, get_password_check_rules, check_password_rules, \
is_need_unblock
from ..utils import get_password_check_rules, is_need_unblock
from ..signals import post_user_create
__all__ = [
'UserListView', 'UserCreateView', 'UserDetailView',
'UserUpdateView', 'UserGrantedAssetView', 'UserProfileView',
'UserProfileUpdateView', 'UserPasswordUpdateView',
'UserPublicKeyUpdateView', 'UserBulkUpdateView',
'UserPublicKeyGenerateView',
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
'UserOtpDisableAuthenticationView', 'UserOtpUpdateView'
'UserUpdateView', 'UserGrantedAssetView',
'UserBulkUpdateView', 'UserAssetPermissionListView',
]
logger = get_logger(__name__)
......@@ -221,239 +208,15 @@ class UserGrantedAssetView(PermissionsMixin, DetailView):
return super().get_context_data(**kwargs)
class UserProfileView(PermissionsMixin, TemplateView):
template_name = 'users/user_profile.html'
permission_classes = [IsValidUser]
def get_context_data(self, **kwargs):
mfa_setting = settings.SECURITY_MFA_AUTH
context = {
'action': _('Profile'),
'mfa_setting': mfa_setting if mfa_setting is not None else False,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserProfileUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_profile_update.html'
model = User
permission_classes = [IsValidUser]
form_class = forms.UserProfileForm
success_url = reverse_lazy('users:user-profile')
def get_object(self, queryset=None):
return self.request.user
def get_context_data(self, **kwargs):
context = {
'app': _('User'),
'action': _('Profile setting'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserPasswordUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_password_update.html'
class UserAssetPermissionListView(PermissionsMixin, DetailView):
model = User
form_class = forms.UserPasswordForm
success_url = reverse_lazy('users:user-profile')
permission_classes = [IsValidUser, UserCanUpdatePassword]
def get_object(self, queryset=None):
return self.request.user
def get_context_data(self, **kwargs):
check_rules = get_password_check_rules()
context = {
'app': _('Users'),
'action': _('Password update'),
'password_check_rules': check_rules,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_url(self):
auth_logout(self.request)
return super().get_success_url()
def form_valid(self, form):
password = form.cleaned_data.get('new_password')
is_ok = check_password_rules(password)
if not is_ok:
form.add_error(
"new_password",
_("* Your password does not meet the requirements")
)
return self.form_invalid(form)
return super().form_valid(form)
class UserPublicKeyUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_pubkey_update.html'
model = User
form_class = forms.UserPublicKeyForm
permission_classes = [IsValidUser, UserCanUpdateSSHKey]
success_url = reverse_lazy('users:user-profile')
def get_object(self, queryset=None):
return self.request.user
template_name = 'users/user_asset_permission.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('Public key update'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserPublicKeyGenerateView(PermissionsMixin, View):
permission_classes = [IsValidUser]
def get(self, request, *args, **kwargs):
private, public = ssh_key_gen(username=request.user.username, hostname='jumpserver')
request.user.public_key = public
request.user.save()
response = HttpResponse(private, content_type='text/plain')
filename = "{0}-jumpserver.pem".format(request.user.username)
response['Content-Disposition'] = 'attachment; filename={}'.format(filename)
return response
class UserOtpEnableAuthenticationView(FormView):
template_name = 'users/user_password_authentication.html'
form_class = forms.UserCheckPasswordForm
def get_form(self, form_class=None):
user = get_user_or_tmp_user(self.request)
form = super().get_form(form_class=form_class)
form['username'].initial = user.username
return form
def get_context_data(self, **kwargs):
user = get_user_or_tmp_user(self.request)
context = {
'user': user
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def form_valid(self, form):
user = get_user_or_tmp_user(self.request)
password = form.cleaned_data.get('password')
user = authenticate(username=user.username, password=password)
if not user:
form.add_error("password", _("Password invalid"))
return self.form_invalid(form)
if user.mfa_is_otp():
return redirect(self.get_success_url())
else:
user.enable_mfa()
user.save()
return redirect('users:user-otp-settings-success')
def get_success_url(self):
return reverse('users:user-otp-enable-install-app')
class UserOtpEnableInstallAppView(TemplateView):
template_name = 'users/user_otp_enable_install_app.html'
def get_context_data(self, **kwargs):
user = get_user_or_tmp_user(self.request)
context = {
'user': user
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserOtpEnableBindView(TemplateView, FormView):
template_name = 'users/user_otp_enable_bind.html'
form_class = forms.UserCheckOtpCodeForm
success_url = reverse_lazy('users:user-otp-settings-success')
def get_context_data(self, **kwargs):
user = get_user_or_tmp_user(self.request)
otp_uri, otp_secret_key = generate_otp_uri(self.request)
context = {
'otp_uri': otp_uri,
'otp_secret_key': otp_secret_key,
'user': user
'action': _('Asset permission'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def form_valid(self, form):
otp_code = form.cleaned_data.get('otp_code')
otp_secret_key = cache.get(self.request.session.session_key+'otp_key', '')
if check_otp_code(otp_secret_key, otp_code):
self.save_otp(otp_secret_key)
return super().form_valid(form)
else:
form.add_error("otp_code", _("MFA code invalid, or ntp sync server time"))
return self.form_invalid(form)
def save_otp(self, otp_secret_key):
user = get_user_or_tmp_user(self.request)
user.enable_mfa()
user.otp_secret_key = otp_secret_key
user.save()
class UserOtpDisableAuthenticationView(FormView):
template_name = 'users/user_otp_authentication.html'
form_class = forms.UserCheckOtpCodeForm
success_url = reverse_lazy('users:user-otp-settings-success')
def form_valid(self, form):
user = self.request.user
otp_code = form.cleaned_data.get('otp_code')
otp_secret_key = user.otp_secret_key
if check_otp_code(otp_secret_key, otp_code):
user.disable_mfa()
user.save()
return super().form_valid(form)
else:
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
return super().form_invalid(form)
class UserOtpUpdateView(UserOtpDisableAuthenticationView):
success_url = reverse_lazy('users:user-otp-enable-bind')
class UserOtpSettingsSuccessView(TemplateView):
template_name = 'flash_message_standalone.html'
# def get(self, request, *args, **kwargs):
# return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
title, describe = self.get_title_describe()
context = {
'title': title,
'messages': describe,
'interval': 1,
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_title_describe(self):
user = get_user_or_tmp_user(self.request)
if self.request.user.is_authenticated:
auth_logout(self.request)
title = _('MFA enable success')
describe = _('MFA enable success, return login page')
if not user.mfa_enabled:
title = _('MFA disable success')
describe = _('MFA disable success, return login page')
return title, describe
......@@ -19,28 +19,23 @@ from daemon import pidfile
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE_DIR)
logging.basicConfig(level=logging.DEBUG)
try:
from apps.jumpserver import const
__version__ = const.VERSION
except ImportError as e:
logging.info("Not found __version__: {}".format(e))
logging.info("Sys path: {}".format(sys.path))
logging.info("Python is: ")
print("Not found __version__: {}".format(e))
print("Python is: ")
logging.info(subprocess.call('which python', shell=True))
__version__ = 'Unknown'
try:
import apps
logging.info("List apps: {}".format(os.listdir('apps')))
logging.info('apps is: {}'.format(apps))
except:
pass
sys.exit(1)
try:
from apps.jumpserver.conf import load_user_config
CONFIG = load_user_config()
from apps.jumpserver.const import CONFIG
except ImportError as e:
logging.info("Import error: {}".format(e))
logging.info("Could not find config file, `cp config_example.yml config.yml`")
print("Import error: {}".format(e))
print("Could not find config file, `cp config_example.yml config.yml`")
sys.exit(1)
os.environ["PYTHONIOENCODING"] = "UTF-8"
......@@ -445,7 +440,10 @@ def stop_service(srv, sig=15):
if process is None:
print("\033[31m No process found\033[0m")
continue
process.wait(1)
try:
process.wait(1)
except:
pass
for i in range(STOP_TIMEOUT):
if i == STOP_TIMEOUT - 1:
print("\033[31m Error\033[0m")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment