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 @@ ...@@ -5,6 +5,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from common.fields.serializer import CustomMetaDictField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .. import const from .. import const
...@@ -16,54 +17,9 @@ __all__ = [ ...@@ -16,54 +17,9 @@ __all__ = [
] ]
class RemoteAppParamsDictField(serializers.DictField): class RemoteAppParamsDictField(CustomMetaDictField):
""" type_map_fields = const.REMOTE_APP_TYPE_MAP_FIELDS
RemoteApp field => params default_type = const.REMOTE_APP_TYPE_CHROME
"""
@staticmethod
def filter_attribute(attribute, instance):
"""
过滤掉params字段值中write_only特性的key-value值
For example, the chrome_password field is not returned when serializing
{
'chrome_target': 'http://www.jumpserver.org/',
'chrome_username': 'admin',
'chrome_password': 'admin',
}
"""
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]:
if field.get('write_only', False):
attribute.pop(field['name'], None)
return attribute
def get_attribute(self, instance):
"""
序列化时调用
"""
attribute = super().get_attribute(instance)
attribute = self.filter_attribute(attribute, instance)
return attribute
@staticmethod
def filter_value(dictionary, value):
"""
过滤掉不属于当前app_type所包含的key-value值
"""
app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME)
fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type]
fields_names = [field['name'] for field in fields]
no_need_keys = [k for k in value.keys() if k not in fields_names]
for k in no_need_keys:
value.pop(k)
return value
def get_value(self, dictionary):
"""
反序列化时调用
"""
value = super().get_value(dictionary)
value = self.filter_value(dictionary, value)
return value
class RemoteAppSerializer(BulkOrgResourceModelSerializer): class RemoteAppSerializer(BulkOrgResourceModelSerializer):
......
...@@ -19,14 +19,14 @@ ...@@ -19,14 +19,14 @@
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
{# chrome #} {# chrome #}
<div class="chrome-fields"> <div class="chrome-fields hidden">
{% bootstrap_field form.chrome_target layout="horizontal" %} {% bootstrap_field form.chrome_target layout="horizontal" %}
{% bootstrap_field form.chrome_username layout="horizontal" %} {% bootstrap_field form.chrome_username layout="horizontal" %}
{% bootstrap_field form.chrome_password layout="horizontal" %} {% bootstrap_field form.chrome_password layout="horizontal" %}
</div> </div>
{# mysql workbench #} {# 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_ip layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_name layout="horizontal" %} {% bootstrap_field form.mysql_workbench_name layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_username layout="horizontal" %} {% bootstrap_field form.mysql_workbench_username layout="horizontal" %}
...@@ -34,14 +34,14 @@ ...@@ -34,14 +34,14 @@
</div> </div>
{# vmware #} {# vmware #}
<div class="vmware_client-fields"> <div class="vmware_client-fields hidden">
{% bootstrap_field form.vmware_target layout="horizontal" %} {% bootstrap_field form.vmware_target layout="horizontal" %}
{% bootstrap_field form.vmware_username layout="horizontal" %} {% bootstrap_field form.vmware_username layout="horizontal" %}
{% bootstrap_field form.vmware_password layout="horizontal" %} {% bootstrap_field form.vmware_password layout="horizontal" %}
</div> </div>
{# custom #} {# custom #}
<div class="custom-fields"> <div class="custom-fields hidden">
{% bootstrap_field form.custom_cmdline layout="horizontal" %} {% bootstrap_field form.custom_cmdline layout="horizontal" %}
{% bootstrap_field form.custom_target layout="horizontal" %} {% bootstrap_field form.custom_target layout="horizontal" %}
{% bootstrap_field form.custom_username layout="horizontal" %} {% bootstrap_field form.custom_username layout="horizontal" %}
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -102,4 +97,4 @@ $(document).ready(function () { ...@@ -102,4 +97,4 @@ $(document).ready(function () {
objectDelete($this, name, the_url, redirect_url); objectDelete($this, name, the_url, redirect_url);
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,6 +2,7 @@ from .admin_user import * ...@@ -2,6 +2,7 @@ from .admin_user import *
from .asset import * from .asset import *
from .label import * from .label import *
from .system_user import * from .system_user import *
from .system_user_relation import *
from .node import * from .node import *
from .domain import * from .domain import *
from .cmd_filter import * from .cmd_filter import *
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# limitations under the License. # limitations under the License.
from django.db import transaction from django.db import transaction
from django.db.models import Count
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.response import Response from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
...@@ -44,6 +45,11 @@ class AdminUserViewSet(OrgBulkModelViewSet): ...@@ -44,6 +45,11 @@ class AdminUserViewSet(OrgBulkModelViewSet):
serializer_class = serializers.AdminUserSerializer serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(_assets_amount=Count('assets'))
return queryset
class AdminUserAuthApi(generics.UpdateAPIView): class AdminUserAuthApi(generics.UpdateAPIView):
model = AdminUser model = AdminUser
......
...@@ -114,7 +114,7 @@ class AssetUserExportViewSet(AssetUserViewSet): ...@@ -114,7 +114,7 @@ class AssetUserExportViewSet(AssetUserViewSet):
permission_classes = [IsOrgAdminOrAppUser] permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self): def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA: if settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions() return super().get_permissions()
...@@ -124,7 +124,7 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView): ...@@ -124,7 +124,7 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
permission_classes = [IsOrgAdminOrAppUser] permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self): def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA: if settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions() return super().get_permissions()
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
# limitations under the License. # limitations under the License.
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.conf import settings
from rest_framework.response import Response from rest_framework.response import Response
from django.db.models import Count
from common.serializers import CeleryTaskSerializer from common.serializers import CeleryTaskSerializer
from common.utils import get_logger from common.utils import get_logger
...@@ -50,6 +50,11 @@ class SystemUserViewSet(OrgBulkModelViewSet): ...@@ -50,6 +50,11 @@ class SystemUserViewSet(OrgBulkModelViewSet):
serializer_class = serializers.SystemUserSerializer serializer_class = serializers.SystemUserSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(_assets_amount=Count('assets'))
return queryset
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): 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): ...@@ -41,6 +41,7 @@ class AssetUser(OrgModelMixin):
ASSET_USER_CACHE_TIME = 3600 * 24 ASSET_USER_CACHE_TIME = 3600 * 24
_prefer = "system_user" _prefer = "system_user"
_assets_amount = None
@property @property
def private_key_obj(self): def private_key_obj(self):
...@@ -143,6 +144,8 @@ class AssetUser(OrgModelMixin): ...@@ -143,6 +144,8 @@ class AssetUser(OrgModelMixin):
@property @property
def assets_amount(self): 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) cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cached = cache.get(cache_key) cached = cache.get(cache_key)
if not cached: if not cached:
......
...@@ -4,8 +4,10 @@ from rest_framework import serializers ...@@ -4,8 +4,10 @@ from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen from common.utils import ssh_pubkey_gen
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from assets.models import Node
from ..models import SystemUser from ..models import SystemUser
from ..const import ( from ..const import (
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN, GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
...@@ -13,6 +15,12 @@ from ..const import ( ...@@ -13,6 +15,12 @@ from ..const import (
) )
from .base import AuthSerializer, AuthSerializerMixin from .base import AuthSerializer, AuthSerializerMixin
__all__ = [
'SystemUserSerializer', 'SystemUserAuthSerializer',
'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer',
'SystemUserNodeRelationSerializer',
]
class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
""" """
...@@ -143,4 +151,43 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer): ...@@ -143,4 +151,43 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'username') 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) { ...@@ -190,6 +190,16 @@ function setAssetModalOptions(options) {
assetModalOption = 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(){ $(document).ready(function(){
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load bootstrap3 %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
...@@ -247,4 +243,4 @@ $(document).ready(function () { ...@@ -247,4 +243,4 @@ $(document).ready(function () {
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load bootstrap3 %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
...@@ -87,4 +83,4 @@ $(document).ready(function () { ...@@ -87,4 +83,4 @@ $(document).ready(function () {
}) })
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
......
...@@ -32,13 +32,7 @@ ...@@ -32,13 +32,7 @@
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
$("#id_assets").parent().find(".select2-selection").on('click', function (e) { initAssetTreeModel("#id_assets");
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
}
})
}).on('click', '.field-tag', function() { }).on('click', '.field-tag', function() {
changeField(this); changeField(this);
}).on('click', '#change_all', function () { }).on('click', '#change_all', function () {
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<link href='{% static "css/plugins/sweetalert/sweetalert.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> <script src='{% static "js/plugins/sweetalert/sweetalert.min.js" %}'></script>
{% endblock %} {% endblock %}
......
...@@ -382,9 +382,6 @@ $(document).ready(function(){ ...@@ -382,9 +382,6 @@ $(document).ready(function(){
var data = { var data = {
'resources': id_list 'resources': id_list
}; };
function refreshPage() {
setTimeout( function () {window.location.reload();}, 300);
}
function reloadTable() { function reloadTable() {
asset_table.ajax.reload(); asset_table.ajax.reload();
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load bootstrap3 %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
...@@ -91,4 +87,4 @@ $(document).ready(function(){ ...@@ -91,4 +87,4 @@ $(document).ready(function(){
formSubmit(props); formSubmit(props);
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
......
...@@ -25,9 +25,7 @@ ...@@ -25,9 +25,7 @@
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2().off("select2:open"); $('.select2').select2().off("select2:open");
}).on('click', '.select2-selection__rendered', function (e) { initAssetTreeModel('#id_assets');
e.preventDefault();
$("#asset_list_modal").modal();
}) })
.on("submit", "form", function (evt) { .on("submit", "form", function (evt) {
evt.preventDefault(); evt.preventDefault();
...@@ -51,4 +49,4 @@ $(document).ready(function () { ...@@ -51,4 +49,4 @@ $(document).ready(function () {
formSubmit(props); formSubmit(props);
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -138,4 +133,4 @@ $(document).ready(function(){ ...@@ -138,4 +133,4 @@ $(document).ready(function(){
}) })
; ;
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load bootstrap3 %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
...@@ -125,4 +121,4 @@ $(document).ready(function(){ ...@@ -125,4 +121,4 @@ $(document).ready(function(){
protocolChange(); protocolChange();
}); });
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -29,9 +29,7 @@ $(document).ready(function () { ...@@ -29,9 +29,7 @@ $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
closeOnSelect: false closeOnSelect: false
}) })
}).on('click', '.select2-selection__rendered', function (e) { initAssetTreeModel("#id_assets");
e.preventDefault();
$("#asset_list_modal").modal();
}) })
.on("submit", "form", function (evt) { .on("submit", "form", function (evt) {
evt.preventDefault(); evt.preventDefault();
...@@ -55,4 +53,4 @@ $(document).ready(function () { ...@@ -55,4 +53,4 @@ $(document).ready(function () {
formSubmit(props); formSubmit(props);
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -4,9 +4,13 @@ ...@@ -4,9 +4,13 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet"> <style>
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script> .table.node_edit {
margin-bottom: 0;
}
</style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -98,15 +102,8 @@ ...@@ -98,15 +102,8 @@
</td> </td>
</tr> </tr>
</form> </form>
<table class="table" id="node_list_table">
{% for node in system_user.nodes.all|sort %} </table>
<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 %}
</tbody> </tbody>
</table> </table>
</div> </div>
...@@ -120,91 +117,115 @@ ...@@ -120,91 +117,115 @@
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var assetsRelationUrl = "{% url 'api-assets:system-users-assets-relation-list' %}";
var nodesRelationUrl = "{% url 'api-assets:system-users-nodes-relation-list' %}";
function updateSystemUserNode(nodes) { function getRelationUrl(type) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}"; var theUrl = "";
var body = { switch (type) {
nodes: Object.assign([], nodes) case "asset":
}; theUrl = assetsRelationUrl;
var success = function(data) { break;
// remove all the selected groups from select > option and rendered ul element; case "node":
$('.select2-selection__rendered').empty(); theUrl = nodesRelationUrl;
$('#node_selected').val(''); break;
$.map(jumpserver.nodes_selected, function(node_name, index) { }
$('#opt_' + index).remove(); return theUrl;
// change tr html of user groups. }
$('.node_edit tbody').append(
'<tr>' + function addObjects(objectsId, type) {
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' + if (!objectsId || objectsId.length === 0) {
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' + return
'</tr>' }
) var theUrl = getRelationUrl(type);
}); var body = [];
// clear jumpserver.nodes_selected objectsId.forEach(function (v) {
jumpserver.nodes_selected = {}; var data = {systemuser: "{{ object.id }}"};
}; data[type] = v;
body.push(data)
});
requestApi({ requestApi({
url: the_url, url: theUrl,
body: JSON.stringify(body), 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 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 () { $(document).ready(function () {
$('.select2').select2() $('.select2').select2();
nodesSelect2Init(".nodes-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];
});
assetUserListUrl = setUrlParam(assetUserListUrl, "system_user_id", "{{ system_user.id }}"); assetUserListUrl = setUrlParam(assetUserListUrl, "system_user_id", "{{ system_user.id }}");
needPush = true; needPush = true;
initAssetUserTable(); initAssetUserTable();
initNodeTable();
}) })
.on('click', '.btn-remove-from-node', function() { .on('click', '.btn-remove-from-node', function() {
var $this = $(this); var $this = $(this);
var $tr = $this.closest('tr'); var nodeId = $(this).data('uid');
var $badge = $tr.find('.bdg_node'); var success = function() {
var gid = $badge.data('gid'); var $tr = $this.closest('tr');
var node_name = $badge.html() || $badge.text(); $tr.remove();
$('#groups_selected').append( };
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>' removeObject(nodeId, "node", success)
);
$tr.remove();
var nodes = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserNode(nodes);
}) })
.on('click', '#btn-add-to-node', function() { .on('click', '#btn-add-to-node', function() {
if (Object.keys(jumpserver.nodes_selected).length === 0) { var nodes = $("#node_selected").val();
return false; addObjects(nodes, "node");
}
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
updateSystemUserNode(nodes);
}) })
.on('click', '.btn-push', function () { .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) { var error = function (data) {
alert(data) alert(data)
}; };
var success = function (data) { var success = function (data) {
var task_id = data.task; var taskId = data.task;
showCeleryTaskLog(task_id); showCeleryTaskLog(taskId);
}; };
requestApi({ requestApi({
url: the_url, url: theUrl,
error: error, error: error,
method: 'GET', method: 'GET',
success: success success: success
...@@ -213,39 +234,37 @@ $(document).ready(function () { ...@@ -213,39 +234,37 @@ $(document).ready(function () {
.on('click', '.btn-push-auth', function () { .on('click', '.btn-push-auth', function () {
var $this = $(this); var $this = $(this);
var asset_id = $this.data('asset'); var asset_id = $this.data('asset');
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}"; var theUrl = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id); theUrl = theUrl.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) { var success = function (data) {
var task_id = data.task; var taskId = data.task;
showCeleryTaskLog(task_id); showCeleryTaskLog(taskId);
}; };
var error = function (data) { var error = function (data) {
alert(data) alert(data)
}; };
requestApi({ requestApi({
url: the_url, url: theUrl,
method: 'GET', method: 'GET',
success: success, success: success,
error: error error: error
}) })
}) })
.on('click', '.btn-test-connective', function () { .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) { var error = function (data) {
alert(data) alert(data)
}; };
var success = function (data) { var success = function (data) {
var task_id = data.task; var taskId = data.task;
showCeleryTaskLog(task_id); showCeleryTaskLog(taskId);
}; };
requestApi({ requestApi({
url: the_url, url: theUrl,
error: error, error: error,
method: 'GET', method: 'GET',
success: success success: success
}); });
}) })
</script> </script>
{% endblock %} {% endblock %}
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
......
...@@ -23,6 +23,8 @@ router.register(r'asset-users', api.AssetUserViewSet, 'asset-user') ...@@ -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'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user') router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') 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 = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
......
...@@ -11,4 +11,4 @@ class FTPLogViewSet(OrgModelViewSet): ...@@ -11,4 +11,4 @@ class FTPLogViewSet(OrgModelViewSet):
model = FTPLog model = FTPLog
serializer_class = FTPLogSerializer serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) 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__ = [ ...@@ -16,7 +16,7 @@ __all__ = [
class FTPLog(OrgModelMixin): class FTPLog(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User')) 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")) asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
system_user = models.CharField(max_length=128, verbose_name=_("System user")) system_user = models.CharField(max_length=128, verbose_name=_("System user"))
operate = models.CharField(max_length=16, verbose_name=_("Operate")) operate = models.CharField(max_length=16, verbose_name=_("Operate"))
...@@ -39,7 +39,7 @@ class OperateLog(OrgModelMixin): ...@@ -39,7 +39,7 @@ class OperateLog(OrgModelMixin):
action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action")) action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action"))
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type")) resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
resource = models.CharField(max_length=128, verbose_name=_("Resource")) 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) datetime = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):
...@@ -50,7 +50,7 @@ class PasswordChangeLog(models.Model): ...@@ -50,7 +50,7 @@ class PasswordChangeLog(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User')) user = models.CharField(max_length=128, verbose_name=_('User'))
change_by = models.CharField(max_length=128, verbose_name=_("Change by")) 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) datetime = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):
......
...@@ -13,8 +13,8 @@ from common.utils import get_request_ip, get_logger, get_syslogger ...@@ -13,8 +13,8 @@ from common.utils import get_request_ip, get_logger, get_syslogger
from users.models import User from users.models import User
from authentication.signals import post_auth_failed, post_auth_success from authentication.signals import post_auth_failed, post_auth_success
from terminal.models import Session, Command from terminal.models import Session, Command
from terminal.backends.command.serializers import SessionCommandSerializer from common.utils.encode import model_to_json
from . import models, serializers from . import models
from .tasks import write_login_log_async from .tasks import write_login_log_async
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -51,7 +51,10 @@ def create_operate_log(action, sender, resource): ...@@ -51,7 +51,10 @@ def create_operate_log(action, sender, resource):
@receiver(post_save, dispatch_uid="my_unique_identifier") @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: if created:
action = models.OperateLog.ACTION_CREATE action = models.OperateLog.ACTION_CREATE
else: else:
...@@ -79,27 +82,20 @@ def on_user_change_password(sender, instance=None, **kwargs): ...@@ -79,27 +82,20 @@ def on_user_change_password(sender, instance=None, **kwargs):
def on_audits_log_create(sender, instance=None, **kwargs): def on_audits_log_create(sender, instance=None, **kwargs):
if sender == models.UserLoginLog: if sender == models.UserLoginLog:
category = "login_log" category = "login_log"
serializer = serializers.LoginLogSerializer
elif sender == models.FTPLog: elif sender == models.FTPLog:
serializer = serializers.FTPLogSerializer
category = "ftp_log" category = "ftp_log"
elif sender == models.OperateLog: elif sender == models.OperateLog:
category = "operation_log" category = "operation_log"
serializer = serializers.OperateLogSerializer
elif sender == models.PasswordChangeLog: elif sender == models.PasswordChangeLog:
category = "password_change_log" category = "password_change_log"
serializer = serializers.PasswordChangeLogSerializer
elif sender == Session: elif sender == Session:
category = "host_session_log" category = "host_session_log"
serializer = serializers.SessionAuditSerializer
elif sender == Command: elif sender == Command:
category = "session_command_log" category = "session_command_log"
serializer = SessionCommandSerializer
else: else:
return return
s = serializer(instance=instance) data = model_to_json(instance)
data = json_render.render(s.data).decode(errors='ignore')
msg = "{} - {}".format(category, data) msg = "{} - {}".format(category, data)
sys_logger.info(msg) sys_logger.info(msg)
......
...@@ -6,7 +6,7 @@ from django.conf import settings ...@@ -6,7 +6,7 @@ from django.conf import settings
from celery import shared_task from celery import shared_task
from ops.celery.decorator import register_as_period_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 from .utils import write_login_log
...@@ -22,6 +22,18 @@ def clean_login_log_period(): ...@@ -22,6 +22,18 @@ def clean_login_log_period():
UserLoginLog.objects.filter(datetime__lt=expired_day).delete() 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 @shared_task
def write_login_log_async(*args, **kwargs): def write_login_log_async(*args, **kwargs):
write_login_log(*args, **kwargs) write_login_log(*args, **kwargs)
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
{% load common_tags %} {% load common_tags %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/datepicker/datepicker3.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> <style>
#search_btn { #search_btn {
margin-bottom: 0; margin-bottom: 0;
...@@ -14,6 +12,9 @@ ...@@ -14,6 +12,9 @@
.form-control { .form-control {
height: 30px; height: 30px;
} }
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single {
height: 30px !important;
}
</style> </style>
{% endblock %} {% endblock %}
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
{% load common_tags %} {% load common_tags %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/datepicker/datepicker3.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> <style>
#search_btn { #search_btn {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
{% load common_tags %} {% load common_tags %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/datepicker/datepicker3.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> <style>
#search_btn { #search_btn {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -109,7 +109,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): ...@@ -109,7 +109,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
class AccessTokenAuthentication(authentication.BaseAuthentication): class AccessTokenAuthentication(authentication.BaseAuthentication):
keyword = 'Bearer' keyword = 'Bearer'
expiration = settings.TOKEN_EXPIRATION or 3600 # expiration = settings.TOKEN_EXPIRATION or 3600
model = get_user_model() model = get_user_model()
def authenticate(self, request): def authenticate(self, request):
......
# coding:utf-8 # coding:utf-8
# #
import warnings
import ldap import ldap
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist 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 django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion
from users.utils import construct_user_email from users.utils import construct_user_email
...@@ -17,7 +18,6 @@ class LDAPAuthorizationBackend(LDAPBackend): ...@@ -17,7 +18,6 @@ class LDAPAuthorizationBackend(LDAPBackend):
""" """
Override this class to override _LDAPUser to LDAPUser Override this class to override _LDAPUser to LDAPUser
""" """
@staticmethod @staticmethod
def user_can_authenticate(user): def user_can_authenticate(user):
""" """
...@@ -27,18 +27,26 @@ class LDAPAuthorizationBackend(LDAPBackend): ...@@ -27,18 +27,26 @@ class LDAPAuthorizationBackend(LDAPBackend):
is_valid = getattr(user, 'is_valid', None) is_valid = getattr(user, 'is_valid', None)
return is_valid or is_valid is 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') logger.info('Authentication LDAP backend')
if not username: if not username:
logger.info('Authenticate failed: username is None') logger.info('Authenticate failed: username is None')
return None return False
if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS: if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS:
user_model = self.get_user_model() user_model = self.get_user_model()
exist = user_model.objects.filter(username=username).exists() exist = user_model.objects.filter(username=username).exists()
if not exist: if not exist:
msg = 'Authentication failed: user ({}) is not in the user list' msg = 'Authentication failed: user ({}) is not in the user list'
logger.info(msg.format(username)) 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) ldap_user = LDAPUser(self, username=username.strip(), request=request)
user = self.authenticate_ldap_user(ldap_user, password) user = self.authenticate_ldap_user(ldap_user, password)
logger.info('Authenticate user: {}'.format(user)) logger.info('Authenticate user: {}'.format(user))
......
...@@ -25,7 +25,8 @@ __all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView'] ...@@ -25,7 +25,8 @@ __all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
class OpenIDLoginView(RedirectView): class OpenIDLoginView(RedirectView):
def get_redirect_url(self, *args, **kwargs): 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( nonce = Nonce(
redirect_uri=redirect_uri, redirect_uri=redirect_uri,
next_path=self.request.GET.get('next') next_path=self.request.GET.get('next')
......
...@@ -141,7 +141,7 @@ class AuthMixin: ...@@ -141,7 +141,7 @@ class AuthMixin:
) )
def check_user_login_confirm_if_need(self, user): def check_user_login_confirm_if_need(self, user):
if not settings.CONFIG.LOGIN_CONFIRM_ENABLE: if not settings.LOGIN_CONFIRM_ENABLE:
return return
confirm_setting = user.get_login_confirm_setting() confirm_setting = user.get_login_confirm_setting()
if self.request.session.get('auth_confirm') or not confirm_setting: if self.request.session.get('auth_confirm') or not confirm_setting:
......
...@@ -60,7 +60,8 @@ class UserLoginView(mixins.AuthMixin, FormView): ...@@ -60,7 +60,8 @@ class UserLoginView(mixins.AuthMixin, FormView):
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1 # show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
if settings.AUTH_OPENID and not self.request.GET.get('admin', 0): if settings.AUTH_OPENID and not self.request.GET.get('admin', 0):
query_string = request.GET.urlencode() 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) return redirect(login_url)
request.session.set_test_cookie() request.session.set_test_cookie()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
......
# -*- coding: utf-8 -*-
#
...@@ -7,7 +7,7 @@ from rest_framework.serializers import ValidationError ...@@ -7,7 +7,7 @@ from rest_framework.serializers import ValidationError
from django.core.cache import cache from django.core.cache import cache
import logging import logging
from . import const from common import const
__all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"] __all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"]
......
...@@ -9,7 +9,7 @@ import unicodecsv ...@@ -9,7 +9,7 @@ import unicodecsv
from rest_framework.parsers import BaseParser from rest_framework.parsers import BaseParser
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from ..utils import get_logger from common.utils import get_logger
logger = get_logger(__file__) logger = get_logger(__file__)
......
...@@ -9,7 +9,7 @@ from six import BytesIO ...@@ -9,7 +9,7 @@ from six import BytesIO
from rest_framework.renderers import BaseRenderer from rest_framework.renderers import BaseRenderer
from rest_framework.utils import encoders, json from rest_framework.utils import encoders, json
from ..utils import get_logger from common.utils import get_logger
logger = get_logger(__file__) 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 ...@@ -5,7 +5,10 @@ from rest_framework import serializers
from django.utils import six from django.utils import six
__all__ = ['StringIDField', 'StringManyToManyField', 'ChoiceDisplayField'] __all__ = [
'StringIDField', 'StringManyToManyField', 'ChoiceDisplayField',
'CustomMetaDictField'
]
class StringIDField(serializers.Field): class StringIDField(serializers.Field):
...@@ -39,3 +42,63 @@ class DictField(serializers.DictField): ...@@ -39,3 +42,63 @@ class DictField(serializers.DictField):
if not value or not isinstance(value, dict): if not value or not isinstance(value, dict):
value = {} value = {}
return super().to_representation(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 @@ ...@@ -3,11 +3,11 @@
from django.http import JsonResponse from django.http import JsonResponse
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from ..filters import IDSpmFilter, CustomFilter from common.drf.filters import IDSpmFilter, CustomFilter
__all__ = [ __all__ = [
"JSONResponseMixin", "CommonApiMixin", "JSONResponseMixin", "CommonApiMixin",
"IDSpmFilterMixin", "CommonApiMixin", "IDSpmFilterMixin",
] ]
......
...@@ -7,10 +7,13 @@ from django.conf import settings ...@@ -7,10 +7,13 @@ from django.conf import settings
from django.dispatch import receiver from django.dispatch import receiver
from django.core.signals import request_finished from django.core.signals import request_finished
from django.db import connection from django.db import connection
from django.conf import LazySettings
from django.db.utils import ProgrammingError, OperationalError
from common.utils import get_logger from common.utils import get_logger
from .local import thread_local from .local import thread_local
from .signals import django_ready
pattern = re.compile(r'FROM `(\w+)`') pattern = re.compile(r'FROM `(\w+)`')
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -62,7 +65,15 @@ if settings.DEBUG and DEBUG_DB: ...@@ -62,7 +65,15 @@ if settings.DEBUG and DEBUG_DB:
request_finished.connect(on_request_finished_logging_db_query) 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.core.mail import send_mail
from django.conf import settings from django.conf import settings
from celery import shared_task from celery import shared_task
from .utils import get_logger from .utils import get_logger
......
...@@ -9,6 +9,7 @@ import uuid ...@@ -9,6 +9,7 @@ import uuid
from functools import wraps from functools import wraps
import time import time
import ipaddress import ipaddress
import psutil
UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}') UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}')
...@@ -234,3 +235,10 @@ class lazyproperty: ...@@ -234,3 +235,10 @@ class lazyproperty:
value = self.func(instance) value = self.func(instance)
setattr(instance, self.func.__name__, value) setattr(instance, self.func.__name__, value)
return 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(): ...@@ -35,20 +35,3 @@ def date_expired_default():
years = 70 years = 70
return timezone.now() + timezone.timedelta(days=365*years) 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 -*- # -*- coding: utf-8 -*-
# #
import re import re
import json
from six import string_types from six import string_types
import base64 import base64
import os import os
import time import time
import hashlib import hashlib
from io import StringIO from io import StringIO
from itertools import chain
import paramiko import paramiko
import sshpubkeys import sshpubkeys
...@@ -15,6 +17,8 @@ from itsdangerous import ( ...@@ -15,6 +17,8 @@ from itsdangerous import (
BadSignature, SignatureExpired BadSignature, SignatureExpired
) )
from django.conf import settings 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 from .http import http_date
...@@ -186,3 +190,35 @@ def get_signer(): ...@@ -186,3 +190,35 @@ def get_signer():
def ensure_last_char_is_ascii(data): def ensure_last_char_is_ascii(data):
remain = '' 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 -*- # -*- coding: utf-8 -*-
# #
from .ipdb import * from .utils import *
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import os import os
from django.utils.translation import ugettext as _
import ipdb import ipdb
__all__ = ['get_ip_city']
ipip_db = None ipip_db = None
def get_ip_city(ip): def get_ip_city(ip):
global ipip_db global ipip_db
if not ip or not isinstance(ip, str):
return _("Invalid ip")
if ':' in ip:
return 'IPv6'
if ipip_db is None: if ipip_db is None:
ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb') ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb')
ipip_db = ipdb.City(ipip_db_path) ipip_db = ipdb.City(ipip_db_path)
info = list(set(ipip_db.find(ip, 'CN'))) info = list(set(ipip_db.find(ip, 'CN')))
if '' in info: if '' in info:
info.remove('') info.remove('')
return ' '.join(info) return ' '.join(info)
\ No newline at end of file
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
"""
配置分类:
1. Django使用的配置文件,写到settings中
2. 程序需要, 用户不需要更改的写到settings中
3. 程序需要, 用户需要更改的写到本config中
"""
import os import os
import sys import sys
import types import types
...@@ -8,6 +14,7 @@ import errno ...@@ -8,6 +14,7 @@ import errno
import json import json
import yaml import yaml
from importlib import import_module from importlib import import_module
from django.urls import reverse_lazy
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(BASE_DIR) PROJECT_DIR = os.path.dirname(BASE_DIR)
...@@ -29,6 +36,10 @@ def import_string(dotted_path): ...@@ -29,6 +36,10 @@ def import_string(dotted_path):
) from err ) from err
class DoesNotExist(Exception):
pass
class Config(dict): class Config(dict):
"""Works exactly like a dict but provides ways to fill it from files """Works exactly like a dict but provides ways to fill it from files
or special dictionaries. There are two common patterns to populate the or special dictionaries. There are two common patterns to populate the
...@@ -72,34 +83,233 @@ class Config(dict): ...@@ -72,34 +83,233 @@ class Config(dict):
the application's :attr:`~flask.Flask.root_path`. the application's :attr:`~flask.Flask.root_path`.
:param defaults: an optional dictionary of default values :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): def convert_type(self, k, v):
self.defaults = defaults or {} default_value = self.defaults.get(k)
self.root_path = root_path if default_value is None:
super().__init__({}) 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): try:
"""Loads a configuration from an environment variable pointing to if tp in [list, dict]:
a configuration file. This is basically just a shortcut with nicer v = json.loads(v)
error messages for this line of code:: 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 def get_from_config(self, item):
:param silent: set to ``True`` if you want silent failure for missing try:
files. value = super().__getitem__(item)
:return: bool. ``True`` if able to load config, ``False`` otherwise. except KeyError:
""" value = None
rv = os.environ.get(variable_name) return value
if not rv:
if silent: def get_from_env(self, item):
return False value = os.environ.get(item, None)
raise RuntimeError('The environment variable %r is not set ' if value is not None:
'and as such configuration could not be ' value = self.convert_type(item, value)
'loaded. Set this variable and make it ' return value
'point to a configuration file' %
variable_name) def get(self, item):
return self.from_pyfile(rv, silent=silent) # 再从配置文件中获取
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): def from_pyfile(self, filename, silent=False):
"""Updates the values in the config from a Python file. This function """Updates the values in the config from a Python file. This function
...@@ -162,7 +372,7 @@ class Config(dict): ...@@ -162,7 +372,7 @@ class Config(dict):
obj = import_string(obj) obj = import_string(obj)
for key in dir(obj): for key in dir(obj):
if key.isupper(): if key.isupper():
self[key] = getattr(obj, key) self.config[key] = getattr(obj, key)
def from_json(self, filename, silent=False): def from_json(self, filename, silent=False):
"""Updates the values in the config from a JSON file. This function """Updates the values in the config from a JSON file. This function
...@@ -224,218 +434,50 @@ class Config(dict): ...@@ -224,218 +434,50 @@ class Config(dict):
for mapping in mappings: for mapping in mappings:
for (key, value) in mapping: for (key, value) in mapping:
if key.isupper(): if key.isupper():
self[key] = value self.config[key] = value
return True return True
def get_namespace(self, namespace, lowercase=True, trim_namespace=True): def load_from_object(self):
"""Returns a dictionary containing a subset of configuration options sys.path.insert(0, PROJECT_DIR)
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
try: try:
if tp in [list, dict]: from config import config as c
v = json.loads(v) self.from_object(c)
else: return self.config
v = tp(v) except ImportError:
except Exception:
pass pass
return v return None
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
def __getitem__(self, item): def load_from_yml(self):
# 先从设置的来 for i in ['config.yml', 'config.yaml']:
try: if not os.path.isfile(os.path.join(self.root_path, i)):
value = super().__getitem__(item) continue
except KeyError: loaded = self.from_yaml(i)
value = None if loaded:
if value is not None: return self.config
return value return False
# 其次从环境变量来
value = os.environ.get(item, None) @classmethod
if value is not None: def load_user_config(cls, root_path=None, config_class=None):
return self.convert_type(item, value) config_class = config_class or Config
return self.defaults.get(item) cls.config_class = config_class
if not root_path:
def __getattr__(self, item): root_path = PROJECT_DIR
return self.__getitem__(item) manager = cls(root_path=root_path)
config = manager.load_from_object()
if config:
defaults = { return config
'SECRET_KEY': '', config = manager.load_from_yml()
'BOOTSTRAP_TOKEN': '', if config:
'DEBUG': True, return config
'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:
msg = """ msg = """
Error: No config file found. Error: No config file found.
You can run `cp config_example.yml config.yml`, and edit it. You can run `cp config_example.yml config.yml`, and edit it.
""" """
raise ImportError(msg) raise ImportError(msg)
return config
@classmethod
def get_dynamic_config(cls, config):
return DynamicConfig(config)
# -*- coding: utf-8 -*- # -*- 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' VERSION = '1.5.5'
CONFIG = ConfigManager.load_user_config()
DYNAMIC = ConfigManager.get_dynamic_config(CONFIG)
...@@ -19,8 +19,8 @@ def jumpserver_processor(request): ...@@ -19,8 +19,8 @@ def jumpserver_processor(request):
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION, 'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL, 'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME, 'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME,
'SECURITY_VIEW_AUTH_NEED_MFA': settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA, 'SECURITY_VIEW_AUTH_NEED_MFA': settings.SECURITY_VIEW_AUTH_NEED_MFA,
'LOGIN_CONFIRM_ENABLE': settings.CONFIG.LOGIN_CONFIRM_ENABLE, 'LOGIN_CONFIRM_ENABLE': settings.LOGIN_CONFIRM_ENABLE,
} }
return context 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 os
import sys
import ldap
from django.urls import reverse_lazy from django.urls import reverse_lazy
from . import const from .. import const
from .conf import load_user_config from ..const import CONFIG, DYNAMIC
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # 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 VERSION = const.VERSION
BASE_DIR = const.BASE_DIR
if not os.path.isdir(LOG_DIR): PROJECT_DIR = const.PROJECT_DIR
os.makedirs(LOG_DIR)
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
...@@ -47,7 +23,7 @@ BOOTSTRAP_TOKEN = CONFIG.BOOTSTRAP_TOKEN ...@@ -47,7 +23,7 @@ BOOTSTRAP_TOKEN = CONFIG.BOOTSTRAP_TOKEN
DEBUG = CONFIG.DEBUG DEBUG = CONFIG.DEBUG
# Absolute url for some case, for example email link # Absolute url for some case, for example email link
SITE_URL = CONFIG.SITE_URL SITE_URL = DYNAMIC.SITE_URL
# LOG LEVEL # LOG LEVEL
LOG_LEVEL = CONFIG.LOG_LEVEL LOG_LEVEL = CONFIG.LOG_LEVEL
...@@ -89,17 +65,6 @@ INSTALLED_APPS = [ ...@@ -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 = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
...@@ -122,7 +87,7 @@ ROOT_URLCONF = 'jumpserver.urls' ...@@ -122,7 +87,7 @@ ROOT_URLCONF = 'jumpserver.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', '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, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
...@@ -136,14 +101,12 @@ TEMPLATES = [ ...@@ -136,14 +101,12 @@ TEMPLATES = [
'django.template.context_processors.media', 'django.template.context_processors.media',
'jumpserver.context_processor.jumpserver_processor', 'jumpserver.context_processor.jumpserver_processor',
'orgs.context_processor.org_processor', 'orgs.context_processor.org_processor',
*XPACK_CONTEXT_PROCESSOR,
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'jumpserver.wsgi.application' WSGI_APPLICATION = 'jumpserver.wsgi.application'
ASGI_APPLICATION = 'jumpserver.routing.application'
LOGIN_REDIRECT_URL = reverse_lazy('index') LOGIN_REDIRECT_URL = reverse_lazy('index')
LOGIN_URL = reverse_lazy('authentication:login') LOGIN_URL = reverse_lazy('authentication:login')
...@@ -205,112 +168,6 @@ AUTH_PASSWORD_VALIDATORS = [ ...@@ -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 # Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/ # https://docs.djangoproject.com/en/1.10/topics/i18n/
# LANGUAGE_CODE = 'en' # LANGUAGE_CODE = 'en'
...@@ -353,66 +210,17 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/' ...@@ -353,66 +210,17 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/'
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ] FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ]
# Email config # Email config
EMAIL_HOST = 'smtp.jumpserver.org' EMAIL_HOST = DYNAMIC.EMAIL_HOST
EMAIL_PORT = 25 EMAIL_PORT = DYNAMIC.EMAIL_PORT
EMAIL_HOST_USER = 'noreply@jumpserver.org' EMAIL_HOST_USER = DYNAMIC.EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = '' EMAIL_HOST_PASSWORD = DYNAMIC.EMAIL_HOST_PASSWORD
EMAIL_FROM = '' EMAIL_FROM = DYNAMIC.EMAIL_FROM
EMAIL_RECIPIENT = '' EMAIL_RECIPIENT = DYNAMIC.EMAIL_RECIPIENT
EMAIL_USE_SSL = False EMAIL_USE_SSL = DYNAMIC.EMAIL_USE_SSL
EMAIL_USE_TLS = False EMAIL_USE_TLS = DYNAMIC.EMAIL_USE_TLS
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
}
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend', AUTHENTICATION_BACKENDS = DYNAMIC.AUTHENTICATION_BACKENDS
'authentication.backends.pubkey.PublicKeyAuthBackend',
]
# Custom User Auth model # Custom User Auth model
AUTH_USER_MODEL = 'users.User' AUTH_USER_MODEL = 'users.User'
...@@ -421,106 +229,6 @@ AUTH_USER_MODEL = 'users.User' ...@@ -421,106 +229,6 @@ AUTH_USER_MODEL = 'users.User'
FILE_UPLOAD_PERMISSIONS = 0o644 FILE_UPLOAD_PERMISSIONS = 0o644
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755 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 # Cache use redis
CACHES = { CACHES = {
'default': { 'default': {
...@@ -534,127 +242,6 @@ CACHES = { ...@@ -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 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 ...@@ -7,10 +7,7 @@ from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from django.views.i18n import JavaScriptCatalog from django.views.i18n import JavaScriptCatalog
# from .views import IndexView, LunaView, I18NView, HealthCheckView, redirect_format_api
from . import views from . import views
from .celery_flower import celery_flower_view
from .swagger import get_swagger_view
api_v1 = [ api_v1 = [
path('users/', include('users.urls.api_urls', namespace='api-users')), path('users/', include('users.urls.api_urls', namespace='api-users')),
...@@ -44,7 +41,7 @@ app_view_patterns = [ ...@@ -44,7 +41,7 @@ app_view_patterns = [
path('auth/', include('authentication.urls.view_urls'), name='auth'), path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('applications/', include('applications.urls.views_urls', namespace='applications')), path('applications/', include('applications.urls.views_urls', namespace='applications')),
path('tickets/', include('tickets.urls.views_urls', namespace='tickets')), 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) \ ...@@ -82,19 +79,19 @@ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += js_i18n_patterns urlpatterns += js_i18n_patterns
handler404 = 'jumpserver.error_views.handler404' handler404 = 'jumpserver.views.handler404'
handler500 = 'jumpserver.error_views.handler500' handler500 = 'jumpserver.views.handler500'
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [ urlpatterns += [
re_path('^swagger(?P<format>\.json|\.yaml)$', re_path('^swagger(?P<format>\.json|\.yaml)$',
get_swagger_view().without_ui(cache_timeout=1), name='schema-json'), views.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('docs/', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"),
path('redoc/', get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'), path('redoc/', views.get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'),
re_path('^v2/swagger(?P<format>\.json|\.yaml)$', re_path('^v2/swagger(?P<format>\.json|\.yaml)$',
get_swagger_view().without_ui(cache_timeout=1), name='schema-json'), views.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('docs/v2/', views.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'), 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 ...@@ -9,6 +9,8 @@ from proxy.views import proxy_view
flower_url = settings.FLOWER_URL flower_url = settings.FLOWER_URL
__all__ = ['celery_flower_view']
@csrf_exempt @csrf_exempt
def celery_flower_view(request, path): def celery_flower_view(request, path):
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import JsonResponse from django.http import JsonResponse
__all__ = ['handler404', 'handler500']
def handler404(request, *args, **argv): def handler404(request, *args, **argv):
if request.content_type.find('application/json') > -1: if request.content_type.find('application/json') > -1:
......
import datetime import datetime
import re
import time
from django.http import HttpResponseRedirect, JsonResponse from django.views.generic import TemplateView
from django.conf import settings
from django.views.generic import TemplateView, View
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Count from django.db.models import Count
from django.shortcuts import redirect 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 from users.models import User
...@@ -19,7 +12,8 @@ from assets.models import Asset ...@@ -19,7 +12,8 @@ from assets.models import Asset
from terminal.models import Session from terminal.models import Session
from orgs.utils import current_org from orgs.utils import current_org
from common.permissions import PermissionsMixin, IsValidUser from common.permissions import PermissionsMixin, IsValidUser
from common.http import HttpResponseTemporaryRedirect
__all__ = ['IndexView']
class IndexView(PermissionsMixin, TemplateView): class IndexView(PermissionsMixin, TemplateView):
...@@ -186,58 +180,3 @@ class IndexView(PermissionsMixin, TemplateView): ...@@ -186,58 +180,3 @@ class IndexView(PermissionsMixin, TemplateView):
kwargs.update(context) kwargs.update(context)
return super(IndexView, self).get_context_data(**kwargs) 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): ...@@ -50,7 +50,7 @@ class CustomSwaggerAutoSchema(SwaggerAutoSchema):
def get_swagger_view(version='v1'): 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 from django.urls import path, include
api_v1_patterns = [ api_v1_patterns = [
path('api/v1/', include(api_v1)) 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 ...@@ -5,17 +5,20 @@ import os
import re import re
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import viewsets
from celery.result import AsyncResult from celery.result import AsyncResult
from rest_framework import generics 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 common.api import LogTailApi
from ..models import CeleryTask from ..models import CeleryTask
from ..serializers import CeleryResultSerializer from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
from ..celery.utils import get_celery_task_log_path 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): class CeleryTaskLogApi(LogTailApi):
...@@ -62,3 +65,14 @@ class CeleryResultApi(generics.RetrieveAPIView): ...@@ -62,3 +65,14 @@ class CeleryResultApi(generics.RetrieveAPIView):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
return AsyncResult(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 = [] ...@@ -9,51 +9,37 @@ _after_app_shutdown_clean_periodic_tasks = []
def add_register_period_task(task): def add_register_period_task(task):
_need_registered_period_tasks.append(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(): def get_register_period_tasks():
# key = "__REGISTER_PERIODIC_TASKS"
# return cache.get(key, [])
return _need_registered_period_tasks return _need_registered_period_tasks
def add_after_app_shutdown_clean_task(name): 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) _after_app_shutdown_clean_periodic_tasks.append(name)
def get_after_app_shutdown_clean_tasks(): def get_after_app_shutdown_clean_tasks():
# key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS"
# return cache.get(key, [])
return _after_app_shutdown_clean_periodic_tasks return _after_app_shutdown_clean_periodic_tasks
def add_after_app_ready_task(name): 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) _after_app_ready_start_tasks.append(name)
def get_after_app_ready_tasks(): def get_after_app_ready_tasks():
# key = "__AFTER_APP_READY_RUN_TASKS"
# return cache.get(key, [])
return _after_app_ready_start_tasks 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 Warning: Task must be have not any args and kwargs
:param crontab: "* * * * *" :param crontab: "* * * * *"
:param interval: 60*60*60 :param interval: 60*60*60
:param description: "
:param name: ""
:return: :return:
""" """
if crontab is None and interval is None: if crontab is None and interval is None:
...@@ -65,14 +51,16 @@ def register_as_period_task(crontab=None, interval=None): ...@@ -65,14 +51,16 @@ def register_as_period_task(crontab=None, interval=None):
# Because when this decorator run, the task was not created, # Because when this decorator run, the task was not created,
# So we can't use func.name # 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({ add_register_period_task({
name: { _name: {
'task': name, 'task': task,
'interval': interval, 'interval': interval,
'crontab': crontab, 'crontab': crontab,
'args': (), 'args': (),
'enabled': True, 'enabled': True,
'description': description
} }
}) })
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import logging import logging
from django.dispatch import receiver
from django.core.cache import cache from django.core.cache import cache
from celery import subtask from celery import subtask
...@@ -11,6 +12,7 @@ from kombu.utils.encoding import safe_str ...@@ -11,6 +12,7 @@ from kombu.utils.encoding import safe_str
from django_celery_beat.models import PeriodicTask from django_celery_beat.models import PeriodicTask
from common.utils import get_logger 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 .decorator import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks
from .logger import CeleryTaskFileHandler from .logger import CeleryTaskFileHandler
......
...@@ -10,6 +10,10 @@ from django_celery_beat.models import ( ...@@ -10,6 +10,10 @@ from django_celery_beat.models import (
PeriodicTask, IntervalSchedule, CrontabSchedule, PeriodicTasks PeriodicTask, IntervalSchedule, CrontabSchedule, PeriodicTasks
) )
from common.utils import get_logger
logger = get_logger(__name__)
def create_or_update_celery_periodic_tasks(tasks): def create_or_update_celery_periodic_tasks(tasks):
""" """
...@@ -21,6 +25,7 @@ def create_or_update_celery_periodic_tasks(tasks): ...@@ -21,6 +25,7 @@ def create_or_update_celery_periodic_tasks(tasks):
'args': (16, 16), 'args': (16, 16),
'kwargs': {}, 'kwargs': {},
'enabled': False, 'enabled': False,
'description': ''
}, },
} }
:return: :return:
...@@ -35,34 +40,30 @@ def create_or_update_celery_periodic_tasks(tasks): ...@@ -35,34 +40,30 @@ def create_or_update_celery_periodic_tasks(tasks):
return None return None
if isinstance(detail.get("interval"), int): if isinstance(detail.get("interval"), int):
intervals = IntervalSchedule.objects.filter( kwargs = dict(
every=detail["interval"], period=IntervalSchedule.SECONDS every=detail['interval'],
period=IntervalSchedule.SECONDS,
) )
if intervals: # 不能使用 get_or_create,因为可能会有多个
interval = intervals[0] interval = IntervalSchedule.objects.filter(**kwargs).first()
else: if interval is None:
interval = IntervalSchedule.objects.create( interval = IntervalSchedule.objects.create(**kwargs)
every=detail['interval'],
period=IntervalSchedule.SECONDS,
)
elif isinstance(detail.get("crontab"), str): elif isinstance(detail.get("crontab"), str):
try: try:
minute, hour, day, month, week = detail["crontab"].split() minute, hour, day, month, week = detail["crontab"].split()
except ValueError: except ValueError:
raise SyntaxError("crontab is not valid") logger.error("crontab is not valid")
return
kwargs = dict( kwargs = dict(
minute=minute, hour=hour, day_of_week=week, minute=minute, hour=hour, day_of_week=week,
day_of_month=day, month_of_year=month, timezone=get_current_timezone() day_of_month=day, month_of_year=month, timezone=get_current_timezone()
) )
contabs = CrontabSchedule.objects.filter( crontab = CrontabSchedule.objects.filter(**kwargs).first()
**kwargs if crontab is None:
)
if contabs:
crontab = contabs[0]
else:
crontab = CrontabSchedule.objects.create(**kwargs) crontab = CrontabSchedule.objects.create(**kwargs)
else: else:
raise SyntaxError("Schedule is not valid") logger.error("Schedule is not valid")
return
defaults = dict( defaults = dict(
interval=interval, interval=interval,
...@@ -71,8 +72,9 @@ def create_or_update_celery_periodic_tasks(tasks): ...@@ -71,8 +72,9 @@ def create_or_update_celery_periodic_tasks(tasks):
task=detail['task'], task=detail['task'],
args=json.dumps(detail.get('args', [])), args=json.dumps(detail.get('args', [])),
kwargs=json.dumps(detail.get('kwargs', {})), kwargs=json.dumps(detail.get('kwargs', {})),
enabled=detail.get('enabled', True), description=detail.get('description') or ''
) )
print(defaults)
task = PeriodicTask.objects.update_or_create( task = PeriodicTask.objects.update_or_create(
defaults=defaults, name=name, defaults=defaults, name=name,
......
# -*- coding: utf-8 -*-
#
from .celery import *
from .adhoc import *
...@@ -3,17 +3,7 @@ from __future__ import unicode_literals ...@@ -3,17 +3,7 @@ from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from django.shortcuts import reverse from django.shortcuts import reverse
from .models import Task, AdHoc, AdHocRunHistory, CommandExecution 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
class TaskSerializer(serializers.ModelSerializer): class TaskSerializer(serializers.ModelSerializer):
...@@ -87,3 +77,4 @@ class CommandExecutionSerializer(serializers.ModelSerializer): ...@@ -87,3 +77,4 @@ class CommandExecutionSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_log_url(obj): def get_log_url(obj):
return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id}) 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 # coding: utf-8
import os import os
import subprocess import subprocess
import datetime
import time import time
from django.conf import settings from django.conf import settings
from celery import shared_task, subtask from celery import shared_task, subtask
from celery.exceptions import SoftTimeLimitExceeded from celery.exceptions import SoftTimeLimitExceeded
from django.utils import timezone 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 ( from .celery.decorator import (
register_as_period_task, after_app_shutdown_clean_periodic, register_as_period_task, after_app_shutdown_clean_periodic,
after_app_ready_start after_app_ready_start
) )
from .celery.utils import create_or_update_celery_periodic_tasks from .celery.utils import create_or_update_celery_periodic_tasks
from .models import Task, CommandExecution, CeleryTask from .models import Task, CommandExecution, CeleryTask
from .utils import send_server_performance_mail
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -59,7 +60,7 @@ def run_command_execution(cid, **kwargs): ...@@ -59,7 +60,7 @@ def run_command_execution(cid, **kwargs):
@shared_task @shared_task
@after_app_shutdown_clean_periodic @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(): def clean_tasks_adhoc_period():
logger.debug("Start clean task adhoc and run history") logger.debug("Start clean task adhoc and run history")
tasks = Task.objects.all() tasks = Task.objects.all()
...@@ -72,7 +73,7 @@ def clean_tasks_adhoc_period(): ...@@ -72,7 +73,7 @@ def clean_tasks_adhoc_period():
@shared_task @shared_task
@after_app_shutdown_clean_periodic @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(): def clean_celery_tasks_period():
expire_days = 30 expire_days = 30
logger.debug("Start clean celery task history") logger.debug("Start clean celery task history")
...@@ -103,6 +104,19 @@ def create_or_update_registered_periodic_tasks(): ...@@ -103,6 +104,19 @@ def create_or_update_registered_periodic_tasks():
create_or_update_celery_periodic_tasks(task) 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") @shared_task(queue="ansible")
def hello(name, callback=None): def hello(name, callback=None):
import time import time
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.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> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.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> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.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> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -11,8 +11,6 @@ ...@@ -11,8 +11,6 @@
rel="stylesheet"> rel="stylesheet">
<link href="{% static 'css/plugins/codemirror/ambiance.css' %}" <link href="{% static 'css/plugins/codemirror/ambiance.css' %}"
rel="stylesheet"> rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}"
rel="stylesheet">
<script type="text/javascript" <script type="text/javascript"
src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script> src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script type="text/javascript" <script type="text/javascript"
...@@ -22,7 +20,6 @@ ...@@ -22,7 +20,6 @@
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script> <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/codemirror.js' %}"></script>
<script src="{% static 'js/plugins/codemirror/mode/shell/shell.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"> <style type="text/css">
.xterm .xterm-screen canvas { .xterm .xterm-screen canvas {
position: absolute; position: absolute;
......
...@@ -6,8 +6,6 @@ ...@@ -6,8 +6,6 @@
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/datepicker/datepicker3.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> <style>
.form-control { .form-control {
height: 30px; height: 30px;
...@@ -16,6 +14,11 @@ ...@@ -16,6 +14,11 @@
#search_btn { #search_btn {
margin-bottom: 0; margin-bottom: 0;
} }
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single {
height: 30px !important;
}
</style> </style>
{% endblock %} {% endblock %}
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.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> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/sweetalert/sweetalert.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> <script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
{% endblock %} {% endblock %}
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.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> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -13,6 +13,7 @@ router.register(r'tasks', api.TaskViewSet, 'task') ...@@ -13,6 +13,7 @@ router.register(r'tasks', api.TaskViewSet, 'task')
router.register(r'adhoc', api.AdHocViewSet, 'adhoc') router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
router.register(r'history', api.AdHocRunHistoryViewSet, 'history') router.register(r'history', api.AdHocRunHistoryViewSet, 'history')
router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution') router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution')
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
urlpatterns = [ urlpatterns = [
path('tasks/<uuid:pk>/run/', api.TaskRun.as_view(), name='task-run'), path('tasks/<uuid:pk>/run/', api.TaskRun.as_view(), name='task-run'),
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from django.utils.translation import ugettext_lazy as _ 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
from common.tasks import send_mail_async
from orgs.utils import set_to_root_org from orgs.utils import set_to_root_org
from .models import Task, AdHoc from .models import Task, AdHoc
...@@ -56,4 +58,14 @@ def update_or_create_ansible_task( ...@@ -56,4 +58,14 @@ def update_or_create_ansible_task(
return task, created 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): ...@@ -17,6 +17,6 @@ class CeleryTaskLogView(PermissionsMixin, TemplateView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context.update({ context.update({
'task_id': self.kwargs.get('pk'), 'task_id': self.kwargs.get('pk'),
'ws_port': settings.CONFIG.WS_LISTEN_PORT 'ws_port': settings.WS_LISTEN_PORT
}) })
return context return context
...@@ -80,7 +80,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView): ...@@ -80,7 +80,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
'action': _('Command execution'), 'action': _('Command execution'),
'form': self.get_form(), 'form': self.get_form(),
'system_users': system_users, 'system_users': system_users,
'ws_port': settings.CONFIG.WS_LISTEN_PORT 'ws_port': settings.WS_LISTEN_PORT
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
......
...@@ -46,7 +46,11 @@ class OrgModelViewSet(CommonApiMixin, OrgQuerySetMixin, ModelViewSet): ...@@ -46,7 +46,11 @@ class OrgModelViewSet(CommonApiMixin, OrgQuerySetMixin, ModelViewSet):
class OrgBulkModelViewSet(CommonApiMixin, OrgQuerySetMixin, BulkModelViewSet): class OrgBulkModelViewSet(CommonApiMixin, OrgQuerySetMixin, BulkModelViewSet):
def allow_bulk_destroy(self, qs, filtered): 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 return False
if self.request.query_params.get('spm', ''): if self.request.query_params.get('spm', ''):
return True return True
......
...@@ -95,6 +95,14 @@ class Organization(models.Model): ...@@ -95,6 +95,14 @@ class Organization(models.Model):
def get_org_admins(self): def get_org_admins(self):
return self.org_admins return self.org_admins
def org_id(self):
if self.is_real():
return self.id
elif self.is_root():
return None
else:
return ''
@lazyproperty @lazyproperty
def org_auditors(self): def org_auditors(self):
from users.models import User from users.models import User
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
from .asset_permission import * from .asset_permission import *
from .user_permission import * from .user_permission import *
from .asset_permission_relation import *
from .user_group_permission import * from .user_group_permission import *
from .remote_app_permission import * from .remote_app_permission import *
from .user_remote_app_permission import * from .user_remote_app_permission import *
...@@ -2,12 +2,9 @@ ...@@ -2,12 +2,9 @@
# #
from django.db.models import Q 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 common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgModelViewSet from orgs.mixins.api import OrgModelViewSet
from orgs.mixins import generics
from common.utils import get_object_or_none from common.utils import get_object_or_none
from ..models import AssetPermission from ..models import AssetPermission
from ..hands import ( from ..hands import (
...@@ -17,9 +14,7 @@ from .. import serializers ...@@ -17,9 +14,7 @@ from .. import serializers
__all__ = [ __all__ = [
'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi', 'AssetPermissionViewSet',
'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi',
'AssetPermissionAddAssetApi', 'AssetPermissionAssetsApi',
] ]
...@@ -29,6 +24,7 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -29,6 +24,7 @@ class AssetPermissionViewSet(OrgModelViewSet):
""" """
model = AssetPermission model = AssetPermission
serializer_class = serializers.AssetPermissionCreateUpdateSerializer serializer_class = serializers.AssetPermissionCreateUpdateSerializer
serializer_display_class = serializers.AssetPermissionListSerializer
filter_fields = ['name'] filter_fields = ['name']
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
...@@ -38,11 +34,9 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -38,11 +34,9 @@ class AssetPermissionViewSet(OrgModelViewSet):
) )
return queryset return queryset
def get_serializer_class(self): def is_query_all(self):
if self.action in ("list", 'retrieve') and \ query_all = self.request.query_params.get('all', '1') == '1'
self.request.query_params.get("display"): return query_all
return serializers.AssetPermissionListSerializer
return self.serializer_class
def filter_valid(self, queryset): def filter_valid(self, queryset):
valid_query = self.request.query_params.get('is_valid', None) valid_query = self.request.query_params.get('is_valid', None)
...@@ -81,7 +75,10 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -81,7 +75,10 @@ class AssetPermissionViewSet(OrgModelViewSet):
if not _nodes: if not _nodes:
return queryset.none() 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: for node in _nodes:
nodes |= set(node.get_ancestors(with_self=True)) nodes |= set(node.get_ancestors(with_self=True))
queryset = queryset.filter(nodes__in=nodes) queryset = queryset.filter(nodes__in=nodes)
...@@ -101,6 +98,9 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -101,6 +98,9 @@ class AssetPermissionViewSet(OrgModelViewSet):
return queryset return queryset
if not assets: if not assets:
return queryset.none() return queryset.none()
if not self.is_query_all():
queryset = queryset.filter(assets__in=assets)
return queryset
inherit_all_nodes = set() inherit_all_nodes = set()
inherit_nodes_keys = assets.all().values_list('nodes__key', flat=True) inherit_nodes_keys = assets.all().values_list('nodes__key', flat=True)
...@@ -117,7 +117,6 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -117,7 +117,6 @@ class AssetPermissionViewSet(OrgModelViewSet):
def filter_user(self, queryset): def filter_user(self, queryset):
user_id = self.request.query_params.get('user_id') user_id = self.request.query_params.get('user_id')
username = self.request.query_params.get('username') username = self.request.query_params.get('username')
query_group = self.request.query_params.get('all')
if user_id: if user_id:
user = get_object_or_none(User, pk=user_id) user = get_object_or_none(User, pk=user_id)
elif username: elif username:
...@@ -126,14 +125,14 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -126,14 +125,14 @@ class AssetPermissionViewSet(OrgModelViewSet):
return queryset return queryset
if not user: if not user:
return queryset.none() return queryset.none()
kwargs = {} if not self.is_query_all():
args = [] queryset = queryset.filter(users=user)
if query_group: return queryset
groups = user.groups.all() groups = user.groups.all()
args.append(Q(users=user) | Q(user_groups__in=groups)) queryset = queryset.filter(
else: Q(users=user) | Q(user_groups__in=groups)
kwargs["users"] = user ).distinct()
return queryset.filter(*args, **kwargs).distinct() return queryset
def filter_user_group(self, queryset): def filter_user_group(self, queryset):
user_group_id = self.request.query_params.get('user_group_id') user_group_id = self.request.query_params.get('user_group_id')
...@@ -167,99 +166,3 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -167,99 +166,3 @@ class AssetPermissionViewSet(OrgModelViewSet):
queryset = self.filter_user_group(queryset) queryset = self.filter_user_group(queryset)
queryset = queryset.distinct() queryset = queryset.distinct()
return queryset 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): ...@@ -80,9 +80,10 @@ class BasePermission(OrgModelMixin):
return False return False
def get_all_users(self): def get_all_users(self):
users = set(self.users.all()) from users.models import User
for group in self.user_groups.all(): users_id = self.users.all().values_list('id', flat=True)
_users = group.users.all() groups_id = self.user_groups.all().values_list('id', flat=True)
set_or_append_attr_bulk(_users, 'inherit', group.name) users = User.objects.filter(
users.update(set(_users)) Q(id__in=users_id) | Q(groups__id__in=groups_id)
).distinct()
return users return users
...@@ -4,3 +4,4 @@ ...@@ -4,3 +4,4 @@
from .asset_permission import * from .asset_permission import *
from .user_permission import * from .user_permission import *
from .remote_app_permission import * from .remote_app_permission import *
from .asset_permission_relation import *
...@@ -6,12 +6,10 @@ from rest_framework import serializers ...@@ -6,12 +6,10 @@ from rest_framework import serializers
from common.fields import StringManyToManyField from common.fields import StringManyToManyField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from perms.models import AssetPermission, Action from perms.models import AssetPermission, Action
from assets.models import Asset
__all__ = [ __all__ = [
'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer',
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', 'ActionsField',
'ActionsField', 'AssetPermissionAssetsSerializer',
] ]
...@@ -59,23 +57,4 @@ class AssetPermissionListSerializer(BulkOrgResourceModelSerializer): ...@@ -59,23 +57,4 @@ class AssetPermissionListSerializer(BulkOrgResourceModelSerializer):
fields = '__all__' 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 @@ ...@@ -2,10 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -54,8 +50,8 @@ ...@@ -54,8 +50,8 @@
<th class="text-center"> <th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" > <input type="checkbox" id="check_all" class="ipt_check_all" >
</th> </th>
<th class="text-center">{% trans 'Hostname' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'IP' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -75,7 +71,7 @@ ...@@ -75,7 +71,7 @@
<form> <form>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td colspan="2"> <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> </select>
</td> </td>
</tr> </tr>
...@@ -112,10 +108,46 @@ ...@@ -112,10 +108,46 @@
</form> </form>
{% for node in asset_permission.nodes.all %} {% 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 ><b class="bdg_group" data-gid={{ node.id }}>{{ node.full_value }}</b></td>
<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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -129,69 +161,83 @@ ...@@ -129,69 +161,83 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_asset_list_modal.html' %} {% include 'assets/_asset_list_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <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 assetsRelationUrl = "{% url 'api-perms:asset-permissions-assets-relation-list' %}";
var the_url = "{% url 'api-perms:asset-permission-remove-asset' pk=asset_permission.id %}"; var nodesRelationUrl = "{% url 'api-perms:asset-permissions-nodes-relation-list' %}";
var body = { var systemUsersRelationUrl = "{% url 'api-perms:asset-permissions-system-users-relation-list' %}";
assets: assets
}; function getRelationUrl(type) {
var success = function(data) { var theUrl = "";
location.reload(); 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({ requestApi({
url: the_url, url: theUrl,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success method: "POST",
success: reloadPage
}); });
} }
function updateNodes(nodes, success) { function removeObject(objectId, type) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}"; if (!objectId) {
var body = { return
nodes: nodes }
}; var theUrl = getRelationUrl(type);
theUrl = setUrlParam(theUrl, 'assetpermission', "{{ object.id }}");
theUrl = setUrlParam(theUrl, type, objectId);
requestApi({ requestApi({
url: the_url, url: theUrl,
body: JSON.stringify(body), method: "DELETE",
success: success success: reloadPage
}); });
} }
var table; var table;
function initAssetTable() { function initAssetTable() {
var options = { var options = {
ele: $('#asset_list_table'), ele: $('#asset_list_table'),
toggle: true, toggle: true,
columnDefs: [ columnDefs: [
{ {targets: 2, createdCell: function (td, cellData, rowData) {
targets: 0, 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>'
var html = '<input type="checkbox" class="text-center ipt_check" id="id_' + cellData + '">'; .replace('{{ DEFAULT_PK }}', cellData);
$(td).html(html); 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: [ columns: [
{data: "id"}, {data: "hostname"}, {data: "ip"} {data: "asset"}, {data: "asset_display"}, {data: "asset", width: "30px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
...@@ -199,76 +245,38 @@ function initAssetTable() { ...@@ -199,76 +245,38 @@ function initAssetTable() {
return table return table
} }
var assetDirectRelated = {{ assets | safe }};
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
table = initAssetTable();
nodesSelect2Init(".nodes-select2"); nodesSelect2Init(".nodes-select2");
initAssetTable();
$("#id_assets").parent().find(".select2-selection").on('click', function (e) { initAssetTreeModel('#id_assets');
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
}
})
}) })
.on('click', '.btn-add-assets', function () { .on('click', '.btn-add-assets', function () {
var assets_selected = $("#id_assets option:selected").map(function () { var assetsId = $("#id_assets").val();
return $(this).attr('value'); addObjects(assetsId, "asset");
}).get();
if (assets_selected.length === 0) {
return false;
}
addAssets(assets_selected);
}) })
.on('click', '.btn-remove-asset', function () { .on('click', '.btn-remove-asset', function () {
var asset_id = $(this).data("gid"); var assetId = $(this).data("uid");
if (asset_id === "") { removeObject(assetId, "asset")
return
}
var assets = [asset_id];
removeAssets(assets)
}) })
.on('click', '#btn-add-node', function () { .on('click', '#btn-add-node', function () {
var nodes_selected = {}; var nodesId = $("#nodes_select2").val();
$("#nodes_select2 option:selected").each(function (i, data) { addObjects(nodesId, "node");
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);
})
.on('click', '.btn-remove-node', function () { .on('click', '.btn-remove-node', function () {
var $this = $(this); var nodeId = $(this).data("uid");
var $tr = $this.closest('tr'); removeObject(nodeId, "node")
var nodes = $('.bdg_group').map(function() { })
if ($(this).data('gid') !== $this.data('gid')){ .on('click', '#btn-add-system-user', function () {
return $(this).data('gid'); var systemUsersId = $("#system_users_select2").val();
} addObjects(systemUsersId, "systemuser");
}).get(); })
var success = function () { .on('click', '.btn-remove-sys-user', function () {
$tr.remove() var systemUserId = $(this).data("uid");
}; removeObject(systemUserId, "systemuser")
updateNodes(nodes, success);
}) })
</script> </script>
{% endblock %} {% endblock %}
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
{% load static %} {% load static %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% 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"> <link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
{% endblock %} {% endblock %}
...@@ -111,21 +109,14 @@ $(document).ready(function () { ...@@ -111,21 +109,14 @@ $(document).ready(function () {
initDateRangePicker('#date_start'); initDateRangePicker('#date_start');
initDateRangePicker('#date_expired'); initDateRangePicker('#date_expired');
initAssetTreeModel('#id_assets');
$("#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();
}
})
}) })
.on("submit", "form", function (evt) { .on("submit", "form", function (evt) {
evt.preventDefault(); evt.preventDefault();
var the_url = '{% url 'api-perms:asset-permission-list' %}'; var theUrl = '{% url 'api-perms:asset-permission-list' %}';
var method = "POST"; var method = "POST";
{% if api_action == "update" %} {% 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"; method = "PUT";
{% endif %} {% endif %}
var redirect_to = '{% url "perms:asset-permission-list" %}'; var redirect_to = '{% url "perms:asset-permission-list" %}';
...@@ -135,7 +126,7 @@ $(document).ready(function () { ...@@ -135,7 +126,7 @@ $(document).ready(function () {
objectAttrsIsDatetime(data, ['date_start', 'date_expired']); objectAttrsIsDatetime(data, ['date_start', 'date_expired']);
objectAttrsIsBool(data, ['is_active']); objectAttrsIsBool(data, ['is_active']);
var props = { var props = {
url: the_url, url: theUrl,
data: data, data: data,
method: method, method: method,
form: form, form: form,
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -135,42 +130,7 @@ ...@@ -135,42 +130,7 @@
</div> </div>
</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> </div>
</div> </div>
...@@ -182,17 +142,7 @@ ...@@ -182,17 +142,7 @@
<script> <script>
jumpserver.system_users_selected = {}; 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 () { $(document).ready(function () {
$('.select2').select2() $('.select2').select2()
...@@ -213,36 +163,7 @@ $(document).ready(function () { ...@@ -213,36 +163,7 @@ $(document).ready(function () {
var redirect_url = "{% url 'perms:asset-permission-list' %}"; var redirect_url = "{% url 'perms:asset-permission-list' %}";
objectDelete($this, name, the_url, redirect_url); objectDelete($this, name, the_url, redirect_url);
}) })
.on('click', '#btn-add-system-user', function () { .on('click', '#is_active', 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 () {
var the_url = '{% url "api-perms:asset-permission-detail" pk=object.id %}'; var the_url = '{% url "api-perms:asset-permission-detail" pk=object.id %}';
var checked = $(this).prop('checked'); var checked = $(this).prop('checked');
var body = { var body = {
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<link href="{% static 'css/plugins/jstree/style.min.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/jstree/style.min.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
...@@ -96,9 +94,6 @@ function beforeNodeAsync(treeId, treeNode) { ...@@ -96,9 +94,6 @@ function beforeNodeAsync(treeId, treeNode) {
return true return true
} }
function makeLabel(data) {
return "<label class='detail-key'><b>" + data[0] + ": </b></label>" + data[1] + "</br>"
}
function format(d) { function format(d) {
var data = ""; var data = "";
...@@ -176,7 +171,7 @@ function initTable() { ...@@ -176,7 +171,7 @@ function initTable() {
$(td).html(update_btn + del_btn); $(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: [ columns: [
{data: "id"}, {data: "name"}, {data: "users", orderable: false}, {data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "assets", orderable: false}, {data: "user_groups", orderable: false}, {data: "assets", orderable: false},
...@@ -213,6 +208,10 @@ $(document).ready(function(){ ...@@ -213,6 +208,10 @@ $(document).ready(function(){
{title: "{% trans 'Hostname' %}", value: "hostname"}, {title: "{% trans 'Hostname' %}", value: "hostname"},
{title: "{% trans 'Node' %}", value: "node"}, {title: "{% trans 'Node' %}", value: "node"},
{title: "{% trans 'System user' %}", value: "system_user"}, {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) initTableFilterDropdown('#permission_list_table_filter input', filterMenu)
}) })
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -48,29 +44,19 @@ ...@@ -48,29 +44,19 @@
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<table class="table table-hover"> <table class="table table-hover" id="user_list_table">
<thead> <thead>
<tr> <tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th>{% trans 'Name' %}</th> <th>{% trans 'Name' %}</th>
<th>{% trans 'Username' %}</th> <th class="text-center">{% trans 'Action' %}</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <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> </tbody>
</table> </table>
<div class="row">
{% include '_pagination.html' %}
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -85,10 +71,7 @@ ...@@ -85,10 +71,7 @@
<form> <form>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td colspan="2"> <td colspan="2">
<select data-placeholder="{% trans 'Select user' %}" class="select2 user" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select user' %}" class="select2 users-select2" style="width: 100%" multiple="" tabindex="4">
{% for user in users_remain %}
<option value="{{ user.id }}">{{ user }}</option>
{% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
...@@ -113,7 +96,7 @@ ...@@ -113,7 +96,7 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <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 %} {% for user_group in user_groups_remain %}
<option value="{{ user_group.id }}" id="opt_{{ user_group.id }}">{{ user_group }}</option> <option value="{{ user_group.id }}" id="opt_{{ user_group.id }}">{{ user_group }}</option>
{% endfor %} {% endfor %}
...@@ -128,7 +111,7 @@ ...@@ -128,7 +111,7 @@
</form> </form>
{% for user_group in asset_permission.user_groups.all %} {% 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 ><b class="bdg_group" data-gid={{ user_group.id }}>{{ user_group }}</b></td>
<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> <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 @@ ...@@ -145,120 +128,124 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
jumpserver.users_selected = {}; var usersDirectRelated = {{ users | safe }};
jumpserver.nodes_selected = {};
function addUsers(users) { var table;
var the_url = "{% url 'api-perms:asset-permission-add-user' pk=asset_permission.id %}"; function initUsersTable() {
var body = { var options = {
users: users 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) { var success = function(data) {
location.reload(); location.reload();
}; };
requestApi({ requestApi({
url: the_url, url: theUrl,
body: JSON.stringify(body), body: JSON.stringify(body),
method: "POST",
success: success success: success
}); });
} }
function removeUser(users) { function removeUser(userId) {
var the_url = "{% url 'api-perms:asset-permission-remove-user' pk=asset_permission.id %}"; var theUrl = "{% url 'api-perms:asset-permissions-users-relation-list' %}?user=userId&assetpermission={{ object.id }}";
var body = { theUrl = theUrl.replace("userId", userId);
users: users 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) { var success = function(data) {
location.reload(); location.reload();
}; };
requestApi({ requestApi({
url: the_url, url: theUrl,
body: JSON.stringify(body), body: JSON.stringify(body),
method: "POST",
success: success success: success
}); });
} }
function updateGroup(groups) { function removeGroup(groupId) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}"; var theUrl = "{% url 'api-perms:asset-permissions-user-groups-relation-list' %}?assetpermission={{ object.id }}";
var body = { theUrl = theUrl.replace("groupId", groupId);
user_groups: groups var success = function(data) {
location.reload();
}; };
requestApi({ requestApi({
url: the_url, url: theUrl,
body: JSON.stringify(body) method: "DELETE",
success: success
}); });
} }
$(document).ready(function () { $(document).ready(function () {
$('.select2.user').select2() $(".select2").select2();
.on('select2:select', function(evt) { initUsersTable();
var data = evt.params.data; usersSelect2Init(".users-select2", null, usersDirectRelated);
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]
})
}).on('click', '.btn-add-user', function () { }).on('click', '.btn-add-user', function () {
if (Object.keys(jumpserver.users_selected).length === 0) { var usersSelected = $(".users-select2").val();
return false; addUsers(usersSelected);
}
var users_id = [];
$.map(jumpserver.users_selected, function(value, index) {
users_id.push(index);
});
addUsers(users_id);
}).on('click', '.btn-remove-user', function () { }).on('click', '.btn-remove-user', function () {
var user_id = $(this).data("gid"); var userId = $(this).data("uid");
if (user_id === "") { removeUser(userId)
return
}
var users = [user_id];
removeUser(users)
}).on('click', '#btn-add-group', function () { }).on('click', '#btn-add-group', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) { var groupsSelected = $(".user-groups-select2").val();
return false; addGroups(groupsSelected);
}
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);
}).on('click', '.btn-remove-group', function () { }).on('click', '.btn-remove-group', function () {
var $this = $(this); var groupId = $(this).data("gid");
var $tr = $this.closest('tr'); removeGroup(groupId);
var groups = $('.bdg_group').map(function() {
if ($(this).data('gid') !== $this.data('gid')){
return $(this).data('gid');
}
}).get();
updateGroup(groups);
$tr.remove()
}) })
</script> </script>
{% endblock %} {% endblock %}
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
{% load static %} {% load static %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% 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"> <link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
{% endblock %} {% endblock %}
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -244,4 +239,4 @@ $(document).ready(function () { ...@@ -244,4 +239,4 @@ $(document).ready(function () {
}); });
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -161,4 +157,4 @@ ...@@ -161,4 +157,4 @@
removeRemoteApps(remote_apps) removeRemoteApps(remote_apps)
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
...@@ -253,4 +249,4 @@ ...@@ -253,4 +249,4 @@
$tr.remove() $tr.remove()
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
# coding:utf-8 # coding:utf-8
from django.urls import path, re_path from django.urls import re_path
from rest_framework import routers
from common import api as capi 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' 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 = [ old_version_urlpatterns = [
re_path('(?P<resource>user|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api) 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 = asset_permission_urlpatterns + \
remote_app_permission_urlpatterns \
urlpatterns += router.urls + 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): ...@@ -98,9 +98,7 @@ class AssetPermissionDetailView(PermissionsMixin, DetailView):
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'action': _('Asset permission detail'), 'action': _('Asset permission detail'),
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -131,14 +129,13 @@ class AssetPermissionUserView(PermissionsMixin, ...@@ -131,14 +129,13 @@ class AssetPermissionUserView(PermissionsMixin,
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
user_remain = current_org.get_org_members(exclude=('Auditor',)).exclude( users = [str(i) for i in self.object.users.all().values_list('id', flat=True)]
assetpermission=self.object)
user_groups_remain = UserGroup.objects.exclude( user_groups_remain = UserGroup.objects.exclude(
assetpermission=self.object) assetpermission=self.object)
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'action': _('Asset permission user list'), 'action': _('Asset permission user list'),
'users_remain': user_remain, 'users': users,
'user_groups_remain': user_groups_remain, 'user_groups_remain': user_groups_remain,
} }
kwargs.update(context) kwargs.update(context)
...@@ -163,13 +160,15 @@ class AssetPermissionAssetView(PermissionsMixin, ...@@ -163,13 +160,15 @@ class AssetPermissionAssetView(PermissionsMixin,
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
nodes_remain = Node.objects.exclude( assets = self.object.assets.all().values_list('id', flat=True)
id__in=self.object.nodes.all().values_list('id', flat=True) assets = [str(i) for i in assets]
).only('key')
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'assets': assets,
'action': _('Asset permission asset list'), 'action': _('Asset permission asset list'),
'nodes_remain': nodes_remain, 'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
\ No newline at end of file
...@@ -245,87 +245,6 @@ class LDAPCacheRefreshAPI(generics.RetrieveAPIView): ...@@ -245,87 +245,6 @@ class LDAPCacheRefreshAPI(generics.RetrieveAPIView):
return Response(data={'msg': 'success'}) 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): class PublicSettingApi(generics.RetrieveAPIView):
permission_classes = () permission_classes = ()
serializer_class = PublicSettingSerializer 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 import forms
from django.utils.translation import ugettext_lazy as _ 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(): from .base import BaseForm
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()
__all__ = ['SecuritySettingForm']
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
class SecuritySettingForm(BaseForm): class SecuritySettingForm(BaseForm):
...@@ -263,27 +91,3 @@ class SecuritySettingForm(BaseForm): ...@@ -263,27 +91,3 @@ class SecuritySettingForm(BaseForm):
help_text=_('After opening, the user password changes ' help_text=_('After opening, the user password changes '
'and resets must contain special characters') '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 import json
from django.db import models from django.db import models
from django.core.cache import cache
from django.db.utils import ProgrammingError, OperationalError from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import ugettext_lazy as _ 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 from common.utils import get_signer
...@@ -34,12 +33,28 @@ class Setting(models.Model): ...@@ -34,12 +33,28 @@ class Setting(models.Model):
comment = models.TextField(verbose_name=_("Comment")) comment = models.TextField(verbose_name=_("Comment"))
objects = SettingManager() objects = SettingManager()
cache_key_prefix = '_SETTING_'
def __str__(self): def __str__(self):
return self.name return self.name
def __getattr__(self, item): @classmethod
return cache.get(item) 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 @property
def cleaned_value(self): def cleaned_value(self):
...@@ -64,44 +79,6 @@ class Setting(models.Model): ...@@ -64,44 +79,6 @@ class Setting(models.Model):
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
raise ValueError("Json dump error: {}".format(str(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 @classmethod
def refresh_all_settings(cls): def refresh_all_settings(cls):
try: try:
...@@ -112,16 +89,8 @@ class Setting(models.Model): ...@@ -112,16 +89,8 @@ class Setting(models.Model):
pass pass
def refresh_setting(self): def refresh_setting(self):
setattr(settings, self.name, self.cleaned_value) key = self.cache_key_prefix + self.name
if self.name == "AUTH_LDAP": cache.set(key, self.cleaned_value, None)
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
class Meta: class Meta:
db_table = "settings_setting" 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): __all__ = ['LDAPTestSerializer', 'LDAPUserSerializer']
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)
class LDAPTestSerializer(serializers.Serializer): class LDAPTestSerializer(serializers.Serializer):
...@@ -29,6 +23,3 @@ class LDAPUserSerializer(serializers.Serializer): ...@@ -29,6 +23,3 @@ class LDAPUserSerializer(serializers.Serializer):
email = serializers.CharField() email = serializers.CharField()
existing = serializers.BooleanField(read_only=True) 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 ...@@ -4,9 +4,6 @@ import json
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save, pre_save 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 jumpserver.utils import current_request
from common.utils import get_logger, ssh_key_gen from common.utils import get_logger, ssh_key_gen
...@@ -23,56 +20,9 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): ...@@ -23,56 +20,9 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
@receiver(django_ready) @receiver(django_ready)
def monkey_patch_settings(sender, **kwargs): def on_django_ready_add_db_config(sender, **kwargs):
logger.debug("Monkey patch settings") from django.conf import settings
cache_key_prefix = '_SETTING_' settings.DYNAMIC.db_setting = 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
@receiver(django_ready) @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 @@ ...@@ -44,7 +44,7 @@
<h3>{% trans "Security setting" %}</h3> <h3>{% trans "Security setting" %}</h3>
{% for field in form %} {% for field in form %}
{% if forloop.counter == 6 %} {% if forloop.counter == 8 %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans "Password check rule" %}</h3> <h3>{% trans "Password check rule" %}</h3>
{% endif %} {% endif %}
......
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
{% load common_tags %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
...@@ -65,7 +70,7 @@ ...@@ -65,7 +70,7 @@
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-4 col-sm-offset-2"> <div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
...@@ -73,53 +78,6 @@ ...@@ -73,53 +78,6 @@
type="submit">{% trans 'Submit' %}</button> type="submit">{% trans 'Submit' %}</button>
</div> </div>
</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> </form>
</div> </div>
</div> </div>
...@@ -131,60 +89,7 @@ ...@@ -131,60 +89,7 @@
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <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 () { $(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> </script>
{% endblock %} {% endblock %}
...@@ -12,9 +12,6 @@ urlpatterns = [ ...@@ -12,9 +12,6 @@ urlpatterns = [
path('ldap/users/', api.LDAPUserListApi.as_view(), name='ldap-user-list'), 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/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'),
path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'), 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'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'),
] ]
...@@ -12,7 +12,5 @@ urlpatterns = [ ...@@ -12,7 +12,5 @@ urlpatterns = [
url(r'^email-content/$', views.EmailContentSettingView.as_view(), name='email-content-setting'), url(r'^email-content/$', views.EmailContentSettingView.as_view(), name='email-content-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-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'), url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
] ]
...@@ -4,7 +4,6 @@ from django.contrib import messages ...@@ -4,7 +4,6 @@ from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from common.permissions import PermissionsMixin, IsSuperUser from common.permissions import PermissionsMixin, IsSuperUser
from common import utils
from .utils import LDAPSyncUtil from .utils import LDAPSyncUtil
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm
...@@ -98,8 +97,9 @@ class TerminalSettingView(PermissionsMixin, TemplateView): ...@@ -98,8 +97,9 @@ class TerminalSettingView(PermissionsMixin, TemplateView):
permission_classes = [IsSuperUser] permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
command_storage = utils.get_command_storage_setting() from terminal.models import CommandStorage, ReplayStorage
replay_storage = utils.get_replay_storage_setting() command_storage = CommandStorage.objects.all()
replay_storage = ReplayStorage.objects.all()
context = { context = {
'app': _('Settings'), 'app': _('Settings'),
...@@ -124,32 +124,6 @@ class TerminalSettingView(PermissionsMixin, TemplateView): ...@@ -124,32 +124,6 @@ class TerminalSettingView(PermissionsMixin, TemplateView):
return render(request, self.template_name, context) 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): class SecuritySettingView(PermissionsMixin, TemplateView):
form_class = SecuritySettingForm form_class = SecuritySettingForm
template_name = "settings/security_setting.html" template_name = "settings/security_setting.html"
......
...@@ -413,7 +413,7 @@ $.fn.serializeObject = function () { ...@@ -413,7 +413,7 @@ $.fn.serializeObject = function () {
}; };
function makeLabel(data) { 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) { function parseTableFilter(value) {
...@@ -600,6 +600,7 @@ jumpserver.initServerSideDataTable = function (options) { ...@@ -600,6 +600,7 @@ jumpserver.initServerSideDataTable = function (options) {
// op_html: 'div.btn-group?', // op_html: 'div.btn-group?',
// paging: true, // paging: true,
// paging_numbers_length: 5; // paging_numbers_length: 5;
// hideDefaultDefs: false;
// } // }
var pagingNumbersLength = 5; var pagingNumbersLength = 5;
if (options.paging_numbers_length){ if (options.paging_numbers_length){
...@@ -613,7 +614,8 @@ jumpserver.initServerSideDataTable = function (options) { ...@@ -613,7 +614,8 @@ jumpserver.initServerSideDataTable = function (options) {
orderable: false, orderable: false,
width: "20px", width: "20px",
createdCell: function (td, cellData) { 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) { ...@@ -622,6 +624,9 @@ jumpserver.initServerSideDataTable = function (options) {
render: $.fn.dataTable.render.text() render: $.fn.dataTable.render.text()
} }
]; ];
if (options.hideDefaultDefs) {
columnDefs = [];
}
var select_style = options.select_style || 'multi'; var select_style = options.select_style || 'multi';
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var select = { var select = {
...@@ -635,7 +640,7 @@ jumpserver.initServerSideDataTable = function (options) { ...@@ -635,7 +640,7 @@ jumpserver.initServerSideDataTable = function (options) {
pageLength: options.pageLength || 15, 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">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: 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 || [], order: options.order || [],
buttons: [], buttons: [],
columnDefs: columnDefs, columnDefs: columnDefs,
...@@ -1241,10 +1246,28 @@ function readFile(ref) { ...@@ -1241,10 +1246,28 @@ function readFile(ref) {
return 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({ return $(selector).select2({
closeOnSelect: false, closeOnSelect: false,
ajax: { ajax: {
...@@ -1260,43 +1283,53 @@ function nodesSelect2Init(selector, url) { ...@@ -1260,43 +1283,53 @@ function nodesSelect2Init(selector, url) {
}, },
processResults: function (data) { processResults: function (data) {
var results = $.map(data.results, function (v, i) { 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; var more = !!data.next;
return {results: results, pagination: {"more": more}} return {results: results, pagination: {"more": more}}
} }
}, },
}) })
} }
function usersSelect2Init(selector, url) { function usersSelect2Init(selector, url, disabledData) {
if (!url) { if (!url) {
url = '/api/v1/users/users/' url = '/api/v1/users/users/'
} }
return $(selector).select2({ function displayFormat(v) {
closeOnSelect: false, return v.name + '(' + v.username +')';
ajax: { }
url: url, var option = {
data: function (params) { url: url,
var page = params.page || 1; selector: selector,
var query = { disabledData: disabledData,
search: params.term, displayFormat: displayFormat
offset: (page - 1) * 10, };
limit: 10 return select2AjaxInit(option)
}; }
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 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) { function showCeleryTaskLog(taskId) {
...@@ -1324,7 +1357,7 @@ function initDateRangePicker(selector, options) { ...@@ -1324,7 +1357,7 @@ function initDateRangePicker(selector, options) {
timePicker24Hour: true, timePicker24Hour: true,
autoApply: true, autoApply: true,
}; };
var userLang = navigator.language || navigator.userLanguage;; var userLang = navigator.language || navigator.userLanguage;
if (userLang.indexOf('zh') !== -1) { if (userLang.indexOf('zh') !== -1) {
defaultOption.locale = zhLocale; 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 @@ ...@@ -3,8 +3,6 @@
{% load static %} {% load static %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% 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> <script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
{% block custom_head_css_js_create %} {% endblock %} {% block custom_head_css_js_create %} {% endblock %}
{% endblock %} {% endblock %}
......
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %} {% load static %}
{% load i18n %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> <div class="row">
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
<script src="{% static "js/inspinia.js" %}"></script> <script src="{% static "js/inspinia.js" %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script> <script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}?v=5"></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> <script>
activeNav("{{ FORCE_SCRIPT_NAME }}"); activeNav("{{ FORCE_SCRIPT_NAME }}");
$(document).ready(function(){ $(document).ready(function(){
...@@ -16,5 +18,9 @@ $(document).ready(function(){ ...@@ -16,5 +18,9 @@ $(document).ready(function(){
if ($('.tooltip')[0]) { if ($('.tooltip')[0]) {
$('.tooltip').tooltip(); $('.tooltip').tooltip();
} }
var userLang = navigator.language || navigator.userLanguage;
if (userLang.indexOf('zh') !== -1) {
$.fn.select2.defaults.set('language', 'zh-CN')
}
}); });
</script> </script>
...@@ -13,3 +13,5 @@ ...@@ -13,3 +13,5 @@
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script> <script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script> <script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/plugins/datatables/datatables.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(){ ...@@ -477,4 +477,4 @@ $(document).ready(function(){
); );
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -4,3 +4,4 @@ from .terminal import * ...@@ -4,3 +4,4 @@ from .terminal import *
from .session import * from .session import *
from .command import * from .command import *
from .task import * from .task import *
from .storage import *
...@@ -12,7 +12,7 @@ import jms_storage ...@@ -12,7 +12,7 @@ import jms_storage
from common.utils import is_uuid, get_logger from common.utils import is_uuid, get_logger
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor
from common.filters import DatetimeRangeFilter from common.drf.filters import DatetimeRangeFilter
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import SystemUser from ..hands import SystemUser
from ..models import Session 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 importlib import import_module
from django.conf import settings from django.conf import settings
from .command.serializers import SessionCommandSerializer from .command.serializers import SessionCommandSerializer
from ..const import COMMAND_STORAGE_TYPE_SERVER
from common import utils
TYPE_ENGINE_MAPPING = { TYPE_ENGINE_MAPPING = {
'elasticsearch': 'terminal.backends.command.es', 'elasticsearch': 'terminal.backends.command.es',
...@@ -18,19 +18,18 @@ def get_command_storage(): ...@@ -18,19 +18,18 @@ def get_command_storage():
def get_terminal_command_storages(): def get_terminal_command_storages():
from ..models import CommandStorage
storage_list = {} storage_list = {}
command_storage = utils.get_command_storage_setting() for s in CommandStorage.objects.all():
tp = s.type
for name, params in command_storage.items(): if tp == COMMAND_STORAGE_TYPE_SERVER:
tp = params['TYPE']
if tp == 'server':
storage = get_command_storage() storage = get_command_storage()
else: else:
if not TYPE_ENGINE_MAPPING.get(tp): if not TYPE_ENGINE_MAPPING.get(tp):
continue continue
engine_class = import_module(TYPE_ENGINE_MAPPING[tp]) engine_class = import_module(TYPE_ENGINE_MAPPING[tp])
storage = engine_class.CommandStore(params) storage = engine_class.CommandStore(s.config)
storage_list[name] = storage storage_list[s.name] = storage
return storage_list return storage_list
......
...@@ -5,3 +5,86 @@ ASSETS_CACHE_KEY = "terminal__session__assets" ...@@ -5,3 +5,86 @@ ASSETS_CACHE_KEY = "terminal__session__assets"
USERS_CACHE_KEY = "terminal__session__users" USERS_CACHE_KEY = "terminal__session__users"
SYSTEM_USER_CACHE_KEY = "terminal__session__system_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 import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .models import Terminal from ..models import Terminal, ReplayStorage, CommandStorage
def get_all_command_storage(): def get_all_command_storage():
from common import utils for c in CommandStorage.objects.all():
command_storage = utils.get_command_storage_setting() yield (c.name, c.name)
for k, v in command_storage.items():
yield (k, k)
def get_all_replay_storage(): def get_all_replay_storage():
from common import utils for r in ReplayStorage.objects.all():
replay_storage = utils.get_replay_storage_setting() yield (r.name, r.name)
for k, v in replay_storage.items():
yield (k, k)
class TerminalForm(forms.ModelForm): 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 ...@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import os import os
import uuid import uuid
import jms_storage
from django.db import models from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
...@@ -13,9 +14,11 @@ from django.core.cache import cache ...@@ -13,9 +14,11 @@ from django.core.cache import cache
from users.models import User from users.models import User
from orgs.mixins.models import OrgModelMixin 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 import get_multi_command_storage
from .backends.command.models import AbstractSessionCommand from .backends.command.models import AbstractSessionCommand
from . import const
class Terminal(models.Model): class Terminal(models.Model):
...@@ -55,21 +58,21 @@ class Terminal(models.Model): ...@@ -55,21 +58,21 @@ class Terminal(models.Model):
self.user.is_active = active self.user.is_active = active
self.user.save() self.user.save()
def get_command_storage_setting(self): def get_command_storage_config(self):
storage_all = get_command_storage_setting() s = CommandStorage.objects.filter(name=self.command_storage).first()
if self.command_storage in storage_all: if s:
storage = storage_all.get(self.command_storage) config = s.config
else: else:
storage = storage_all.get('default') config = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
return {"TERMINAL_COMMAND_STORAGE": storage} return {"TERMINAL_COMMAND_STORAGE": config}
def get_replay_storage_setting(self): def get_replay_storage_config(self):
storage_all = get_replay_storage_setting() s = ReplayStorage.objects.filter(name=self.replay_storage).first()
if self.replay_storage in storage_all: if s:
storage = storage_all.get(self.replay_storage) config = s.config
else: else:
storage = storage_all.get('default') config = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
return {"TERMINAL_REPLAY_STORAGE": storage} return {"TERMINAL_REPLAY_STORAGE": config}
@property @property
def config(self): def config(self):
...@@ -78,8 +81,8 @@ class Terminal(models.Model): ...@@ -78,8 +81,8 @@ class Terminal(models.Model):
if not k.startswith('TERMINAL'): if not k.startswith('TERMINAL'):
continue continue
configs[k] = getattr(settings, k) configs[k] = getattr(settings, k)
configs.update(self.get_command_storage_setting()) configs.update(self.get_command_storage_config())
configs.update(self.get_replay_storage_setting()) configs.update(self.get_replay_storage_config())
configs.update({ configs.update({
'SECURITY_MAX_IDLE_TIME': settings.SECURITY_MAX_IDLE_TIME 'SECURITY_MAX_IDLE_TIME': settings.SECURITY_MAX_IDLE_TIME
}) })
...@@ -153,17 +156,19 @@ class Session(OrgModelMixin): ...@@ -153,17 +156,19 @@ class Session(OrgModelMixin):
) )
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_("User")) user = models.CharField(max_length=128, verbose_name=_("User"), db_index=True)
asset = models.CharField(max_length=1024, verbose_name=_("Asset")) user_id = models.CharField(blank=True, default='', max_length=36, db_index=True)
system_user = models.CharField(max_length=128, verbose_name=_("System user")) 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") 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) is_finished = models.BooleanField(default=False)
has_replay = models.BooleanField(default=False, verbose_name=_("Replay")) has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
has_command = models.BooleanField(default=False, verbose_name=_("Command")) has_command = models.BooleanField(default=False, verbose_name=_("Command"))
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL) terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL)
protocol = models.CharField(choices=PROTOCOL_CHOICES, default='ssh', max_length=8) protocol = models.CharField(choices=PROTOCOL_CHOICES, default='ssh', max_length=8, db_index=True)
date_last_active = models.DateTimeField(verbose_name=_("Date last active"), default=timezone.now)
date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now)
date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
...@@ -282,3 +287,80 @@ class Command(AbstractSessionCommand): ...@@ -282,3 +287,80 @@ class Command(AbstractSessionCommand):
class Meta: class Meta:
db_table = "terminal_command" db_table = "terminal_command"
ordering = ('-timestamp',) 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 -*- # -*- 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 rest_framework import serializers
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Terminal, Status, Session, Task from ..models import (
Terminal, Status, Session, Task
)
class TerminalSerializer(serializers.ModelSerializer): class TerminalSerializer(serializers.ModelSerializer):
...@@ -25,21 +24,6 @@ class TerminalSerializer(serializers.ModelSerializer): ...@@ -25,21 +24,6 @@ class TerminalSerializer(serializers.ModelSerializer):
return Session.objects.filter(terminal=obj, is_finished=False).count() 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 StatusSerializer(serializers.ModelSerializer):
class Meta: class Meta:
fields = ['id', 'terminal'] fields = ['id', 'terminal']
...@@ -52,9 +36,3 @@ class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -52,9 +36,3 @@ class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
model = Task model = Task
list_serializer_class = AdaptedBulkListSerializer 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 @@ ...@@ -5,8 +5,6 @@
{% load common_tags %} {% load common_tags %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/datepicker/datepicker3.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>
{% endblock %} {% endblock %}
{% block content_left_head %} {% block content_left_head %}
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
{% block table_search %}{% endblock %} {% block table_search %}{% endblock %}
{% block table_container %} {% 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" > <table class="table table-striped table-bordered table-hover " id="terminal_list_table" >
<thead> <thead>
<tr> <tr>
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
{% load static %} {% load static %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% 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"> <link href="{% static "css/plugins/datepicker/datepicker3.css" %}" rel="stylesheet">
{% endblock %} {% endblock %}
......
...@@ -18,6 +18,8 @@ router.register(r'terminals', api.TerminalViewSet, 'terminal') ...@@ -18,6 +18,8 @@ router.register(r'terminals', api.TerminalViewSet, 'terminal')
router.register(r'tasks', api.TaskViewSet, 'tasks') router.register(r'tasks', api.TaskViewSet, 'tasks')
router.register(r'commands', api.CommandViewSet, 'command') router.register(r'commands', api.CommandViewSet, 'command')
router.register(r'status', api.StatusViewSet, 'status') 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 = [ urlpatterns = [
path('sessions/<uuid:pk>/replay/', path('sessions/<uuid:pk>/replay/',
...@@ -27,7 +29,9 @@ urlpatterns = [ ...@@ -27,7 +29,9 @@ urlpatterns = [
path('terminals/<uuid:terminal>/access-key/', api.TerminalTokenApi.as_view(), path('terminals/<uuid:terminal>/access-key/', api.TerminalTokenApi.as_view(),
name='terminal-access-key'), name='terminal-access-key'),
path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'), 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 # v2: get session's replay
# path('v2/sessions/<uuid:pk>/replay/', # path('v2/sessions/<uuid:pk>/replay/',
# api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}), # api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
......
...@@ -26,4 +26,14 @@ urlpatterns = [ ...@@ -26,4 +26,14 @@ urlpatterns = [
# Command view # Command view
path('command/', views.CommandListView.as_view(), name='command-list'), 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 @@ ...@@ -3,3 +3,7 @@
from .terminal import * from .terminal import *
from .session import * from .session import *
from .command 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): ...@@ -32,8 +32,6 @@ class TicketSerializer(serializers.ModelSerializer):
if action and user not in instance.assignees.all(): if action and user not in instance.assignees.all():
error = {"action": "Only assignees can update"} error = {"action": "Only assignees can update"}
raise serializers.ValidationError(error) raise serializers.ValidationError(error)
print(validated_data)
print(instance.status)
if instance.status == instance.STATUS_CLOSED: if instance.status == instance.STATUS_CLOSED:
validated_data.pop('action') validated_data.pop('action')
instance = super().update(instance, validated_data) instance = super().update(instance, validated_data)
......
...@@ -19,8 +19,8 @@ class UserUserGroupRelationViewSet(BulkModelViewSet): ...@@ -19,8 +19,8 @@ class UserUserGroupRelationViewSet(BulkModelViewSet):
def get_queryset(self): def get_queryset(self):
queryset = User.groups.through.objects.all()\ queryset = User.groups.through.objects.all()\
.annotate(user_name=F('user__name'))\ .annotate(user_display=F('user__name'))\
.annotate(usergroup_name=F('usergroup__name')) .annotate(usergroup_display=F('usergroup__name'))
return queryset return queryset
def allow_bulk_destroy(self, qs, filtered): def allow_bulk_destroy(self, qs, filtered):
......
...@@ -384,7 +384,7 @@ class MFAMixin: ...@@ -384,7 +384,7 @@ class MFAMixin:
@staticmethod @staticmethod
def mfa_is_otp(): def mfa_is_otp():
if settings.CONFIG.OTP_IN_RADIUS: if settings.OTP_IN_RADIUS:
return False return False
return True return True
...@@ -401,7 +401,7 @@ class MFAMixin: ...@@ -401,7 +401,7 @@ class MFAMixin:
return check_otp_code(self.otp_secret_key, code) return check_otp_code(self.otp_secret_key, code)
def check_mfa(self, code): def check_mfa(self, code):
if settings.CONFIG.OTP_IN_RADIUS: if settings.OTP_IN_RADIUS:
return self.check_radius(code) return self.check_radius(code)
else: else:
return self.check_otp(code) return self.check_otp(code)
...@@ -540,6 +540,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): ...@@ -540,6 +540,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
return True return True
return False return False
def set_avatar(self, f):
self.avatar.save(self.username, f)
def avatar_url(self): def avatar_url(self):
admin_default = settings.STATIC_URL + "img/avatar/admin.png" admin_default = settings.STATIC_URL + "img/avatar/admin.png"
user_default = settings.STATIC_URL + "img/avatar/user.png" user_default = settings.STATIC_URL + "img/avatar/user.png"
......
...@@ -8,9 +8,11 @@ __all__ = ['UserUserGroupRelationSerializer'] ...@@ -8,9 +8,11 @@ __all__ = ['UserUserGroupRelationSerializer']
class UserUserGroupRelationSerializer(serializers.ModelSerializer): class UserUserGroupRelationSerializer(serializers.ModelSerializer):
user_name = serializers.CharField(read_only=True) user_display = serializers.CharField(read_only=True)
usergroup_name = serializers.CharField(read_only=True) usergroup_display = serializers.CharField(read_only=True)
class Meta: class Meta:
model = User.groups.through 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 @@ ...@@ -3,9 +3,7 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.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> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
...@@ -21,6 +19,9 @@ ...@@ -21,6 +19,9 @@
<li> <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> <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>
<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"> <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> <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> </li>
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
<li class="active"> <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> <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>
<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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load bootstrap3 %} {% 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 %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
......
...@@ -3,13 +3,8 @@ ...@@ -3,13 +3,8 @@
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.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/sweetalert/sweetalert.min.js" %}"></script>
<script src="{% static "js/plugins/datatables/datatables.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
...@@ -98,7 +93,7 @@ ...@@ -98,7 +93,7 @@
<tr> <tr>
<td ><b class="bdg_user" data-uid={{ user.id }}>{{ user.name }}</b></td> <td ><b class="bdg_user" data-uid={{ user.id }}>{{ user.name }}</b></td>
<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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -132,7 +127,7 @@ function updateGroupMember(users) { ...@@ -132,7 +127,7 @@ function updateGroupMember(users) {
$('.user_edit tbody').append( $('.user_edit tbody').append(
'<tr>' + '<tr>' +
'<td><b class="bdg_user" data-uid="' + index + '">' + user_name + '</b></td>' + '<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>' '</tr>'
) )
}); });
...@@ -156,7 +151,7 @@ $(document).ready(function () { ...@@ -156,7 +151,7 @@ $(document).ready(function () {
delete jumpserver.users_selected[data.id] delete jumpserver.users_selected[data.id]
}); });
usersSelect2Init('#slct_users') usersSelect2Init('#slct_users')
}).on('click', '.btn_remove_user', function() { }).on('click', '.btn-remove-user', function() {
var $this = $(this); var $this = $(this);
var $tr = $this.closest('tr'); var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_user'); var $badge = $tr.find('.bdg_user');
......
...@@ -35,6 +35,7 @@ urlpatterns = [ ...@@ -35,6 +35,7 @@ urlpatterns = [
path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'), 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>/', views.UserDetailView.as_view(), name='user-detail'),
path('user/<uuid:pk>/assets/', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), 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'), path('user/<uuid:pk>/login-history/', views.UserDetailView.as_view(), name='user-login-history'),
# User group view # User group view
......
...@@ -2,4 +2,5 @@ ...@@ -2,4 +2,5 @@
from .login import * from .login import *
from .user import * from .user import *
from .profile import *
from .group 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 ...@@ -4,48 +4,35 @@ from __future__ import unicode_literals
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache from django.core.cache import cache
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import redirect 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.utils.translation import ugettext as _
from django.views import View
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic.edit import ( from django.views.generic.edit import (
CreateView, UpdateView, FormView CreateView, UpdateView
) )
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.contrib.auth import logout as auth_logout
from common.const import ( from common.const import (
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID 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 ( from common.permissions import (
PermissionsMixin, IsOrgAdmin, IsValidUser, PermissionsMixin, IsOrgAdmin,
UserCanUpdatePassword, UserCanUpdateSSHKey,
CanUpdateDeleteUser, CanUpdateDeleteUser,
) )
from orgs.utils import current_org from orgs.utils import current_org
from .. import forms from .. import forms
from ..models import User, UserGroup from ..models import User, UserGroup
from ..utils import generate_otp_uri, check_otp_code, \ from ..utils import get_password_check_rules, is_need_unblock
get_user_or_tmp_user, get_password_check_rules, check_password_rules, \
is_need_unblock
from ..signals import post_user_create from ..signals import post_user_create
__all__ = [ __all__ = [
'UserListView', 'UserCreateView', 'UserDetailView', 'UserListView', 'UserCreateView', 'UserDetailView',
'UserUpdateView', 'UserGrantedAssetView', 'UserProfileView', 'UserUpdateView', 'UserGrantedAssetView',
'UserProfileUpdateView', 'UserPasswordUpdateView', 'UserBulkUpdateView', 'UserAssetPermissionListView',
'UserPublicKeyUpdateView', 'UserBulkUpdateView',
'UserPublicKeyGenerateView',
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
'UserOtpDisableAuthenticationView', 'UserOtpUpdateView'
] ]
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -221,239 +208,15 @@ class UserGrantedAssetView(PermissionsMixin, DetailView): ...@@ -221,239 +208,15 @@ class UserGrantedAssetView(PermissionsMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserProfileView(PermissionsMixin, TemplateView): class UserAssetPermissionListView(PermissionsMixin, DetailView):
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 model = User
form_class = forms.UserPasswordForm template_name = 'users/user_asset_permission.html'
success_url = reverse_lazy('users:user-profile') permission_classes = [IsOrgAdmin]
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): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('Public key update'), 'action': _('Asset permission'),
}
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
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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 ...@@ -19,28 +19,23 @@ from daemon import pidfile
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE_DIR) sys.path.insert(0, BASE_DIR)
logging.basicConfig(level=logging.DEBUG)
try: try:
from apps.jumpserver import const from apps.jumpserver import const
__version__ = const.VERSION __version__ = const.VERSION
except ImportError as e: except ImportError as e:
logging.info("Not found __version__: {}".format(e)) print("Not found __version__: {}".format(e))
logging.info("Sys path: {}".format(sys.path)) print("Python is: ")
logging.info("Python is: ")
logging.info(subprocess.call('which python', shell=True)) logging.info(subprocess.call('which python', shell=True))
__version__ = 'Unknown' __version__ = 'Unknown'
try: sys.exit(1)
import apps
logging.info("List apps: {}".format(os.listdir('apps')))
logging.info('apps is: {}'.format(apps))
except:
pass
try: try:
from apps.jumpserver.conf import load_user_config from apps.jumpserver.const import CONFIG
CONFIG = load_user_config()
except ImportError as e: except ImportError as e:
logging.info("Import error: {}".format(e)) print("Import error: {}".format(e))
logging.info("Could not find config file, `cp config_example.yml config.yml`") print("Could not find config file, `cp config_example.yml config.yml`")
sys.exit(1) sys.exit(1)
os.environ["PYTHONIOENCODING"] = "UTF-8" os.environ["PYTHONIOENCODING"] = "UTF-8"
...@@ -445,7 +440,10 @@ def stop_service(srv, sig=15): ...@@ -445,7 +440,10 @@ def stop_service(srv, sig=15):
if process is None: if process is None:
print("\033[31m No process found\033[0m") print("\033[31m No process found\033[0m")
continue continue
process.wait(1) try:
process.wait(1)
except:
pass
for i in range(STOP_TIMEOUT): for i in range(STOP_TIMEOUT):
if i == STOP_TIMEOUT - 1: if i == STOP_TIMEOUT - 1:
print("\033[31m Error\033[0m") 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