Unverified Commit 722bf786 authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #1542 from jumpserver/dev

Dev to master
parents 91b3b7ce 2cb5876d
......@@ -19,25 +19,25 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
----
### 功能
![Jumpserver功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver13.jpg "Jumpserver功能")
### 开始使用
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html)
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html)
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
### Demo 和 截图
### Demo 和 截图
我们提供了DEMO和截图可以让你快速了解Jumpserver
[DEMO](http://demo.jumpserver.org)
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK
### SDK
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
......
......@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
__version__ = "1.3.2"
__version__ = "1.3.3"
# -*- coding: utf-8 -*-
#
import random
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
......@@ -22,7 +24,8 @@ from ..utils import LabelFilter
logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi'
]
......@@ -106,3 +109,20 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
asset = get_object_or_404(Asset, pk=asset_id)
task = test_asset_connectability_manual.delay(asset)
return Response({"task": task.id})
class AssetGatewayApi(generics.RetrieveAPIView):
queryset = Asset.objects.all()
permission_classes = (IsSuperUserOrAppUser,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
if asset.domain and \
asset.domain.gateways.filter(protocol=asset.protocol).exists():
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
return Response(serializer.data)
else:
return Response({"msg": "Not have gateway"}, status=404)
\ No newline at end of file
......@@ -16,7 +16,7 @@ class AssetCreateForm(forms.ModelForm):
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain',
'domain', 'protocol',
]
widgets = {
......@@ -56,7 +56,7 @@ class AssetUpdateForm(forms.ModelForm):
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain',
'domain', 'protocol',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
......
......@@ -93,14 +93,21 @@ class SystemUserForm(PasswordAndKeyAuthForm):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super().save()
password = self.cleaned_data.get('password', '') or None
login_mode = self.cleaned_data.get('login_mode', '') or None
protocol = self.cleaned_data.get('protocol') or None
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
private_key, public_key = super().gen_keys()
if login_mode == SystemUser.MANUAL_LOGIN or protocol == SystemUser.TELNET_PROTOCOL:
system_user.auto_push = 0
system_user.save()
if auto_generate_key:
logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth()
else:
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
return system_user
def clean(self):
......@@ -109,12 +116,24 @@ class SystemUserForm(PasswordAndKeyAuthForm):
if not self.instance and not auto_generate:
super().validate_password_key()
def is_valid(self):
validated = super().is_valid()
username = self.cleaned_data.get('username')
login_mode = self.cleaned_data.get('login_mode')
if login_mode == SystemUser.AUTO_LOGIN and not username:
self.add_error(
"username", _('* Automatic login mode,'
' must fill in the username.')
)
return False
return validated
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo',
'comment', 'shell', 'priority',
'comment', 'shell', 'priority', 'login_mode',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
......@@ -124,5 +143,8 @@ class SystemUserForm(PasswordAndKeyAuthForm):
'name': '* required',
'username': '* required',
'auto_push': _('Auto push system user to asset'),
'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'),
}
\ No newline at end of file
'priority': _('High level will be using login asset as default, '
'if user was granted more than 2 system user'),
'login_mode': _('If you choose manual login mode, you do not '
'need to fill in the username and password.')
}
......@@ -57,13 +57,27 @@ class Asset(models.Model):
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
TELNET_PROTOCOL = 'telnet'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
(TELNET_PROTOCOL, 'telnet (beta)'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'),
db_index=True)
hostname = models.CharField(max_length=128, unique=True,
verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL,
choices=PROTOCOL_CHOICES,
verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES,
default='Linux', verbose_name=_('Platform'))
......
......@@ -19,7 +19,7 @@ signer = get_signer()
class AssetUser(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=32, verbose_name=_('Username'), validators=[alphanumeric])
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
......
......@@ -95,9 +95,18 @@ class AdminUser(AssetUser):
class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
TELNET_PROTOCOL = 'telnet'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
(TELNET_PROTOCOL, 'telnet (beta)'),
)
AUTO_LOGIN = 'auto'
MANUAL_LOGIN = 'manual'
LOGIN_MODE_CHOICES = (
(AUTO_LOGIN, _('Automatic login')),
(MANUAL_LOGIN, _('Manually login'))
)
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
......@@ -107,6 +116,7 @@ class SystemUser(AssetUser):
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode'))
def __str__(self):
return '{0.name}({0.username})'.format(self)
......
......@@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"is_active", "system_users_join", "os", 'domain',
"platform", "comment"
"platform", "comment", "protocol",
)
@staticmethod
......
......@@ -18,6 +18,13 @@ class SystemUserSerializer(serializers.ModelSerializer):
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.extend([
'get_login_mode_display',
])
return fields
@staticmethod
def get_unreachable_assets(obj):
return obj.unreachable_assets
......@@ -46,7 +53,7 @@ class SystemUserAuthSerializer(AuthSerializer):
model = SystemUser
fields = [
"id", "name", "username", "protocol",
"password", "private_key",
"login_mode", "password", "private_key",
]
......@@ -56,7 +63,10 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
class Meta:
model = SystemUser
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
fields = (
'id', 'name', 'username', 'priority',
'protocol', 'comment', 'login_mode'
)
class SystemUserSimpleSerializer(serializers.ModelSerializer):
......
......@@ -36,12 +36,13 @@
{% endif %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.login_mode layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
<h3 id="auth_title_id">{% trans 'Auth' %}</h3>
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
<div class="auto-generate">
<div class="form-group">
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
......@@ -80,15 +81,22 @@
{% endblock %}
{% block custom_foot_js %}
<script>
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var login_mode_id = '#' + '{{ form.login_mode.id_for_label }}';
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
var password_id = '#' + '{{ form.password.id_for_label }}';
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
var shell_id = '#' + '{{ form.shell.id_for_label }}';
var need_change_field = [
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
];
var need_change_field_login_mode = [
auto_generate_key, private_key_id, auto_push_id, password_id
];
function protocolChange() {
if ($(protocol_id + " option:selected").text() === 'rdp') {
......@@ -96,7 +104,19 @@ function protocolChange() {
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden')
});
} else {
}
else if ($(protocol_id + " option:selected").text() === 'telnet (beta)') {
$('.auth-fields').removeClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden')
});
}
else {
if($(login_mode_id).val() === 'manual'){
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
return
}
authFieldsDisplay();
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').removeClass('hidden')
......@@ -111,18 +131,35 @@ function authFieldsDisplay() {
$('.auth-fields').removeClass('hidden');
}
}
function loginModeChange(){
if ($(login_mode_id).val() === 'manual'){
$('#auth_title_id').addClass('hidden');
$.each(need_change_field_login_mode, function(index, value){
$(value).closest('.form-group').addClass('hidden')
})
}
else if($(login_mode_id).val() === 'auto'){
$('#auth_title_id').removeClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden')
protocolChange();
}
}
$(document).ready(function () {
$('.select2').select2();
authFieldsDisplay();
protocolChange();
loginModeChange();
})
.on('change', protocol_id, function(){
protocolChange();
})
.on('change', auto_generate_key, function(){
authFieldsDisplay();
});
})
.on('change', login_mode_id, function(){
loginModeChange();
})
</script>
{% endblock %}
\ No newline at end of file
......@@ -5,7 +5,7 @@
{% block help_message %}
<div class="alert alert-info help-message">
管理用户是服务器的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
Windows或其它硬件可以随意设置一个
</div>
{% endblock %}
......@@ -107,6 +107,3 @@ $(document).ready(function(){
});
</script>
{% endblock %}
......@@ -17,6 +17,7 @@
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
......@@ -85,14 +86,14 @@ $(document).ready(function () {
allowClear: true,
templateSelection: format
});
$("#id_platform").change(function (){
var platform = $("#id_platform option:selected").text();
$("#id_protocol").change(function (){
var protocol = $("#id_protocol option:selected").text();
var port = 22;
if(platform === 'Windows'){
if(protocol === 'rdp'){
port = 3389;
}
if(platform === 'Other'){
port = null;
if(protocol === 'telnet (beta)'){
port = 23;
}
$("#id_port").val(port);
});
......
......@@ -21,6 +21,7 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
......
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登
录。
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:domain-create' %}" class="btn btn-sm btn-primary"> {% trans "Create domain" %} </a>
......@@ -69,6 +77,3 @@ $(document).ready(function(){
});
</script>
{% endblock %}
......@@ -42,7 +42,7 @@
{% bootstrap_field form.domain layout="horizontal" %}
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
<h3 id="auth_title">{% trans 'Auth' %}</h3>
<div class="auth-fields">
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
......@@ -72,14 +72,23 @@
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
var port = '#' + '{{ form.port.id_for_label }}';
var username = '#' + '{{ form.username.id_for_label }}';
var password = '#' + '{{ form.password.id_for_label }}';
var auth_title = '#auth_title';
function protocolChange() {
if ($(protocol_id + " option:selected").text() === 'rdp') {
$(port).val(3389);
$(private_key_id).closest('.form-group').addClass('hidden')
{#$(port).val(3389);#}
$(private_key_id).closest('.form-group').addClass('hidden');
$(username).closest('.form-group').addClass('hidden');
$(password).closest('.form-group').addClass('hidden');
$(auth_title).addClass('hidden');
} else {
$(port).val(22);
$(private_key_id).closest('.form-group').removeClass('hidden')
{#$(port).val(22);#}
$(private_key_id).closest('.form-group').removeClass('hidden');
$(username).closest('.form-group').removeClass('hidden');
$(password).closest('.form-group').removeClass('hidden');
$(auth_title).removeClass('hidden');
}
}
......
......@@ -62,6 +62,10 @@
<td>{% trans 'Username' %}:</td>
<td><b>{{ system_user.username }}</b></td>
</tr>
<tr>
<td>{% trans 'Login mode' %}:</td>
<td><b>{{ system_user.get_login_mode_display }}</b></td>
</tr>
<tr>
<td>{% trans 'Protocol' %}:</td>
<td><b id="id_protocol_type">{{ system_user.protocol }}</b></td>
......@@ -148,15 +152,14 @@
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Clear auth' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-clear-auth" style="width: 54px">{% trans 'Clear' %}</button>
</span>
</td>
</tr>
{# <tr>#}
{# <td width="50%">{% trans 'Clear auth' %}:</td>#}
{# <td>#}
{# <span style="float: right">#}
{# <button type="button" class="btn btn-primary btn-xs btn-clear-auth" style="width: 54px">{% trans 'Clear' %}</button>#}
{# </span>#}
{# </td>#}
{# </tr>#}
{# <tr>#}
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
......@@ -333,10 +336,22 @@ $(document).ready(function () {
});
}).on('click', '.btn-clear-auth', function () {
var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}';
APIUpdateAttr({
url: the_url,
method: 'DELETE',
success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
var name = '{{ system_user.name }}';
swal({
title: '你确定清除该系统用户的认证信息吗 ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: '取消',
confirmButtonColor: "#ed5565",
confirmButtonText: '确认',
closeOnConfirm: true
}, function () {
APIUpdateAttr({
url: the_url,
method: 'DELETE',
success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
});
});
})
</script>
......
......@@ -26,6 +26,7 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Login mode' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
......@@ -48,7 +49,7 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
{targets: 6, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
......@@ -57,7 +58,7 @@ function initTable() {
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData) {
{targets: 7, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
......@@ -66,7 +67,7 @@ function initTable() {
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 7, createdCell: function (td, cellData, rowData) {
{targets: 8, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
......@@ -84,14 +85,14 @@ function initTable() {
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 9, createdCell: function (td, cellData, rowData) {
{targets: 10, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:system-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);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "assets_amount" },
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
],
op_html: $('#actions').html()
......
......@@ -4,7 +4,6 @@
{% load bootstrap3 %}
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
<div class="form-group">
......
......@@ -23,6 +23,8 @@ urlpatterns = [
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$',
api.AssetGatewayApi.as_view(), name='asset-gateway'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth/$',
......
......@@ -50,4 +50,3 @@ urlpatterns = [
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/create/$', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
url(r'^domain/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
]
......@@ -54,7 +54,8 @@ def test_gateway_connectability(gateway):
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
proxy.connect(gateway.ip, username=gateway.username,
proxy.connect(gateway.ip, gateway.port,
username=gateway.username,
password=gateway.password,
pkey=gateway.private_key_obj)
except(paramiko.AuthenticationException,
......
......@@ -140,11 +140,6 @@ class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView):
domain = self.object.domain
return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id})
def form_valid(self, form):
response = super().form_valid(form)
print(form.cleaned_data)
return response
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
......
......@@ -170,7 +170,7 @@ class TerminalSettingForm(BaseForm):
class SecuritySettingForm(BaseForm):
# MFA全局设置
# MFA global setting
SECURITY_MFA_AUTH = forms.BooleanField(
initial=False, required=False,
label=_("MFA Secondary certification"),
......@@ -179,12 +179,26 @@ class SecuritySettingForm(BaseForm):
'authentication (valid for all users, including administrators)'
)
)
# 最小长度
# limit login count
SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField(
initial=3, min_value=3,
label=_("Limit the number of login failures")
)
# limit login time
SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField(
initial=30, min_value=5,
label=_("No logon interval"),
help_text=_(
"Tip :(unit/minute) if the user has failed to log in for a limited "
"number of times, no login is allowed during this time interval."
)
)
# min length
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
initial=6, label=_("Password minimum length"),
min_value=6
)
# 大写字母
# upper case
SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField(
initial=False, required=False,
......@@ -193,21 +207,21 @@ class SecuritySettingForm(BaseForm):
'After opening, the user password changes '
'and resets must contain uppercase letters')
)
# 小写字母
# lower case
SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField(
initial=False, required=False,
label=_("Must contain lowercase letters"),
help_text=_('After opening, the user password changes '
'and resets must contain lowercase letters')
)
# 数字
# number
SECURITY_PASSWORD_NUMBER = forms.BooleanField(
initial=False, required=False,
label=_("Must contain numeric characters"),
help_text=_('After opening, the user password changes '
'and resets must contain numeric characters')
)
# 特殊字符
# special char
SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField(
initial=False, required=False,
label=_("Must contain special characters"),
......
......@@ -39,9 +39,9 @@
{% endif %}
{% csrf_token %}
<h3>{% trans "MFA setting" %}</h3>
<h3>{% trans "User login settings" %}</h3>
{% for field in form %}
{% if forloop.counter == 2 %}
{% if forloop.counter == 4 %}
<div class="hr-line-dashed"></div>
<h3>{% trans "Password check rule" %}</h3>
{% endif %}
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-07 11:34+0800\n"
"POT-Creation-Date: 2018-07-13 19:20+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -29,38 +29,38 @@ msgstr ""
msgid "测试节点下资产是否可连接: {}"
msgstr ""
#: assets/forms/asset.py:24 assets/models/asset.py:75 assets/models/user.py:103
#: assets/forms/asset.py:24 assets/models/asset.py:89 assets/models/user.py:112
#: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:175 perms/models.py:33
#: assets/templates/assets/system_user_detail.html:178 perms/models.py:33
msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109
#: assets/forms/asset.py:113 assets/models/asset.py:80
#: assets/forms/asset.py:113 assets/models/asset.py:94
#: assets/models/cluster.py:19 assets/models/user.py:72
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user"
msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125
#: assets/templates/assets/asset_create.html:35
#: assets/templates/assets/asset_create.html:37
#: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_list.html:75
#: assets/templates/assets/asset_update.html:40
#: assets/templates/assets/asset_update.html:42
#: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:34
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:71
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:85
#: assets/models/domain.py:46
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:38 assets/forms/asset.py:63 assets/forms/asset.py:77
#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:29
#: assets/templates/assets/asset_update.html:34 perms/forms.py:40
#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35 perms/forms.py:40
#: perms/forms.py:47 perms/models.py:76
#: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:142
......@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets"
msgstr "选择资产"
#: assets/forms/asset.py:105 assets/models/asset.py:67
#: assets/forms/asset.py:105 assets/models/asset.py:81
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58
......@@ -99,11 +99,11 @@ msgid "Port"
msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13
#: assets/models/asset.py:223 assets/templates/assets/admin_user_list.html:25
#: assets/models/asset.py:237 assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:15
#: assets/templates/assets/domain_list.html:23
#: assets/templates/assets/label_list.html:16
#: assets/templates/assets/system_user_list.html:29 audits/models.py:11
#: assets/templates/assets/system_user_list.html:30 audits/models.py:11
#: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:37
#: perms/models.py:32
......@@ -118,14 +118,14 @@ msgstr "端口"
msgid "Asset"
msgstr "资产"
#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:120
#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:139
#: assets/models/base.py:21 assets/models/cluster.py:18
#: assets/models/domain.py:17 assets/models/group.py:20
#: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:23
#: assets/templates/assets/domain_detail.html:56
#: assets/templates/assets/domain_gateway_list.html:56
#: assets/templates/assets/domain_list.html:14
#: assets/templates/assets/domain_list.html:22
#: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:26 common/models.py:26
......@@ -147,16 +147,16 @@ msgstr "资产"
msgid "Name"
msgstr "名称"
#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:121
#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:140
#: assets/models/base.py:22 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
#: users/forms.py:21 users/forms.py:30 users/models/authentication.py:45
#: users/models/user.py:47 users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:56
#: users/forms.py:31 users/models/authentication.py:70 users/models/user.py:47
#: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:60
#: users/templates/users/login_log_list.html:49
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:24
......@@ -169,8 +169,8 @@ msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113
#: users/forms.py:15 users/forms.py:23 users/forms.py:32 users/forms.py:44
#: users/templates/users/login.html:59
#: users/forms.py:15 users/forms.py:33 users/forms.py:45
#: users/templates/users/login.html:63
#: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:14
......@@ -192,17 +192,27 @@ msgstr "ssh密钥不合法"
msgid "Password and private key file must be input one"
msgstr "密码和私钥, 必须输入一个"
#: assets/forms/user.py:126
#: assets/forms/user.py:125
msgid "* Automatic login mode, must fill in the username."
msgstr "自动登录模式,必须填写用户名"
#: assets/forms/user.py:145
msgid "Auto push system user to asset"
msgstr "自动推送系统用户到资产"
#: assets/forms/user.py:127
#: assets/forms/user.py:146
msgid ""
"High level will be using login asset as default, if user was granted more "
"than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/models/asset.py:63 assets/models/domain.py:43
#: assets/forms/user.py:148
msgid ""
"If you choose manual login mode, you do not need to fill in the username and "
"password."
msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
#: assets/models/asset.py:74 assets/models/domain.py:43
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61
......@@ -217,7 +227,7 @@ msgstr "高优先级的系统用户将会作为默认登录用户"
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:66 assets/templates/assets/_asset_list_modal.html:45
#: assets/models/asset.py:77 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:86
......@@ -229,98 +239,107 @@ msgstr "IP"
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:69 assets/templates/assets/asset_detail.html:97
#: assets/models/asset.py:80 assets/models/domain.py:45
#: assets/models/user.py:115
#: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:28
#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:83 assets/templates/assets/asset_detail.html:97
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:76 assets/models/domain.py:48
#: assets/models/asset.py:90 assets/models/domain.py:48
#: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:85 assets/templates/assets/asset_detail.html:65
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:65
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:113
#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:113
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:77
#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:77
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:81
#: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:81
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:95 assets/templates/assets/asset_detail.html:109
#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:109
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:98
#: assets/models/asset.py:112
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:99
#: assets/models/asset.py:113
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:100
#: assets/models/asset.py:114
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:102 assets/templates/assets/asset_detail.html:89
#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:89
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:104
#: assets/models/asset.py:118
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:106
#: assets/models/asset.py:120
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:101
#: assets/models/asset.py:123 assets/templates/assets/asset_detail.html:101
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:111
#: assets/models/asset.py:125
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:113
#: assets/models/asset.py:127
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:115
#: assets/models/asset.py:129
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:119 assets/templates/assets/asset_create.html:33
#: assets/models/asset.py:133 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:220
#: assets/templates/assets/asset_update.html:38 templates/_nav.html:27
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:121 assets/models/base.py:29
#: assets/models/asset.py:135 assets/models/base.py:29
#: assets/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:96
#: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:90 users/templates/users/user_detail.html:111
msgid "Created by"
msgstr "创建者"
#: assets/models/asset.py:124 assets/models/cluster.py:26
#: assets/models/asset.py:138 assets/models/cluster.py:26
#: assets/models/domain.py:20 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:92
#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
#: perms/models.py:39 perms/models.py:82
#: perms/templates/perms/asset_permission_detail.html:94
......@@ -329,7 +348,7 @@ msgstr "创建者"
msgid "Date created"
msgstr "创建日期"
#: assets/models/asset.py:126 assets/models/base.py:26
#: assets/models/asset.py:140 assets/models/base.py:26
#: assets/models/cluster.py:29 assets/models/domain.py:18
#: assets/models/domain.py:47 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
......@@ -337,9 +356,9 @@ msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:125
#: assets/templates/assets/domain_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:61
#: assets/templates/assets/domain_list.html:17
#: assets/templates/assets/system_user_detail.html:100
#: assets/templates/assets/system_user_list.html:33 common/models.py:30
#: assets/templates/assets/domain_list.html:25
#: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:34 common/models.py:30
#: ops/models/adhoc.py:42 perms/models.py:40 perms/models.py:83
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13
......@@ -392,7 +411,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:13
#: users/models/user.py:343
#: users/models/user.py:345
msgid "System"
msgstr "系统"
......@@ -404,14 +423,6 @@ msgstr "默认Cluster"
msgid "Cluster"
msgstr "集群"
#: assets/models/domain.py:45 assets/models/user.py:106
#: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:28
#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
msgstr "协议"
#: assets/models/group.py:30
msgid "Asset group"
msgstr "资产组"
......@@ -431,10 +442,10 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:281
#: users/models/user.py:31 users/models/user.py:331
#: terminal/templates/terminal/session_list.html:71 users/forms.py:282
#: users/models/user.py:31 users/models/user.py:333
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:362
#: users/templates/users/user_group_list.html:13 users/views/user.py:361
msgid "User"
msgstr "用户"
......@@ -451,7 +462,15 @@ msgstr "分类"
msgid "Key"
msgstr ""
#: assets/models/user.py:104
#: assets/models/user.py:108
msgid "Automatic login"
msgstr "自动登录"
#: assets/models/user.py:109
msgid "Manually login"
msgstr "手动登录"
#: assets/models/user.py:113
#: assets/templates/assets/_asset_group_bulk_update_modal.html:11
#: assets/templates/assets/system_user_asset.html:21
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
......@@ -461,7 +480,7 @@ msgstr ""
#: assets/views/asset.py:197 assets/views/domain.py:29
#: assets/views/domain.py:45 assets/views/domain.py:61
#: assets/views/domain.py:74 assets/views/domain.py:98
#: assets/views/domain.py:126 assets/views/domain.py:150
#: assets/views/domain.py:126 assets/views/domain.py:145
#: assets/views/label.py:26 assets/views/label.py:42 assets/views/label.py:58
#: assets/views/system_user.py:28 assets/views/system_user.py:44
#: assets/views/system_user.py:60 assets/views/system_user.py:74
......@@ -469,25 +488,30 @@ msgstr ""
msgid "Assets"
msgstr "资产管理"
#: assets/models/user.py:105
#: assets/models/user.py:114
msgid "Priority"
msgstr "优先级"
#: assets/models/user.py:107 assets/templates/assets/_system_user.html:58
#: assets/templates/assets/system_user_detail.html:118
#: assets/templates/assets/system_user_update.html:11
#: assets/models/user.py:116 assets/templates/assets/_system_user.html:59
#: assets/templates/assets/system_user_detail.html:122
#: assets/templates/assets/system_user_update.html:10
msgid "Auto push"
msgstr "自动推送"
#: assets/models/user.py:108 assets/templates/assets/system_user_detail.html:70
#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:74
msgid "Sudo"
msgstr "Sudo"
#: assets/models/user.py:109 assets/templates/assets/system_user_detail.html:75
#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:79
msgid "Shell"
msgstr "Shell"
#: assets/models/user.py:149 audits/models.py:12
#: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:29
msgid "Login mode"
msgstr "登录模式"
#: assets/models/user.py:159 audits/models.py:12
#: audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:43
#: perms/models.py:34 perms/models.py:78
......@@ -601,32 +625,31 @@ msgid "Basic"
msgstr "基本"
#: assets/templates/assets/_system_user.html:44
#: assets/templates/assets/asset_create.html:25
#: assets/templates/assets/asset_update.html:30
#: assets/templates/assets/asset_create.html:26
#: assets/templates/assets/asset_update.html:31
#: assets/templates/assets/gateway_create_update.html:45
#: assets/templates/assets/system_user_update.html:7
#: users/templates/users/_user.html:21
msgid "Auth"
msgstr "认证"
#: assets/templates/assets/_system_user.html:47
#: assets/templates/assets/_system_user.html:48
msgid "Auto generate key"
msgstr "自动生成密钥"
#: assets/templates/assets/_system_user.html:64
#: assets/templates/assets/asset_create.html:59
#: assets/templates/assets/asset_update.html:63
#: assets/templates/assets/_system_user.html:65
#: assets/templates/assets/asset_create.html:60
#: assets/templates/assets/asset_update.html:64
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:45
#: terminal/templates/terminal/terminal_update.html:42
msgid "Other"
msgstr "其它"
#: assets/templates/assets/_system_user.html:70
#: assets/templates/assets/_system_user.html:71
#: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23
#: assets/templates/assets/asset_create.html:66
#: assets/templates/assets/asset_update.html:70
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18
......@@ -647,12 +670,12 @@ msgstr "其它"
msgid "Reset"
msgstr "重置"
#: assets/templates/assets/_system_user.html:71
#: assets/templates/assets/_system_user.html:72
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_list.html:108
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59
#: assets/templates/assets/label_create_update.html:19
......@@ -662,7 +685,7 @@ msgstr "重置"
#: common/templates/common/security_setting.html:71
#: common/templates/common/terminal_setting.html:108
#: perms/templates/perms/asset_permission_create_update.html:70
#: terminal/templates/terminal/session_list.html:124
#: terminal/templates/terminal/session_list.html:126
#: terminal/templates/terminal/terminal_update.html:48
#: users/templates/users/_user.html:47
#: users/templates/users/forgot_password.html:44
......@@ -702,14 +725,14 @@ msgstr "资产列表"
#: assets/templates/assets/admin_user_assets.html:54
#: assets/templates/assets/admin_user_list.html:26
#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/system_user_list.html:30
#: assets/templates/assets/system_user_list.html:31
#: users/templates/users/user_group_granted_asset.html:47
msgid "Reachable"
msgstr "可连接"
#: assets/templates/assets/admin_user_assets.html:66
#: assets/templates/assets/system_user_asset.html:64
#: assets/templates/assets/system_user_detail.html:112
#: assets/templates/assets/system_user_detail.html:116
#: perms/templates/perms/asset_permission_detail.html:114
msgid "Quick update"
msgstr "快速更新"
......@@ -722,7 +745,7 @@ msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:75
#: assets/templates/assets/asset_detail.html:171
#: assets/templates/assets/system_user_asset.html:81
#: assets/templates/assets/system_user_detail.html:147
#: assets/templates/assets/system_user_detail.html:151
msgid "Test"
msgstr "测试"
......@@ -733,10 +756,10 @@ msgstr "测试"
#: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85
#: assets/templates/assets/domain_list.html:42
#: assets/templates/assets/domain_list.html:50
#: assets/templates/assets/label_list.html:38
#: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:88
#: assets/templates/assets/system_user_list.html:89
#: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:191
#: terminal/templates/terminal/terminal_detail.html:16
......@@ -757,10 +780,10 @@ msgstr "更新"
#: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86
#: assets/templates/assets/domain_list.html:43
#: assets/templates/assets/domain_list.html:51
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:89
#: assets/templates/assets/system_user_list.html:90
#: ops/templates/ops/task_list.html:72
#: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:192
......@@ -785,12 +808,13 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:638
#: assets/templates/assets/system_user_detail.html:192
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
#: assets/templates/assets/system_user_detail.html:195
#: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:366
#: users/templates/users/user_detail.html:391
#: users/templates/users/user_detail.html:414
#: users/templates/users/user_detail.html:374
#: users/templates/users/user_detail.html:399
#: users/templates/users/user_detail.html:422
#: users/templates/users/user_detail.html:458
#: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:200
......@@ -804,12 +828,12 @@ msgid "Create admin user"
msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:27
#: assets/templates/assets/system_user_list.html:31
#: assets/templates/assets/system_user_list.html:32
msgid "Unreachable"
msgstr "不可达"
#: assets/templates/assets/admin_user_list.html:28
#: assets/templates/assets/system_user_list.html:32
#: assets/templates/assets/system_user_list.html:33
#: ops/templates/ops/adhoc_history.html:54
#: ops/templates/ops/task_history.html:60
msgid "Ratio"
......@@ -818,13 +842,13 @@ msgstr "比例"
#: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/asset_list.html:91
#: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:18
#: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:17
#: assets/templates/assets/system_user_list.html:34
#: assets/templates/assets/system_user_list.html:35
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:42
#: perms/templates/perms/asset_permission_list.html:60
#: terminal/templates/terminal/session_list.html:80
#: terminal/templates/terminal/session_list.html:81
#: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:29
......@@ -882,8 +906,8 @@ msgid "Refresh"
msgstr "刷新"
#: assets/templates/assets/asset_detail.html:300
#: users/templates/users/user_detail.html:286
#: users/templates/users/user_detail.html:313
#: users/templates/users/user_detail.html:294
#: users/templates/users/user_detail.html:321
msgid "Update successfully!"
msgstr "更新成功"
......@@ -978,9 +1002,10 @@ msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:633
#: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:361
#: users/templates/users/user_detail.html:386
#: assets/templates/assets/system_user_list.html:134
#: users/templates/users/user_detail.html:369
#: users/templates/users/user_detail.html:394
#: users/templates/users/user_detail.html:453
#: users/templates/users/user_group_list.html:81
#: users/templates/users/user_list.html:195
msgid "Are you sure?"
......@@ -1003,7 +1028,7 @@ msgstr "删除"
msgid "Asset Deleting failed."
msgstr "删除失败"
#: assets/templates/assets/asset_update.html:59
#: assets/templates/assets/asset_update.html:60
msgid "Configuration"
msgstr "配置"
......@@ -1020,7 +1045,7 @@ msgstr "您确定删除吗?"
#: assets/templates/assets/domain_detail.html:21
#: assets/templates/assets/domain_detail.html:64
#: assets/templates/assets/domain_gateway_list.html:21
#: assets/templates/assets/domain_list.html:16
#: assets/templates/assets/domain_list.html:24
msgid "Gateway"
msgstr "网关"
......@@ -1040,7 +1065,7 @@ msgstr "创建网关"
msgid "Test connection"
msgstr "测试连接"
#: assets/templates/assets/domain_list.html:6 assets/views/domain.py:46
#: assets/templates/assets/domain_list.html:14 assets/views/domain.py:46
msgid "Create domain"
msgstr "创建网域"
......@@ -1053,17 +1078,17 @@ msgid "Assets of "
msgstr "资产"
#: assets/templates/assets/system_user_asset.html:70
#: assets/templates/assets/system_user_detail.html:135
#: assets/templates/assets/system_user_detail.html:139
msgid "Push system user now"
msgstr "立刻推送系统"
#: assets/templates/assets/system_user_asset.html:73
#: assets/templates/assets/system_user_detail.html:138
#: assets/templates/assets/system_user_detail.html:142
msgid "Push"
msgstr "推送"
#: assets/templates/assets/system_user_asset.html:78
#: assets/templates/assets/system_user_detail.html:144
#: assets/templates/assets/system_user_detail.html:148
msgid "Test assets connective"
msgstr "测试资产可连接性"
......@@ -1075,28 +1100,23 @@ msgstr "任务已下发,查看ops任务列表"
msgid "Task has been send, seen left assets status"
msgstr "任务已下发,查看左侧资产状态"
#: assets/templates/assets/system_user_detail.html:81
#: assets/templates/assets/system_user_detail.html:85
msgid "Home"
msgstr "家目录"
#: assets/templates/assets/system_user_detail.html:87
#: assets/templates/assets/system_user_detail.html:91
msgid "Uid"
msgstr "Uid"
#: assets/templates/assets/system_user_detail.html:153
#: assets/templates/assets/system_user_detail.html:339
msgid "Clear auth"
msgstr "清除认证信息"
#: assets/templates/assets/system_user_detail.html:156
msgid "Clear"
msgstr "清除"
#: assets/templates/assets/system_user_detail.html:183
#: assets/templates/assets/system_user_detail.html:186
msgid "Add to node"
msgstr "添加到节点"
#: assets/templates/assets/system_user_detail.html:339
#: assets/templates/assets/system_user_detail.html:353
msgid "Clear auth"
msgstr "清除认证信息"
#: assets/templates/assets/system_user_detail.html:353
msgid "success"
msgstr "成功"
......@@ -1105,20 +1125,20 @@ msgstr "成功"
msgid "Create system user"
msgstr "创建系统用户"
#: assets/templates/assets/system_user_list.html:134
#: assets/templates/assets/system_user_list.html:135
msgid "This will delete the selected System Users !!!"
msgstr "删除选择系统用户"
#: assets/templates/assets/system_user_list.html:142
#: assets/templates/assets/system_user_list.html:143
msgid "System Users Deleted."
msgstr "已被删除"
#: assets/templates/assets/system_user_list.html:143
#: assets/templates/assets/system_user_list.html:148
#: assets/templates/assets/system_user_list.html:144
#: assets/templates/assets/system_user_list.html:149
msgid "System Users Delete"
msgstr "删除系统用户"
#: assets/templates/assets/system_user_list.html:147
#: assets/templates/assets/system_user_list.html:148
msgid "System Users Deleting failed."
msgstr "系统用户删除失败"
......@@ -1166,7 +1186,7 @@ msgstr "网域详情"
msgid "Domain gateway list"
msgstr "域网关列表"
#: assets/views/domain.py:151
#: assets/views/domain.py:146
msgid "Update gateway"
msgstr "创建网关"
......@@ -1214,7 +1234,8 @@ msgid "Filename"
msgstr "文件名"
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/task_list.html:39
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
#: users/templates/users/user_detail.html:443
msgid "Success"
msgstr "成功"
......@@ -1223,7 +1244,7 @@ msgstr "成功"
#: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/task_history.html:58 perms/models.py:36
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:137
#: terminal/templates/terminal/session_list.html:77
#: terminal/templates/terminal/session_list.html:78
msgid "Date start"
msgstr "开始日期"
......@@ -1410,45 +1431,60 @@ msgid ""
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: common/forms.py:184
#: common/forms.py:185
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
#: common/forms.py:190
msgid "No logon interval"
msgstr "禁止登录时间间隔"
#: common/forms.py:192
msgid ""
"Tip :(unit/minute) if the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval."
msgstr ""
"提示:(单位 / 分钟)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
#: common/forms.py:198
msgid "Password minimum length"
msgstr "密码最小长度 "
#: common/forms.py:191
#: common/forms.py:205
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
#: common/forms.py:193
#: common/forms.py:207
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:199
#: common/forms.py:213
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
#: common/forms.py:200
#: common/forms.py:214
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:206
#: common/forms.py:220
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
#: common/forms.py:207
#: common/forms.py:221
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:213
#: common/forms.py:227
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
#: common/forms.py:214
#: common/forms.py:228
msgid ""
"After opening, the user password changes and resets must contain special "
"characters"
......@@ -1462,7 +1498,8 @@ msgstr ""
msgid "discard time"
msgstr ""
#: common/models.py:29 users/templates/users/user_detail.html:96
#: common/models.py:29 users/models/authentication.py:51
#: users/templates/users/user_detail.html:96
msgid "Enabled"
msgstr "启用"
......@@ -1508,8 +1545,8 @@ msgid "Security setting"
msgstr "安全设置"
#: common/templates/common/security_setting.html:42
msgid "MFA setting"
msgstr "MFA 设置"
msgid "User login settings"
msgstr "用户登录设置"
#: common/templates/common/security_setting.html:46
msgid "Password check rule"
......@@ -1780,7 +1817,7 @@ msgid "Versions"
msgstr "版本"
#: ops/templates/ops/task_list.html:40
#: users/templates/users/login_log_list.html:54
#: users/templates/users/login_log_list.html:57
msgid "Date"
msgstr "日期"
......@@ -1805,8 +1842,8 @@ msgstr "任务列表"
msgid "Task run history"
msgstr "执行历史"
#: perms/forms.py:18 users/forms.py:238 users/forms.py:243 users/forms.py:255
#: users/forms.py:285
#: perms/forms.py:18 users/forms.py:239 users/forms.py:244 users/forms.py:256
#: users/forms.py:286
msgid "Select users"
msgstr "选择用户"
......@@ -1815,7 +1852,7 @@ msgstr "选择用户"
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
#: users/models/group.py:23 users/models/user.py:55
#: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:192
#: users/templates/users/user_detail.html:200
#: users/templates/users/user_list.html:26
msgid "User group"
msgstr "用户组"
......@@ -1868,7 +1905,7 @@ msgid "Add node to this permission"
msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125
#: users/templates/users/user_detail.html:209
#: users/templates/users/user_detail.html:217
msgid "Join"
msgstr "加入"
......@@ -1958,14 +1995,14 @@ msgstr "商业支持"
msgid "Docs"
msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:121
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:122
#: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:39
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:344
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:343
msgid "Profile"
msgstr "个人信息"
......@@ -1982,7 +2019,7 @@ msgid "Logout"
msgstr "注销登录"
#: templates/_header_bar.html:49 users/templates/users/login.html:44
#: users/templates/users/login.html:64
#: users/templates/users/login.html:68
msgid "Login"
msgstr "登录"
......@@ -2022,13 +2059,13 @@ msgstr "关闭"
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
#: users/views/login.py:277 users/views/login.py:335 users/views/user.py:66
#: users/views/user.py:81 users/views/user.py:103 users/views/user.py:174
#: users/views/user.py:329 users/views/user.py:381 users/views/user.py:416
#: users/views/login.py:330 users/views/login.py:388 users/views/user.py:65
#: users/views/user.py:80 users/views/user.py:102 users/views/user.py:175
#: users/views/user.py:330 users/views/user.py:380 users/views/user.py:415
msgid "Users"
msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:67
#: templates/_nav.html:13 users/views/user.py:66
msgid "User list"
msgstr "用户列表"
......@@ -2138,14 +2175,14 @@ msgstr "线程数"
msgid "Boot Time"
msgstr "运行时间"
#: terminal/models.py:132 terminal/templates/terminal/session_list.html:102
#: terminal/models.py:132 terminal/templates/terminal/session_list.html:104
msgid "Replay"
msgstr "回放"
#: terminal/models.py:133 terminal/templates/terminal/command_list.html:55
#: terminal/templates/terminal/command_list.html:71
#: terminal/templates/terminal/session_detail.html:48
#: terminal/templates/terminal/session_list.html:76
#: terminal/templates/terminal/session_list.html:77
msgid "Command"
msgstr "命令"
......@@ -2196,24 +2233,28 @@ msgstr "监控"
msgid "Terminate session"
msgstr "终止会话"
#: terminal/templates/terminal/session_list.html:79
#: terminal/templates/terminal/session_list.html:76
msgid "Login from"
msgstr "登录来源"
#: terminal/templates/terminal/session_list.html:80
msgid "Duration"
msgstr "时长"
#: terminal/templates/terminal/session_list.html:104
#: terminal/templates/terminal/session_list.html:106
msgid "Monitor"
msgstr "监控"
#: terminal/templates/terminal/session_list.html:106
#: terminal/templates/terminal/session_list.html:108
#: terminal/templates/terminal/session_list.html:110
msgid "Terminate"
msgstr "终断"
#: terminal/templates/terminal/session_list.html:120
#: terminal/templates/terminal/session_list.html:122
msgid "Terminate selected"
msgstr "终断所选"
#: terminal/templates/terminal/session_list.html:140
#: terminal/templates/terminal/session_list.html:142
msgid "Terminate task send, waiting ..."
msgstr "终断任务已发送,请等待"
......@@ -2283,6 +2324,10 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api.py:221 users/templates/users/login.html:50
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
#: users/authentication.py:56
msgid "Invalid signature header. No credentials provided."
msgstr ""
......@@ -2334,11 +2379,11 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr ""
#: users/forms.py:38
#: users/forms.py:39
msgid "MFA code"
msgstr "MFA 验证码"
#: users/forms.py:49 users/models/user.py:59
#: users/forms.py:50 users/models/user.py:59
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25
......@@ -2346,31 +2391,31 @@ msgstr "MFA 验证码"
msgid "Role"
msgstr "角色"
#: users/forms.py:52 users/forms.py:201
#: users/forms.py:53 users/forms.py:202
msgid "ssh public key"
msgstr "ssh公钥"
#: users/forms.py:53 users/forms.py:202
#: users/forms.py:54 users/forms.py:203
msgid "ssh-rsa AAAA..."
msgstr ""
#: users/forms.py:54
#: users/forms.py:55
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
#: users/forms.py:72 users/templates/users/user_detail.html:200
#: users/forms.py:73 users/templates/users/user_detail.html:208
msgid "Join user groups"
msgstr "添加到用户组"
#: users/forms.py:83 users/forms.py:216
#: users/forms.py:84 users/forms.py:217
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms.py:87 users/forms.py:220 users/serializers.py:48
#: users/forms.py:88 users/forms.py:221 users/serializers.py:48
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
#: users/forms.py:127
#: users/forms.py:128
msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick "
......@@ -2379,16 +2424,17 @@ msgstr ""
"提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修"
"改->更改MFA设置)中直接绑定!"
#: users/forms.py:137
#: users/forms.py:138
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:142 users/models/user.py:71
#: users/forms.py:143 users/models/authentication.py:75 users/models/user.py:71
#: users/templates/users/first_login.html:45
#: users/templates/users/login_log_list.html:54
msgid "MFA"
msgstr "MFA"
#: users/forms.py:147
#: users/forms.py:148
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
......@@ -2397,41 +2443,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)"
#: users/forms.py:154 users/templates/users/first_login.html:48
#: users/forms.py:155 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:107
#: users/templates/users/first_login.html:130
msgid "Finish"
msgstr "完成"
#: users/forms.py:160
#: users/forms.py:161
msgid "Old password"
msgstr "原来密码"
#: users/forms.py:165
#: users/forms.py:166
msgid "New password"
msgstr "新密码"
#: users/forms.py:170
#: users/forms.py:171
msgid "Confirm password"
msgstr "确认密码"
#: users/forms.py:180
#: users/forms.py:181
msgid "Old password error"
msgstr "原来密码错误"
#: users/forms.py:188
#: users/forms.py:189
msgid "Password does not match"
msgstr "密码不一致"
#: users/forms.py:199
#: users/forms.py:200
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
#: users/forms.py:203
#: users/forms.py:204
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
#: users/forms.py:231 users/models/user.py:79
#: users/forms.py:232 users/models/user.py:79
#: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:45
#: users/templates/users/user_profile.html:68
......@@ -2444,27 +2490,57 @@ msgstr "ssh公钥"
msgid "Private Token"
msgstr "ssh密钥"
#: users/models/authentication.py:46
#: users/models/authentication.py:50 users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: users/models/authentication.py:52 users/models/authentication.py:60
msgid "-"
msgstr ""
#: users/models/authentication.py:61
msgid "Username/password check failed"
msgstr "用户名/密码 校验失败"
#: users/models/authentication.py:62
msgid "MFA authentication failed"
msgstr "MFA 认证失败"
#: users/models/authentication.py:67
msgid "Failed"
msgstr "失败"
#: users/models/authentication.py:71
msgid "Login type"
msgstr "登录方式"
#: users/models/authentication.py:47
#: users/models/authentication.py:72
msgid "Login ip"
msgstr "登录IP"
#: users/models/authentication.py:48
#: users/models/authentication.py:73
msgid "Login city"
msgstr "登录城市"
#: users/models/authentication.py:49
#: users/models/authentication.py:74
msgid "User agent"
msgstr "Agent"
#: users/models/authentication.py:50
#: users/models/authentication.py:76
#: users/templates/users/login_log_list.html:55
msgid "Reason"
msgstr "原因"
#: users/models/authentication.py:77
#: users/templates/users/login_log_list.html:56
msgid "Status"
msgstr "状态"
#: users/models/authentication.py:78
msgid "Date login"
msgstr "登录日期"
#: users/models/user.py:30 users/models/user.py:339
#: users/models/user.py:30 users/models/user.py:341
msgid "Administrator"
msgstr "管理员"
......@@ -2506,7 +2582,7 @@ msgstr "微信"
msgid "Source"
msgstr "用户来源"
#: users/models/user.py:342
#: users/models/user.py:344
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
......@@ -2586,7 +2662,7 @@ msgid " for more information"
msgstr "获取更多信息"
#: users/templates/users/forgot_password.html:26
#: users/templates/users/login.html:73
#: users/templates/users/login.html:77
msgid "Forgot password"
msgstr "忘记密码"
......@@ -2594,7 +2670,7 @@ msgstr "忘记密码"
msgid "Input your email, that will send a mail to your"
msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中"
#: users/templates/users/login.html:50
#: users/templates/users/login.html:53
msgid "Captcha invalid"
msgstr "验证码错误"
......@@ -2623,7 +2699,7 @@ msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!"
#: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:352 users/utils.py:80
#: users/templates/users/user_detail.html:360 users/utils.py:80
msgid "Reset password"
msgstr "重置密码"
......@@ -2649,7 +2725,7 @@ msgid "Setting"
msgstr "设置"
#: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:81
#: users/templates/users/user_list.html:16 users/views/user.py:80
msgid "Create user"
msgstr "创建用户"
......@@ -2658,7 +2734,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:175
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:176
msgid "User detail"
msgstr "用户详情"
......@@ -2673,10 +2749,6 @@ msgstr "授权的资产"
msgid "Force enabled"
msgstr "强制启用"
#: users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: users/templates/users/user_detail.html:119
#: users/templates/users/user_profile.html:108
msgid "Last login"
......@@ -2699,44 +2771,57 @@ msgstr "发送"
msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件"
#: users/templates/users/user_detail.html:295
#: users/templates/users/user_detail.html:186
#: users/templates/users/user_detail.html:444
msgid "Unblock user"
msgstr "解除登录限制"
#: users/templates/users/user_detail.html:189
msgid "Unblock"
msgstr "解除"
#: users/templates/users/user_detail.html:303
msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的MFA"
#: users/templates/users/user_detail.html:351
#: users/templates/users/user_detail.html:359
msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱"
#: users/templates/users/user_detail.html:362
#: users/templates/users/user_detail.html:370
msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#: users/templates/users/user_detail.html:376
#: users/templates/users/user_detail.html:384
msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱"
#: users/templates/users/user_detail.html:377
#: users/templates/users/user_detail.html:385
msgid "Reset SSH public key"
msgstr "重置SSH密钥"
#: users/templates/users/user_detail.html:387
#: users/templates/users/user_detail.html:395
msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:404
#: users/templates/users/user_detail.html:412
#: users/templates/users/user_profile.html:211
msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:405
#: users/templates/users/user_detail.html:409
#: users/templates/users/user_detail.html:413
#: users/templates/users/user_detail.html:417
#: users/templates/users/user_profile.html:212
#: users/templates/users/user_profile.html:217
msgid "User SSH public key update"
msgstr "ssh密钥"
#: users/templates/users/user_detail.html:454
msgid "After unlocking the user, the user can log in normally."
msgstr "解除用户登录限制后,此用户即可正常登录"
#: users/templates/users/user_group_create_update.html:31
msgid "Cancel"
msgstr "取消"
......@@ -2793,8 +2878,8 @@ msgstr "用户删除失败"
msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:116 users/views/user.py:204
#: users/views/user.py:258
#: users/templates/users/user_profile.html:116 users/views/user.py:205
#: users/views/user.py:259
msgid "User groups"
msgstr "用户组"
......@@ -2840,7 +2925,7 @@ msgid ""
"corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:104
#: users/templates/users/user_update.html:4 users/views/user.py:103
msgid "Update user"
msgstr "更新用户"
......@@ -2849,7 +2934,7 @@ msgid "Create account successfully"
msgstr "创建账户成功"
#: users/utils.py:43
#, fuzzy, python-format
#, python-format
msgid ""
"\n"
" Hello %(name)s:\n"
......@@ -2978,7 +3063,7 @@ msgstr "禁用或失效"
msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法"
#: users/utils.py:290 users/utils.py:300
#: users/utils.py:289 users/utils.py:299
msgid "Bit"
msgstr " 位"
......@@ -2994,103 +3079,112 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
#: users/views/login.py:62
#: users/views/login.py:75
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:128 users/views/user.py:501 users/views/user.py:526
#: users/views/login.py:178 users/views/user.py:500 users/views/user.py:525
msgid "MFA code invalid"
msgstr "MFA码认证失败"
#: users/views/login.py:154
#: users/views/login.py:207
msgid "Logout success"
msgstr "退出登录成功"
#: users/views/login.py:155
#: users/views/login.py:208
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:171
#: users/views/login.py:224
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:184
#: users/views/login.py:237
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/login.py:185
#: users/views/login.py:238
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:198
#: users/views/login.py:251
msgid "Reset password success"
msgstr "重置密码成功"
#: users/views/login.py:199
#: users/views/login.py:252
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:220 users/views/login.py:233
#: users/views/login.py:273 users/views/login.py:286
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/login.py:229
#: users/views/login.py:282
msgid "Password not same"
msgstr "密码不一致"
#: users/views/login.py:239 users/views/user.py:116 users/views/user.py:399
#: users/views/login.py:292 users/views/user.py:118 users/views/user.py:398
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/login.py:277
#: users/views/login.py:330
msgid "First login"
msgstr "首次登陆"
#: users/views/login.py:336
#: users/views/login.py:389
msgid "Login log list"
msgstr "登录日志"
#: users/views/user.py:128
#: users/views/user.py:129
msgid "Bulk update user success"
msgstr "批量更新用户成功"
#: users/views/user.py:233
#: users/views/user.py:234
msgid "Invalid file."
msgstr "文件不合法"
#: users/views/user.py:330
#: users/views/user.py:331
msgid "User granted assets"
msgstr "用户授权资产"
#: users/views/user.py:363
#: users/views/user.py:362
msgid "Profile setting"
msgstr "个人信息设置"
#: users/views/user.py:382
#: users/views/user.py:381
msgid "Password update"
msgstr "密码更新"
#: users/views/user.py:417
#: users/views/user.py:416
msgid "Public key update"
msgstr "密钥更新"
#: users/views/user.py:458
#: users/views/user.py:457
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/user.py:552
#: users/views/user.py:551
msgid "MFA enable success"
msgstr "MFA 绑定成功"
#: users/views/user.py:553
#: users/views/user.py:552
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:555
#: users/views/user.py:554
msgid "MFA disable success"
msgstr "MFA 解绑成功"
#: users/views/user.py:556
#: users/views/user.py:555
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
#~ msgid "Unblock user successfully. "
#~ msgstr "解除登录限制成功"
#~ msgid "Clear"
#~ msgstr "清除"
#~ msgid "MFA setting"
#~ msgstr "MFA 设置"
......@@ -343,10 +343,11 @@ if AUTH_LDAP:
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
# Celery using redis as broker
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
'db':CONFIG.REDIS_DB_CELERY_BROKER or 3,
}
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
......@@ -367,10 +368,11 @@ CELERY_WORKER_HIJACK_ROOT_LOGGER = False
CACHES = {
'default': {
'BACKEND': 'redis_cache.RedisCache',
'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/4' % {
'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
'db':CONFIG.REDIS_DB_CACHE or 4,
}
}
}
......@@ -403,6 +405,8 @@ TERMINAL_REPLAY_STORAGE = {
DEFAULT_PASSWORD_MIN_LENGTH = 6
DEFAULT_LOGIN_LIMIT_COUNT = 3
DEFAULT_LOGIN_LIMIT_TIME = 30
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = {
......
......@@ -93,7 +93,7 @@ class JMSInventory(BaseInventory):
if gateway.password:
proxy_command_list.insert(
0, "sshpass -p {}".format(gateway.password)
0, "sshpass -p '{}'".format(gateway.password)
)
if gateway.private_key:
proxy_command_list.append("-i {}".format(gateway.private_key_file))
......
......@@ -77,9 +77,9 @@ class UserGrantedAssetsApi(ListAPIView):
util = AssetPermissionUtil(user)
for k, v in util.get_assets().items():
if k.is_unixlike():
system_users_granted = [s for s in v if s.protocol == 'ssh']
system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']]
else:
system_users_granted = [s for s in v if s.protocol == 'rdp']
system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']]
k.system_users_granted = system_users_granted
queryset.append(k)
return queryset
......@@ -128,9 +128,9 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
assets = _assets.keys()
for k, v in _assets.items():
if k.is_unixlike():
system_users_granted = [s for s in v if s.protocol == 'ssh']
system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']]
else:
system_users_granted = [s for s in v if s.protocol == 'rdp']
system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']]
k.system_users_granted = system_users_granted
node.assets_granted = assets
queryset.append(node)
......
......@@ -6,13 +6,11 @@ from .. import views
app_name = 'perms'
urlpatterns = [
url(r'^asset-permission$', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
url(r'^asset-permission/create$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
url(r'^asset-permission/$', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
url(r'^asset-permission/create/$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user/$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
]
......@@ -173,14 +173,14 @@ function APIUpdateAttr(props) {
}
if (typeof props.success === 'function') {
return props.success(data);
}
}
}).fail(function(jqXHR, textStatus, errorThrown) {
if (flash_message) {
toastr.error(fail_message);
}
if (typeof props.error === 'function') {
return props.error(jqXHR.responseText);
}
}
});
// return true;
}
......@@ -198,7 +198,8 @@ function objectDelete(obj, name, url, redirectTo) {
}
};
var fail = function() {
swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
// swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
swal("错误", "[ "+name+" ]"+"正在被资产使用中,请先解除资产绑定", "error");
};
APIUpdateAttr({
url: url,
......@@ -219,7 +220,7 @@ function objectDelete(obj, name, url, redirectTo) {
confirmButtonText: '确认',
closeOnConfirm: true,
}, function () {
doDelete()
doDelete()
});
}
......@@ -272,7 +273,7 @@ jumpserver.initDataTable = function (options) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
}
},
{className: 'text-center', targets: '_all'}
{className: 'text-center', render: $.fn.dataTable.render.text(), targets: '_all'}
];
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var select = {
......
<div class="footer fixed">
<div class="pull-right">
Version <strong>1.3.2-{% include '_build.html' %}</strong> GPLv2.
Version <strong>1.3.3-{% include '_build.html' %}</strong> GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
<div>
......
......@@ -259,10 +259,35 @@ class SessionReplayViewSet(viewsets.ViewSet):
serializer_class = ReplaySerializer
permission_classes = (IsSuperUserOrAppUser,)
session = None
def gen_session_path(self):
upload_to = 'replay' # 仅添加到本地存储中
def get_session_path(self, version=2):
"""
获取session日志的文件路径
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
:return:
"""
suffix = '.replay.gz'
if version == 1:
suffix = '.gz'
date = self.session.date_start.strftime('%Y-%m-%d')
return os.path.join(date, str(self.session.id) + '.gz')
return os.path.join(date, str(self.session.id) + suffix)
def get_local_path(self, version=2):
session_path = self.get_session_path(version=version)
if version == 2:
local_path = os.path.join(self.upload_to, session_path)
else:
local_path = session_path
return local_path
def save_to_storage(self, f):
local_path = self.get_local_path()
try:
name = default_storage.save(local_path, f)
return name, None
except OSError as e:
return None, e
def create(self, request, *args, **kwargs):
session_id = kwargs.get('pk')
......@@ -271,46 +296,49 @@ class SessionReplayViewSet(viewsets.ViewSet):
if serializer.is_valid():
file = serializer.validated_data['file']
file_path = self.gen_session_path()
try:
default_storage.save(file_path, file)
return Response({'url': default_storage.url(file_path)},
status=201)
except IOError:
return Response("Save error", status=500)
name, err = self.save_to_storage(file)
if not name:
msg = "Failed save replay `{}`: {}".format(session_id, err)
logger.error(msg)
return Response({'msg': str(err)}, status=400)
url = default_storage.url(name)
return Response({'url': url}, status=201)
else:
logger.error(
'Update load data invalid: {}'.format(serializer.errors))
msg = 'Upload data invalid: {}'.format(serializer.errors)
logger.error(msg)
return Response({'msg': serializer.errors}, status=401)
def retrieve(self, request, *args, **kwargs):
session_id = kwargs.get('pk')
self.session = get_object_or_404(Session, id=session_id)
path = self.gen_session_path()
if default_storage.exists(path):
url = default_storage.url(path)
return redirect(url)
else:
config = settings.TERMINAL_REPLAY_STORAGE
configs = copy.deepcopy(config)
for cfg in config:
if config[cfg]['TYPE'] == 'server':
configs.__delitem__(cfg)
if not configs:
return HttpResponseNotFound()
date = self.session.date_start.strftime('%Y-%m-%d')
file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
target_path = default_storage.base_location + '/' + path
storage = jms_storage.get_multi_object_storage(configs)
ok, err = storage.download(file_path, target_path)
if ok:
return redirect(default_storage.url(path))
else:
logger.error("Failed download replay file: {}".format(err))
return HttpResponseNotFound()
# 新版本和老版本的文件后缀不同
session_path = self.get_session_path() # 存在外部存储上的路径
local_path = self.get_local_path()
local_path_v1 = self.get_local_path(version=1)
# 去default storage中查找
for _local_path in (local_path, local_path_v1, session_path):
if default_storage.exists(_local_path):
url = default_storage.url(_local_path)
return redirect(url)
# 去定义的外部storage查找
configs = settings.TERMINAL_REPLAY_STORAGE
configs = {k: v for k, v in configs.items() if v['TYPE'] != 'server'}
if not configs:
return HttpResponseNotFound()
target_path = os.path.join(default_storage.base_location, local_path) # 保存到storage的路径
target_dir = os.path.dirname(target_path)
if not os.path.isdir(target_dir):
os.makedirs(target_dir, exist_ok=True)
storage = jms_storage.get_multi_object_storage(configs)
ok, err = storage.download(session_path, target_path)
if not ok:
logger.error("Failed download replay file: {}".format(err))
return HttpResponseNotFound()
return redirect(default_storage.url(local_path))
class SessionReplayV2ViewSet(SessionReplayViewSet):
......
......@@ -73,6 +73,7 @@
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Remote addr' %}</th>
<th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Login from' %}</th>
<th class="text-center">{% trans 'Command' %}</th>
<th class="text-center">{% trans 'Date start' %}</th>
{# <th class="text-center">{% trans 'Date last active' %}</th>#}
......@@ -92,6 +93,7 @@
<td class="text-center">{{ session.system_user }}</td>
<td class="text-center">{{ session.remote_addr|default:"" }}</td>
<td class="text-center">{{ session.protocol }}</td>
<td class="text-center">{{ session.get_login_from_display }}</td>
<td class="text-center">{{ session.id | get_session_command_amount }}</td>
<td class="text-center">{{ session.date_start }}</td>
......
......@@ -3,6 +3,7 @@ import uuid
from django.core.cache import cache
from django.urls import reverse
from django.utils.translation import ugettext as _
from rest_framework import generics
from rest_framework.permissions import AllowAny, IsAuthenticated
......@@ -14,10 +15,11 @@ from .serializers import UserSerializer, UserGroupSerializer, \
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async
from .models import User, UserGroup
from .models import User, UserGroup, LoginLog
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
IsSuperUserOrAppUser
from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code
from .utils import check_user_valid, generate_token, get_login_ip, \
check_otp_code, set_user_login_failed_count_to_cache, is_block_login
from common.mixins import IDInFilterMixin
from common.utils import get_logger
......@@ -93,6 +95,22 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user.save()
class UserUnblockPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
def perform_update(self, serializer):
user = self.get_object()
username = user.username if user else ''
key_limit = self.key_prefix_limit.format(username, '*')
key_block = self.key_prefix_block.format(username)
cache.delete_pattern(key_limit)
cache.delete(key_block)
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer
......@@ -128,16 +146,12 @@ class UserToken(APIView):
return Response({'error': msg}, status=406)
class UserProfile(APIView):
permission_classes = (IsValidUser,)
class UserProfile(generics.RetrieveAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = UserSerializer
def get(self, request):
# return Response(request.user.to_json())
return Response(self.serializer_class(request.user).data)
def post(self, request):
return Response(self.serializer_class(request.user).data)
def get_object(self):
return self.request.user
class UserOtpAuthApi(APIView):
......@@ -153,10 +167,23 @@ class UserOtpAuthApi(APIView):
return Response({'msg': '请先进行用户名和密码验证'}, status=401)
if not check_otp_code(user.otp_secret_key, otp_code):
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(request, data)
return Response({'msg': 'MFA认证失败'}, status=401)
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
token = generate_token(request, user)
self.write_login_log(request, user)
return Response(
{
'token': token,
......@@ -165,7 +192,7 @@ class UserOtpAuthApi(APIView):
)
@staticmethod
def write_login_log(request, user):
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
......@@ -173,25 +200,54 @@ class UserOtpAuthApi(APIView):
if not login_ip:
login_ip = get_login_ip(request)
write_login_log_async.delay(
user.username, ip=login_ip,
type=login_type, user_agent=user_agent,
)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserAuthApi(APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
def post(self, request):
user, msg = self.check_user_valid(request)
# limit login
username = request.data.get('username')
ip = request.data.get('remote_addr', None)
ip = ip if ip else get_login_ip(request)
key_limit = self.key_prefix_limit.format(username, ip)
key_block = self.key_prefix_block.format(username)
if is_block_login(key_limit):
msg = _("Log in frequently and try again later")
return Response({'msg': msg}, status=401)
user, msg = self.check_user_valid(request)
if not user:
data = {
'username': request.data.get('username', ''),
'mfa': LoginLog.MFA_UNKNOWN,
'reason': LoginLog.REASON_PASSWORD,
'status': False
}
self.write_login_log(request, data)
set_user_login_failed_count_to_cache(key_limit, key_block)
return Response({'msg': msg}, status=401)
if not user.otp_enabled:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
token = generate_token(request, user)
self.write_login_log(request, user)
return Response(
{
'token': token,
......@@ -208,7 +264,8 @@ class UserAuthApi(APIView):
'otp_url': reverse('api-users:user-otp-auth'),
'seed': seed,
'user': self.serializer_class(user).data
}, status=300)
}, status=300
)
@staticmethod
def check_user_valid(request):
......@@ -222,7 +279,7 @@ class UserAuthApi(APIView):
return user, msg
@staticmethod
def write_login_log(request, user):
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
......@@ -230,10 +287,14 @@ class UserAuthApi(APIView):
if not login_ip:
login_ip = get_login_ip(request)
write_login_log_async.delay(
user.username, ip=login_ip,
type=login_type, user_agent=user_agent,
)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent,
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserConnectionTokenApi(APIView):
......
......@@ -41,12 +41,40 @@ class LoginLog(models.Model):
('W', 'Web'),
('T', 'Terminal'),
)
MFA_DISABLED = 0
MFA_ENABLED = 1
MFA_UNKNOWN = 2
MFA_CHOICE = (
(MFA_DISABLED, _('Disabled')),
(MFA_ENABLED, _('Enabled')),
(MFA_UNKNOWN, _('-')),
)
REASON_NOTHING = 0
REASON_PASSWORD = 1
REASON_MFA = 2
REASON_CHOICE = (
(REASON_NOTHING, _('-')),
(REASON_PASSWORD, _('Username/password check failed')),
(REASON_MFA, _('MFA authentication failed')),
)
STATUS_CHOICE = (
(True, _('Success')),
(False, _('Failed'))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(max_length=20, verbose_name=_('Username'))
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason'))
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
class Meta:
......
......@@ -45,13 +45,17 @@
</div>
<form class="m-t" role="form" method="post" action="">
{% csrf_token %}
{% if form.errors %}
{% if block_login %}
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
{% elif form.errors %}
{% if 'captcha' in form.errors %}
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
{% else %}
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
{% endif %}
{% endif %}
<div class="form-group">
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
</div>
......
......@@ -51,6 +51,9 @@
<th class="text-center">{% trans 'UA' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'City' %}</th>
<th class="text-center">{% trans 'MFA' %}</th>
<th class="text-center">{% trans 'Reason' %}</th>
<th class="text-center">{% trans 'Status' %}</th>
<th class="text-center">{% trans 'Date' %}</th>
{% endblock %}
......@@ -65,6 +68,9 @@
</td>
<td class="text-center">{{ login_log.ip }}</td>
<td class="text-center">{{ login_log.city }}</td>
<td class="text-center">{{ login_log.get_mfa_display }}</td>
<td class="text-center">{{ login_log.get_reason_display }}</td>
<td class="text-center">{{ login_log.get_status_display }}</td>
<td class="text-center">{{ login_log.datetime }}</td>
</tr>
{% endfor %}
......
......@@ -182,6 +182,14 @@
</span>
</td>
</tr>
<tr style="{% if not unblock %}display:none{% endif %}">
<td>{% trans 'Unblock user' %}</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn-unblock-user" style="width: 54px">{% trans 'Unblock' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
......@@ -275,7 +283,7 @@ $(document).ready(function() {
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id];
})
});
})
.on('click', '#is_active', function() {
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
......@@ -293,7 +301,7 @@ $(document).ready(function() {
.on('click', '#force_enable_otp', function() {
{% if request.user == user_object %}
toastr.error("{% trans 'Goto profile page enable MFA' %}");
return
return;
{% endif %}
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
......@@ -421,11 +429,45 @@ $(document).ready(function() {
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
}).on('click', '.btn-delete-user', function () {
var $this = $(this);
var name = "{{ user.name }}";
var uid = "{{ user.id }}";
var name = "{{ user_object.name }}";
var uid = "{{ user_object.id }}";
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'users:user-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#btn-unblock-user', function () {
function doReset() {
{#var the_url = '{% url "api-users:user-reset-password" pk=user_object.id %}';#}
var the_url = '{% url "api-users:user-unblock" pk=user_object.id %}';
var body = {};
var success = function() {
var msg = "{% trans "Success" %}";
{#swal("{% trans 'Unblock user' %}", msg, "success");#}
swal({
title: "{% trans 'Unblock user' %}",
text: msg,
type: "success"
}, function() {
location.reload()
}
);
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans "After unlocking the user, the user can log in normally."%}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
doReset();
});
})
</script>
{% endblock %}
......@@ -59,7 +59,7 @@ function initTable() {
ele: $('#user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
var detail_btn = '<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">' + escape(cellData) + '</a>';
$(td).html(detail_btn.replace("{{ DEFAULT_PK }}", rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
......
......@@ -29,6 +29,8 @@ urlpatterns = [
api.UserResetPKApi.as_view(), name='user-public-key-reset'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/update/$',
api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/unblock/$',
api.UserUnblockPKApi.as_view(), name='user-unblock'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/groups/$',
api.UserUpdateGroupApi.as_view(), name='user-update-group'),
url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/users/$',
......
......@@ -8,13 +8,13 @@ app_name = 'users'
urlpatterns = [
# Login view
url(r'^login$', views.UserLoginView.as_view(), name='login'),
url(r'^logout$', views.UserLogoutView.as_view(), name='logout'),
url(r'^login/otp$', views.UserLoginOtpView.as_view(), name='login-otp'),
url(r'^password/forgot$', views.UserForgotPasswordView.as_view(), name='forgot-password'),
url(r'^password/forgot/sendmail-success$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
url(r'^password/reset$', views.UserResetPasswordView.as_view(), name='reset-password'),
url(r'^password/reset/success$', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'),
url(r'^login/$', views.UserLoginView.as_view(), name='login'),
url(r'^logout/$', views.UserLogoutView.as_view(), name='logout'),
url(r'^login/otp/$', views.UserLoginOtpView.as_view(), name='login-otp'),
url(r'^password/forgot/$', views.UserForgotPasswordView.as_view(), name='forgot-password'),
url(r'^password/forgot/sendmail-success/$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
url(r'^password/reset/$', views.UserResetPasswordView.as_view(), name='reset-password'),
url(r'^password/reset/success/$', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'),
# Profile
url(r'^profile/$', views.UserProfileView.as_view(), name='user-profile'),
......@@ -29,23 +29,23 @@ urlpatterns = [
url(r'^profile/otp/settings-success/$', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
# User view
url(r'^user$', views.UserListView.as_view(), name='user-list'),
url(r'^user/export/', views.UserExportView.as_view(), name='user-export'),
url(r'^user/$', views.UserListView.as_view(), name='user-list'),
url(r'^user/export/$', views.UserExportView.as_view(), name='user-export'),
url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
url(r'^user/import/$', views.UserBulkImportView.as_view(), name='user-import'),
url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/update$', views.UserUpdateView.as_view(), name='user-update'),
url(r'^user/update$', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})$', views.UserDetailView.as_view(), name='user-detail'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history', views.UserDetailView.as_view(), name='user-login-history'),
url(r'^user/create/$', views.UserCreateView.as_view(), name='user-create'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.UserUpdateView.as_view(), name='user-update'),
url(r'^user/update/$', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.UserDetailView.as_view(), name='user-detail'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history/$', views.UserDetailView.as_view(), name='user-login-history'),
# User group view
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
url(r'^user-group/create$', views.UserGroupCreateView.as_view(), name='user-group-create'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/update$', views.UserGroupUpdateView.as_view(), name='user-group-update'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
url(r'^user-group/$', views.UserGroupListView.as_view(), name='user-group-list'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
url(r'^user-group/create/$', views.UserGroupCreateView.as_view(), name='user-group-create'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.UserGroupUpdateView.as_view(), name='user-group-update'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
# Login log
url(r'^login-log/$', views.LoginLogListView.as_view(), name='login-log-list'),
......
......@@ -13,7 +13,7 @@ import ipaddress
from django.http import Http404
from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth import authenticate, login as auth_login
from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _
from django.core.cache import cache
......@@ -200,16 +200,15 @@ def get_login_ip(request):
return login_ip
def write_login_log(username, type='', ip='', user_agent=''):
def write_login_log(*args, **kwargs):
ip = kwargs.get('ip', '')
if not (ip and validate_ip(ip)):
ip = ip[:15]
city = "Unknown"
else:
city = get_ip_city(ip)
LoginLog.objects.create(
username=username, type=type,
ip=ip, city=city, user_agent=user_agent
)
kwargs.update({'ip': ip, 'city': city})
LoginLog.objects.create(**kwargs)
def get_ip_city(ip, timeout=10):
......@@ -332,3 +331,44 @@ def check_password_rules(password):
match_obj = re.match(pattern, password)
return bool(match_obj)
def set_user_login_failed_count_to_cache(key_limit, key_block):
count = cache.get(key_limit)
count = count + 1 if count else 1
setting_limit_time = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_TIME'
).first()
limit_time = setting_limit_time.cleaned_value if setting_limit_time \
else settings.DEFAULT_LOGIN_LIMIT_TIME
setting_limit_count = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_COUNT'
).first()
limit_count = setting_limit_count.cleaned_value if setting_limit_count \
else settings.DEFAULT_LOGIN_LIMIT_COUNT
if count >= limit_count:
cache.set(key_block, 1, int(limit_time)*60)
cache.set(key_limit, count, int(limit_time)*60)
def is_block_login(key_limit):
count = cache.get(key_limit)
setting_limit_count = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_COUNT'
).first()
limit_count = setting_limit_count.cleaned_value if setting_limit_count \
else settings.DEFAULT_LOGIN_LIMIT_COUNT
if count and count >= limit_count:
return True
def is_need_unblock(key_block):
if not cache.get(key_block):
return False
return True
......@@ -25,8 +25,10 @@ from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from common.models import Setting
from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \
get_user_or_tmp_user, set_tmp_user_to_cache, get_password_check_rules, check_password_rules
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \
redirect_user_first_login_or_index, get_user_or_tmp_user, \
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
is_block_login, set_user_login_failed_count_to_cache
from ..tasks import write_login_log_async
from .. import forms
......@@ -47,7 +49,9 @@ class UserLoginView(FormView):
form_class = forms.UserLoginForm
form_class_captcha = forms.UserLoginCaptchaForm
redirect_field_name = 'next'
key_prefix = "_LOGIN_INVALID_{}"
key_prefix_captcha = "_LOGIN_INVALID_{}"
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
def get(self, request, *args, **kwargs):
if request.user.is_staff:
......@@ -57,6 +61,16 @@ class UserLoginView(FormView):
request.session.set_test_cookie()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# limit login authentication
ip = get_login_ip(request)
username = self.request.POST.get('username')
key_limit = self.key_prefix_limit.format(username, ip)
if is_block_login(key_limit):
return self.render_to_response(self.get_context_data(block_login=True))
return super().post(request, *args, **kwargs)
def form_valid(self, form):
if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again."))
......@@ -65,8 +79,24 @@ class UserLoginView(FormView):
return redirect(self.get_success_url())
def form_invalid(self, form):
# write login failed log
username = form.cleaned_data.get('username')
data = {
'username': username,
'mfa': LoginLog.MFA_UNKNOWN,
'reason': LoginLog.REASON_PASSWORD,
'status': False
}
self.write_login_log(data)
# limit user login failed count
ip = get_login_ip(self.request)
cache.set(self.key_prefix.format(ip), 1, 3600)
key_limit = self.key_prefix_limit.format(username, ip)
key_block = self.key_prefix_block.format(username)
set_user_login_failed_count_to_cache(key_limit, key_block)
# show captcha
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
old_form = form
form = self.form_class_captcha(data=form.data)
form._errors = old_form.errors
......@@ -74,7 +104,7 @@ class UserLoginView(FormView):
def get_form_class(self):
ip = get_login_ip(self.request)
if cache.get(self.key_prefix.format(ip)):
if cache.get(self.key_prefix_captcha.format(ip)):
return self.form_class_captcha
else:
return self.form_class
......@@ -91,7 +121,13 @@ class UserLoginView(FormView):
elif not user.otp_enabled:
# 0 & T,F
auth_login(self.request, user)
self.write_login_log()
data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def get_context_data(self, **kwargs):
......@@ -101,13 +137,16 @@ class UserLoginView(FormView):
kwargs.update(context)
return super().get_context_data(**kwargs)
def write_login_log(self):
def write_login_log(self, data):
login_ip = get_login_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay(
self.request.user.username, type='W',
ip=login_ip, user_agent=user_agent
)
tmp_data = {
'ip': login_ip,
'type': 'W',
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserLoginOtpView(FormView):
......@@ -122,22 +161,38 @@ class UserLoginOtpView(FormView):
if check_otp_code(otp_secret_key, otp_code):
auth_login(self.request, user)
self.write_login_log()
data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect(self.get_success_url())
else:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(data)
form.add_error('otp_code', _('MFA code invalid'))
return super().form_invalid(form)
def get_success_url(self):
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def write_login_log(self):
def write_login_log(self, data):
login_ip = get_login_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay(
self.request.user.username, type='W',
ip=login_ip, user_agent=user_agent
)
tmp_data = {
'ip': login_ip,
'type': 'W',
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
@method_decorator(never_cache, name='dispatch')
......
......@@ -36,7 +36,9 @@ from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.models import Setting
from .. import forms
from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_user_or_tmp_user, get_password_check_rules, check_password_rules
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, \
get_user_or_tmp_user, get_password_check_rules, check_password_rules, \
is_need_unblock
from ..signals import post_user_create
from ..tasks import write_login_log_async
......@@ -168,13 +170,17 @@ class UserDetailView(AdminUserRequiredMixin, DetailView):
model = User
template_name = 'users/user_detail.html'
context_object_name = "user_object"
key_prefix_block = "_LOGIN_BLOCK_{}"
def get_context_data(self, **kwargs):
user = self.get_object()
key_block = self.key_prefix_block.format(user.username)
groups = UserGroup.objects.exclude(id__in=self.object.groups.all())
context = {
'app': _('Users'),
'action': _('User detail'),
'groups': groups
'groups': groups,
'unblock': is_need_unblock(key_block),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......
......@@ -21,10 +21,10 @@ class Config:
ALLOWED_HOSTS = ['*']
# Development env open this, when error occur display the full process track, Production disable it
DEBUG = True
DEBUG = os.environ.get("DEBUG") or True
# DEBUG, INFO, WARNING, ERROR, CRITICAL can set. See https://docs.djangoproject.com/en/1.10/topics/logging/
LOG_LEVEL = 'DEBUG'
LOG_LEVEL = os.environ.get("LOG_LEVEL") or 'DEBUG'
LOG_DIR = os.path.join(BASE_DIR, 'logs')
# Database setting, Support sqlite3, mysql, postgres ....
......@@ -35,12 +35,12 @@ class Config:
DB_NAME = os.path.join(BASE_DIR, 'data', 'db.sqlite3')
# MySQL or postgres setting like:
# DB_ENGINE = 'mysql'
# DB_HOST = '127.0.0.1'
# DB_PORT = 3306
# DB_USER = 'root'
# DB_PASSWORD = ''
# DB_NAME = 'jumpserver'
# DB_ENGINE = os.environ.get("DB_ENGINE") or 'mysql'
# DB_HOST = os.environ.get("DB_HOST") or '127.0.0.1'
# DB_PORT = os.environ.get("DB_PORT") or 3306
# DB_USER = os.environ.get("DB_USER") or 'jumpserver'
# DB_PASSWORD = os.environ.get("DB_PASSWORD") or 'weakPassword'
# DB_NAME = os.environ.get("DB_NAME") or 'jumpserver'
# When Django start it will bind this host and port
# ./manage.py runserver 127.0.0.1:8080
......@@ -48,9 +48,11 @@ class Config:
HTTP_LISTEN_PORT = 8080
# Use Redis as broker for celery and web socket
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_PASSWORD = ''
REDIS_HOST = os.environ.get("REDIS_HOST") or '127.0.0.1'
REDIS_PORT = os.environ.get("REDIS_PORT") or 6379
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD") or ''
REDIS_DB_CELERY = os.environ.get('REDIS_DB') or 3
REDIS_DB_CACHE = os.environ.get('REDIS_DB') or 4
def __init__(self):
pass
......
libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake
libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake libkrb5-dev
......@@ -61,7 +61,7 @@ pytz==2018.3
PyYAML==3.12
redis==2.10.6
requests==2.18.4
jms-storage==0.0.17
jms-storage==0.0.18
s3transfer==0.1.13
simplejson==3.13.2
six==1.11.0
......
......@@ -4,3 +4,5 @@
python3 ../apps/manage.py makemigrations
python3 ../apps/manage.py migrate
python3 ../apps/manage.py makemigrations --merge
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