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