Commit d5cb0a2e authored by ibuler's avatar ibuler

[Update] Merge with dev

parents 792fc3b0 f802fb64
......@@ -7,6 +7,7 @@ from rest_framework import filters
from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404
from django.http import Http404
from django.conf import settings
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger
......@@ -110,12 +111,22 @@ class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
class AssetUserExportViewSet(AssetUserViewSet):
serializer_class = serializers.AssetUserExportSerializer
http_method_names = ['get']
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
def get_object(self):
query_params = self.request.query_params
......
......@@ -41,8 +41,8 @@ class AssetUserManager:
instances_map = {}
instances = []
for name, backend in self.backends:
if name != "db" and self._prefer != name:
continue
# if name != "db":
# continue
_instances = backend.filter(
username=username, assets=assets, latest=latest,
prefer=self._prefer, prefer_id=prefer_id,
......
......@@ -53,7 +53,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend):
return queryset
if node is None:
return queryset.none()
return queryset
query_all = self.is_query_all(request)
if query_all:
pattern = node.get_all_children_pattern(with_self=True)
......
......@@ -120,6 +120,10 @@ class SystemUser(AssetUser):
def __str__(self):
return '{0.name}({0.username})'.format(self)
@property
def nodes_amount(self):
return self.nodes.all().count()
@property
def login_mode_display(self):
return self.get_login_mode_display()
......
......@@ -26,13 +26,14 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
fields = [
'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
'assets_amount', 'auto_generate_key'
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment',
'assets_amount', 'nodes_amount', 'auto_generate_key'
]
extra_kwargs = {
'password': {"write_only": True},
'public_key': {"write_only": True},
'private_key': {"write_only": True},
'nodes_amount': {'label': _('Node')},
'assets_amount': {'label': _('Asset')},
'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True},
......
......@@ -40,6 +40,7 @@ var prefer = null;
var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}";
var testDatetime = "{% trans 'Test datetime: ' %}";
var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}";
var mfaNeedCheck = "{{ SECURITY_VIEW_AUTH_NEED_MFA }}" === "True";
function initAssetUserTable() {
var options = {
......@@ -112,6 +113,10 @@ $(document).ready(function(){
authAssetId = $(this).data("asset") ;
authHostname = $(this).data("hostname");
authUsername = $(this).data('user');
if (!mfaNeedCheck){
$("#asset_user_auth_view").modal('show');
return
}
var now = new Date();
var nowTime = now.getTime() / 1000;
if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) {
......
......@@ -70,43 +70,6 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}},
{#{targets: 4, createdCell: function (td, cellData) {#}
{# var innerHtml = "";#}
{# var data = cellData.reachable;#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html(innerHtml)#}
{#}},#}
{#{targets: 5, createdCell: function (td, cellData) {#}
{# var data = cellData.unreachable;#}
{# var innerHtml = "";#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
{#}},#}
{#{targets: 6, createdCell: function (td, cellData, rowData) {#}
{# var val = 0;#}
{# var innerHtml = "";#}
{# var total = rowData.assets_amount;#}
{# var reachable = cellData.reachable;#}
{# if (total !== 0) {#}
{# val = reachable/total * 100;#}
{# }#}
{##}
{# if (val === 100) {#}
{# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
{# } else {#}
{# var num = new Number(val);#}
{# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
{#}},#}
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
......@@ -116,7 +79,7 @@ function initTable() {
columns: [
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount", orderable: false},
{#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#}
{data: "comment"}, {data: "id", orderable: false}
{data: "comment"}, {data: "id", orderable: false, width: "100px"}
]
};
admin_user_table = jumpserver.initServerSideDataTable(options);
......
......@@ -177,7 +177,7 @@ function initTable() {
data: "connectivity",
orderable: false,
width: '60px'
}, {data: "id", orderable: false}
}, {data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......@@ -292,7 +292,8 @@ $(document).ready(function(){
format: 'csv',
params: {
search: search,
node_id: current_node_id || ''
node_id: current_node_id || '',
show_current_asset: getCookie('show_current_asset')
}
};
APIExportData(props);
......
......@@ -144,7 +144,9 @@ function updateCMDFilterSystemUsers(system_users) {
});
}
$(document).ready(function () {
$(".select2").select2();
$(".select2").select2({
closeOnSelect: false
});
}).on('click', '#btn-binding-system-users', function () {
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
return $(s).data('gid')
......
......@@ -63,7 +63,8 @@ function initTable() {
ajax_url: '{% url "api-assets:cmd-filter-list" %}',
columns: [
{data: "id"}, {data: "name" }, {data: "rules", orderable: false},
{data: "system_users", orderable: false}, {data: "comment"}, {data: "id", orderable: false}
{data: "system_users", orderable: false}, {data: "comment"},
{data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......
......@@ -59,7 +59,7 @@ function initTable() {
ajax_url: '{% url "api-assets:domain-list" %}',
columns: [
{data: "id"}, {data: "name" }, {data: "asset_count", orderable: false },
{data: "gateway_count", orderable: false }, {data: "comment" }, {data: "id", orderable: false}
{data: "gateway_count", orderable: false }, {data: "comment" }, {data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......
......@@ -44,7 +44,8 @@ function initTable() {
ajax_url: '{% url "api-assets:label-list" %}?sort=name',
columns: [
{data: "id"}, {data: "name" }, {data: "value" },
{data: "asset_count", orderable: false}, {data: "id", orderable: false}
{data: "asset_count", orderable: false},
{data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......
......@@ -3,10 +3,6 @@
{% block help_message %}
<div class="alert alert-info help-message">
{# 系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);#}
{# 简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。#}
{# 系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。#}
{# 目前还不支持Windows的自动推送#}
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
{% trans 'When system users are created, if you choose auto push Jumpserver to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %}
......@@ -91,7 +87,7 @@ function initTable() {
columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"},
{data: "login_mode"}, {data: "assets_amount", orderable: false },
{data: "comment" }, {data: "id", orderable: false }
{data: "comment" }, {data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......
......@@ -104,6 +104,7 @@ $(document).ready(function () {
body: JSON.stringify({asset: assetId}),
flash_message: false,
success: function (data) {
favoriteAssets.push(assetId);
var btn = disfavorBtnTmpl.replace("ID", assetId);
$this.replaceWith(btn)
}
......@@ -117,6 +118,10 @@ $(document).ready(function () {
method: "DELETE",
flash_message: false,
success: function (data) {
var index = favoriteAssets.indexOf(assetId);
if (index !== '-1'){
favoriteAssets.splice(index, 1);
}
var btn = favorBtnTmpl.replace("ID", assetId);
$this.replaceWith(btn)
}
......
......@@ -88,11 +88,13 @@ class UserLoginLog(models.Model):
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
@classmethod
def get_login_logs(cls, date_form=None, date_to=None, user=None, keyword=None):
def get_login_logs(cls, date_from=None, date_to=None, user=None, keyword=None):
login_logs = cls.objects.all()
if date_form and date_to:
if date_from and date_to:
date_from = "{} {}".format(date_from, '00:00:00')
date_to = "{} {}".format(date_to, '23:59:59')
login_logs = login_logs.filter(
datetime__gt=date_form, datetime__lt=date_to
datetime__gte=date_from, datetime__lte=date_to
)
if user:
login_logs = login_logs.filter(username=user)
......
......@@ -121,7 +121,7 @@
});
})
.on('click', '.btn_export', function () {
var date_form = $('#id_date_from').val();
var date_from = $('#id_date_from').val();
var date_to = $('#id_date_to').val();
var user = $('.select2 option:selected').val();
var keyword = $('#search').val();
......@@ -129,7 +129,7 @@
url: "{% url "audits:login-log-export" %}",
method: 'POST',
data: JSON.stringify({
'date_form':date_form,
'date_from':date_from,
'date_to':date_to,
'user':user,
'keyword':keyword
......
......@@ -267,19 +267,22 @@ class LoginLogExportView(PermissionsMixin, View):
header = [field.verbose_name for field in fields]
login_logs = cache.get(request.GET.get('spm', ''), [])
response = write_content_to_excel(excel_response, login_logs=login_logs,
header=header, fields=fields)
response = write_content_to_excel(
excel_response, login_logs=login_logs, header=header, fields=fields
)
return response
def post(self, request):
try:
date_form = json.loads(request.body).get('date_form', [])
date_from = json.loads(request.body).get('date_from', [])
date_to = json.loads(request.body).get('date_to', [])
user = json.loads(request.body).get('user', [])
keyword = json.loads(request.body).get('keyword', [])
login_logs = UserLoginLog.get_login_logs(
date_form=date_form, date_to=date_to, user=user, keyword=keyword)
date_from=date_from, date_to=date_to, user=user,
keyword=keyword,
)
except ValueError:
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().hex
......
......@@ -63,7 +63,6 @@ class IDSpmFilter(filters.BaseFilterBackend):
cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key)
if not resources_id or not isinstance(resources_id, list):
queryset = queryset.none()
return queryset
queryset = queryset.filter(id__in=resources_id)
return queryset
......
......@@ -51,7 +51,7 @@ class TreeNode:
result = True
elif self.pId != other.pId:
result = self.pId > other.pId
elif self.id.startswith('-') and not other.id.startswith('-'):
elif str(self.id).startswith('-') and not str(other.id).startswith('-'):
result = False
else:
result = self.name > other.name
......
......@@ -182,3 +182,7 @@ def encrypt_password(password, salt=None):
def get_signer():
signer = Signer(settings.SECRET_KEY)
return signer
def ensure_last_char_is_ascii(data):
remain = ''
......@@ -361,6 +361,7 @@ defaults = {
'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,
......
......@@ -19,6 +19,7 @@ def jumpserver_processor(request):
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME,
'SECURITY_VIEW_AUTH_NEED_MFA': settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA,
}
return context
......
This diff is collapsed.
......@@ -4,16 +4,39 @@
<title>{% trans 'Task log' %}</title>
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<style>
body {
background-color: black;
}
.xterm-rows {
{#padding: 15px;#}
font-family: "Bitstream Vera Sans Mono", Monaco, "Consolas", Courier, monospace;
font-size: 13px;
}
.terminal .xterm-viewport {
background-color: #1f1b1b;
overflow: auto;
}
body ::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3);
background-color: #272323;
border-radius: 6px;
}
body ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
body ::-webkit-scrollbar-thumb {
background-color: #494141;
border-radius: 6px;
}
</style>
</head>
<div id="term" style="height: 100%;width: 100%">
......@@ -35,12 +58,13 @@
cursorBlink: false,
screenKeys: false,
fontFamily: '"Monaco", "Consolas", "monospace"',
fontSize: 12,
fontSize: 13,
lineHeight: 1.2,
rightClickSelectsWord: true,
disableStdin: true
});
term.open(document.getElementById('term'));
term.resize(120, 30);
window.fit.fit(term);
ws = new WebSocket(wsURL);
ws.onmessage = function(e) {
......@@ -61,5 +85,7 @@
term.write("Connect websocket server error")
}
}
}).on('resize', window, function () {
window.fit.fit(term);
});
</script>
......@@ -34,6 +34,27 @@
.select2-container .select2-selection--single {
height: 34px;
}
.terminal .xterm-viewport {
background-color: #1f1b1b;
overflow: auto;
}
body ::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3);
background-color: #272323;
border-radius: 6px;
}
body ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
body ::-webkit-scrollbar-thumb {
background-color: #494141;
border-radius: 6px;
}
</style>
{% endblock %}
......@@ -80,9 +101,7 @@
<select class="select2 form-control"
id="system-users-select">
{% for s in system_users %}
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %}
<option value="{{ s.id }}">{{ s }}</option>
{% endif %}
<option value="{{ s.id }}" {% if s.protocol != 'ssh' or s.login_mode != 'auto' %}disabled{% endif %}>{{ s }}</option>
{% endfor %}
</select>
<button type="button"
......@@ -104,41 +123,6 @@
var url = null;
var treeUrl = "{% url 'api-perms:my-nodes-with-assets-as-tree' %}?cache_policy=1";
function proposeGeometry(term) {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
var elementStyle = window.getComputedStyle(term.element);
var elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
var elementPaddingHor = elementPadding.right + elementPadding.left;
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
var geometry = {
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
};
return geometry;
}
function fit(term) {
var geometry = proposeGeometry(term);
if (geometry) {
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
term._core.renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}
function initTree() {
$('#assetTree').html("{% trans 'Loading' %}" + '..');
if (systemUserId) {
......@@ -159,12 +143,6 @@
enable: true
}
},
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: {
enable: true,
showRemoveBtn: false,
......@@ -244,17 +222,18 @@
cursorBlink: false,
screenKeys: false,
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
fontSize: 14,
lineHeight: 1,
fontSize: 13,
rightClickSelectsWord: true,
disableStdin: true,
lineHeight: 1.2,
theme: {
background: '#1f1b1b'
}
});
term.open(document.getElementById('term'));
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
fit(term);
window.fit.fit(term);
{#fit(term);#}
term.write(msg);
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
......
......@@ -70,7 +70,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
user = self.request.user
with tmp_to_root_org():
util = AssetPermissionUtilV2(user)
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh']
system_users = util.get_system_users()
return system_users
def get_context_data(self, **kwargs):
......
......@@ -3,11 +3,13 @@ import os
import threading
import json
from celery.result import AsyncResult
from common.utils import get_logger
from .celery.utils import get_celery_task_log_path
from channels.generic.websocket import JsonWebsocketConsumer
logger = get_logger(__name__)
class CeleryLogWebsocket(JsonWebsocketConsumer):
disconnected = False
......@@ -21,12 +23,8 @@ class CeleryLogWebsocket(JsonWebsocketConsumer):
if task_id:
self.handle_task(task_id)
def handle_task(self, task_id):
def wait_util_log_path_exist(self, task_id):
log_path = get_celery_task_log_path(task_id)
def func():
task_log_f = None
while not self.disconnected:
if not os.path.exists(log_path):
self.send_json({'message': '.', 'task': task_id})
......@@ -34,21 +32,39 @@ class CeleryLogWebsocket(JsonWebsocketConsumer):
continue
self.send_json({'message': '\r\n'})
try:
task_log_f = open(log_path)
break
logger.debug('Task log path: {}'.format(log_path))
task_log_f = open(log_path, 'rb')
return task_log_f
except OSError:
return None
def read_log_file(self, task_id):
task_log_f = self.wait_util_log_path_exist(task_id)
if not task_log_f:
logger.debug('Task log file is None: {}'.format(task_id))
return
task_end_mark = []
while not self.disconnected:
data = task_log_f.readline()
data = task_log_f.read(4096)
if data:
data = data.replace('\n', '\r\n')
self.send_json({'message': data, 'task': task_id})
if data.startswith('Task') and data.find('succeeded'):
data = data.replace(b'\n', b'\r\n')
self.send_json(
{'message': data.decode(errors='ignore'), 'task': task_id}
)
if data.find(b'succeeded in') != -1:
task_end_mark.append(1)
if data.find(bytes(task_id, 'utf8')) != -1:
task_end_mark.append(1)
elif len(task_end_mark) == 2:
logger.debug('Task log end: {}'.format(task_id))
break
time.sleep(0.2)
task_log_f.close()
thread = threading.Thread(target=func)
def handle_task(self, task_id):
logger.info("Task id: {}".format(task_id))
thread = threading.Thread(target=self.read_log_file, args=(task_id,))
thread.start()
def disconnect(self, close_code):
......
......@@ -73,14 +73,17 @@ class AssetPermissionViewSet(OrgModelViewSet):
node_id = self.request.query_params.get('node_id')
node_name = self.request.query_params.get('node')
if node_id:
node = get_object_or_none(Node, pk=node_id)
_nodes = Node.objects.filter(pk=node_id)
elif node_name:
node = get_object_or_none(Node, name=node_name)
_nodes = Node.objects.filter(value=node_name)
else:
return queryset
if not node:
if not _nodes:
return queryset.none()
nodes = node.get_ancestors(with_self=True)
nodes = set()
for node in _nodes:
nodes |= set(node.get_ancestors(with_self=True))
queryset = queryset.filter(nodes__in=nodes)
return queryset
......
......@@ -29,7 +29,7 @@ class BasePermissionQuerySet(models.QuerySet):
return self.filter(is_active=False)
def invalid(self):
now = timezone.now
now = timezone.now()
q = (
Q(is_active=False) |
Q(date_start__gt=now) |
......
......@@ -190,7 +190,7 @@ function initTable() {
{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}
{data: "is_valid", orderable: false}, {data: "id", orderable: false, width: "100px"}
],
select: {},
op_html: $('#actions').html()
......
......@@ -62,10 +62,10 @@ asset_permission_urlpatterns = [
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>/user/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'),
path('asset-permissions/<uuid:pk>/user/add/', api.AssetPermissionAddUserApi.as_view(), name='asset-permission-add-user'),
path('asset-permissions/<uuid:pk>/asset/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'),
path('asset-permissions/<uuid:pk>/asset/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'),
path('asset-permissions/<uuid:pk>/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'),
......@@ -75,7 +75,7 @@ asset_permission_urlpatterns = [
path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
# 刷新缓存
path('asset-permissions/user/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
path('asset-permissions/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
]
......@@ -99,10 +99,10 @@ remote_app_permission_urlpatterns = [
path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'),
# 用户和RemoteApp变更
path('remote-app-permissions/<uuid:pk>/user/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'),
path('remote-app-permissions/<uuid:pk>/user/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'),
path('remote-app-permissions/<uuid:pk>/remote-app/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'),
path('remote-app-permissions/<uuid:pk>/remote-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
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 = [
......
......@@ -234,6 +234,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
if user_tree.contains(key):
nodes_single_assets.pop(key)
if not nodes_single_assets:
return
# 如果要设置到ungroup中
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
node_key = Node.ungrouped_key
......@@ -336,8 +339,8 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.add_direct_nodes_to_user_tree(user_tree)
self.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
self.add_favorite_node_if_need(user_tree)
self.add_empty_node_if_need(user_tree)
self.set_user_tree_to_cache_if_need(user_tree)
self.set_user_tree_to_local(user_tree)
return user_tree
......@@ -487,6 +490,7 @@ class ParserNode:
'ip': asset.ip,
'protocols': asset.protocols_as_list,
'platform': asset.platform,
"org_name": asset.org_name,
},
}
}
......
......@@ -5,6 +5,7 @@ import os
import json
import jms_storage
from smtplib import SMTPSenderRefused
from rest_framework import generics
from rest_framework.views import Response, APIView
from django.conf import settings
......@@ -41,9 +42,20 @@ class MailTestingAPI(APIView):
email_from = email_from or email_host_user
email_recipient = email_recipient or email_from
send_mail(subject, message, email_from, [email_recipient])
except SMTPSenderRefused as e:
resp = e.smtp_error
if isinstance(resp, bytes):
for coding in ('gbk', 'utf8'):
try:
resp = resp.decode(coding)
except UnicodeDecodeError:
continue
else:
break
return Response({"error": str(resp)}, status=401)
except Exception as e:
print(e)
return Response({"error": str(e)}, status=401)
return Response({"msg": self.success_message.format(email_recipient)})
else:
return Response({"error": str(serializer.errors)}, status=401)
......
apps/static/img/facio.ico

1.7 KB | W: | H:

apps/static/img/facio.ico

3.21 KB | W: | H:

apps/static/img/facio.ico
apps/static/img/facio.ico
apps/static/img/facio.ico
apps/static/img/facio.ico
  • 2-up
  • Swipe
  • Onion skin
......@@ -206,7 +206,7 @@ function formSubmit(props) {
var errors = jqXHR.responseJSON;
var noneFieldErrorRef = props.form.children('.alert-danger');
if (noneFieldErrorRef.length !== 1) {
props.form.prepend('<div class="alert alert-danger" style="display: none"></div>');
props.form.prepend('<div class="alert alert-danger has-error" style="display: none"></div>');
noneFieldErrorRef = props.form.children('.alert-danger');
}
var noneFieldErrorMsg = "";
......@@ -252,6 +252,7 @@ function formSubmit(props) {
noneFieldErrorRef.css('display', 'block');
noneFieldErrorRef.html(noneFieldErrorMsg);
}
$('.has-error').get(0).scrollIntoView();
}
})
......@@ -458,6 +459,7 @@ jumpserver.initDataTable = function (options) {
{
targets: 0,
orderable: false,
width: "20px",
createdCell: function (td, cellData) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
}
......@@ -555,6 +557,7 @@ jumpserver.initServerSideDataTable = function (options) {
{
targets: 0,
orderable: false,
width: "20px",
createdCell: function (td, cellData) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
}
......
......@@ -84,9 +84,6 @@
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add user' %}" id="slct_users" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for user in users %}
<option value="{{ user.id }}" id="opt_{{ user.id }}">{{ user.name }}</option>
{% endfor %}
</select>
</td>
</tr>
......@@ -157,7 +154,8 @@ $(document).ready(function () {
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.users_selected[data.id]
})
});
usersSelect2Init('#slct_users')
}).on('click', '.btn_remove_user', function() {
var $this = $(this);
var $tr = $this.closest('tr');
......
......@@ -82,7 +82,7 @@ function initTable() {
],
ajax_url: '{% url "api-users:user-group-list" %}?display=1',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "users", orderable: false},
{data: "comment"}, {data: "id", orderable: false }],
{data: "comment"}, {data: "id", orderable: false, width:"100px"}],
order: [],
op_html: $('#actions').html()
};
......
......@@ -125,7 +125,7 @@ function initTable() {
{data: "groups_display", orderable: false},
{data: "source"},
{data: "is_valid", orderable: false},
{data: "id", orderable: false}
{data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......
......@@ -22,21 +22,20 @@ logger = logging.getLogger('jumpserver')
def construct_user_created_email_body(user):
default_body = _("""
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<p style="text-indent:2em;">
<span>
Username: %(username)s.
</span>
<span>
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a>
</span>
<span>
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
</span>
<span>
<div>
<p>Your account has been created successfully</p>
<div>
Username: %(username)s
<br/>
Password: <a href="%(rest_password_url)s?token=%(rest_password_token)s">
click here to set your password</a>
(This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>)
</div>
<div>
<p>---</p>
<a href="%(login_url)s">Login direct</a>
</span>
</p>
</div>
</div>
""") % {
'username': user.username,
'rest_password_url': reverse('users:reset-password', external=True),
......
......@@ -65,6 +65,7 @@ logging.basicConfig(
EXIT_EVENT = threading.Event()
LOCK = threading.Lock()
files_preserve = []
STOP_TIMEOUT = 10
logger = logging.getLogger()
......@@ -120,7 +121,7 @@ def check_pid(pid):
def get_pid_file_path(s):
return os.path.join('/tmp', '{}.pid'.format(s))
return os.path.join(TMP_DIR, '{}.pid'.format(s))
def get_log_file_path(s):
......@@ -433,9 +434,19 @@ def stop_service(srv, sig=15):
if not is_running(s):
show_service_status(s)
continue
logging.info("Stop service: {}".format(s))
print("Stop service: {}".format(s), end='')
pid = get_pid(s)
os.kill(pid, sig)
for i in range(STOP_TIMEOUT):
if i == STOP_TIMEOUT - 1:
print("\033[31m Error\033[0m")
if not is_running(s):
print("\033[32m Ok\033[0m")
break
else:
time.sleep(1)
continue
with LOCK:
processes.pop(s, None)
......@@ -472,9 +483,9 @@ def show_service_status(s):
for ns in services_set:
if is_running(ns):
pid = get_pid(ns)
logging.info("{} is running: {}".format(ns, pid))
print("{} is running: {}".format(ns, pid))
else:
logging.info("{} is stopped".format(ns))
print("{} is stopped".format(ns))
if __name__ == '__main__':
......@@ -499,6 +510,7 @@ if __name__ == '__main__':
)
parser.add_argument('-d', '--daemon', nargs="?", const=1)
parser.add_argument('-w', '--worker', type=int, nargs="?", const=4)
parser.add_argument('-f', '--force', nargs="?", const=1)
args = parser.parse_args()
if args.daemon:
DAEMON = True
......@@ -513,6 +525,9 @@ if __name__ == '__main__':
start_services_and_watch(srv)
os._exit(0)
elif action == "stop":
if args.force:
stop_service_force(srv)
else:
stop_service(srv)
elif action == "restart":
DAEMON = True
......
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