diff --git a/README.md b/README.md index df8324761..f0bceec9b 100644 --- a/README.md +++ b/README.md @@ -19,25 +19,25 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点 ---- ### 功能 - + ![Jumpserver功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver13.jpg "Jumpserver功能") ### 开始使用 -快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html) +快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html) -一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html) +一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html) 也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org) -### Demo 和 截图 +### Demo 和 截图 我们提供了DEMO和截图可以让你快速了解Jumpserver [DEMO](http://demo.jumpserver.org) [截图](http://docs.jumpserver.org/zh/docs/snapshot.html) -### SDK +### SDK 我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互, diff --git a/apps/__init__.py b/apps/__init__.py index 7e96164aa..be40e1dd2 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -2,4 +2,4 @@ # -*- coding: utf-8 -*- # -__version__ = "1.3.2" +__version__ = "1.3.3" diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index ff5047ba3..8c1f3d726 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # +import random + from rest_framework import generics from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet @@ -22,7 +24,8 @@ from ..utils import LabelFilter logger = get_logger(__file__) __all__ = [ 'AssetViewSet', 'AssetListUpdateApi', - 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi' + 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi', + 'AssetGatewayApi' ] @@ -106,3 +109,20 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): asset = get_object_or_404(Asset, pk=asset_id) task = test_asset_connectability_manual.delay(asset) return Response({"task": task.id}) + + +class AssetGatewayApi(generics.RetrieveAPIView): + queryset = Asset.objects.all() + permission_classes = (IsSuperUserOrAppUser,) + + def retrieve(self, request, *args, **kwargs): + asset_id = kwargs.get('pk') + asset = get_object_or_404(Asset, pk=asset_id) + + if asset.domain and \ + asset.domain.gateways.filter(protocol=asset.protocol).exists(): + gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol)) + serializer = serializers.GatewayWithAuthSerializer(instance=gateway) + return Response(serializer.data) + else: + return Response({"msg": "Not have gateway"}, status=404) \ No newline at end of file diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index f8f187b4d..5000c087d 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -16,7 +16,7 @@ class AssetCreateForm(forms.ModelForm): fields = [ 'hostname', 'ip', 'public_ip', 'port', 'comment', 'nodes', 'is_active', 'admin_user', 'labels', 'platform', - 'domain', + 'domain', 'protocol', ] widgets = { @@ -56,7 +56,7 @@ class AssetUpdateForm(forms.ModelForm): fields = [ 'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform', 'public_ip', 'number', 'comment', 'admin_user', 'labels', - 'domain', + 'domain', 'protocol', ] widgets = { 'nodes': forms.SelectMultiple(attrs={ diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py index 2295dc005..b25e19d87 100644 --- a/apps/assets/forms/user.py +++ b/apps/assets/forms/user.py @@ -93,14 +93,21 @@ class SystemUserForm(PasswordAndKeyAuthForm): # Because we define custom field, so we need rewrite :method: `save` system_user = super().save() password = self.cleaned_data.get('password', '') or None + login_mode = self.cleaned_data.get('login_mode', '') or None + protocol = self.cleaned_data.get('protocol') or None auto_generate_key = self.cleaned_data.get('auto_generate_key', False) private_key, public_key = super().gen_keys() + if login_mode == SystemUser.MANUAL_LOGIN or protocol == SystemUser.TELNET_PROTOCOL: + system_user.auto_push = 0 + system_user.save() + if auto_generate_key: logger.info('Auto generate key and set system user auth') system_user.auto_gen_auth() else: system_user.set_auth(password=password, private_key=private_key, public_key=public_key) + return system_user def clean(self): @@ -109,12 +116,24 @@ class SystemUserForm(PasswordAndKeyAuthForm): if not self.instance and not auto_generate: super().validate_password_key() + def is_valid(self): + validated = super().is_valid() + username = self.cleaned_data.get('username') + login_mode = self.cleaned_data.get('login_mode') + if login_mode == SystemUser.AUTO_LOGIN and not username: + self.add_error( + "username", _('* Automatic login mode,' + ' must fill in the username.') + ) + return False + return validated + class Meta: model = SystemUser fields = [ 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auto_push', 'sudo', - 'comment', 'shell', 'priority', + 'comment', 'shell', 'priority', 'login_mode', ] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), @@ -124,5 +143,8 @@ class SystemUserForm(PasswordAndKeyAuthForm): 'name': '* required', 'username': '* required', 'auto_push': _('Auto push system user to asset'), - 'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'), - } \ No newline at end of file + 'priority': _('High level will be using login asset as default, ' + 'if user was granted more than 2 system user'), + 'login_mode': _('If you choose manual login mode, you do not ' + 'need to fill in the username and password.') + } diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index a974d3385..7a2b3fe57 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -57,13 +57,27 @@ class Asset(models.Model): ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), + ('Windows2016', 'Windows(2016)'), ('Other', 'Other'), ) + + SSH_PROTOCOL = 'ssh' + RDP_PROTOCOL = 'rdp' + TELNET_PROTOCOL = 'telnet' + PROTOCOL_CHOICES = ( + (SSH_PROTOCOL, 'ssh'), + (RDP_PROTOCOL, 'rdp'), + (TELNET_PROTOCOL, 'telnet (beta)'), + ) + id = models.UUIDField(default=uuid.uuid4, primary_key=True) ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) + protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, + choices=PROTOCOL_CHOICES, + verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform')) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index cb9bb96ae..908e6b647 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -19,7 +19,7 @@ signer = get_signer() class AssetUser(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) - username = models.CharField(max_length=32, verbose_name=_('Username'), validators=[alphanumeric]) + username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric]) _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) _public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key')) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index bf31b8491..5faca5da8 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -95,9 +95,18 @@ class AdminUser(AssetUser): class SystemUser(AssetUser): SSH_PROTOCOL = 'ssh' RDP_PROTOCOL = 'rdp' + TELNET_PROTOCOL = 'telnet' PROTOCOL_CHOICES = ( (SSH_PROTOCOL, 'ssh'), (RDP_PROTOCOL, 'rdp'), + (TELNET_PROTOCOL, 'telnet (beta)'), + ) + + AUTO_LOGIN = 'auto' + MANUAL_LOGIN = 'manual' + LOGIN_MODE_CHOICES = ( + (AUTO_LOGIN, _('Automatic login')), + (MANUAL_LOGIN, _('Manually login')) ) nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) @@ -107,6 +116,7 @@ class SystemUser(AssetUser): auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) + login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode')) def __str__(self): return '{0.name}({0.username})'.format(self) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index a0fdfab73..e63735794 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): fields = ( "id", "hostname", "ip", "port", "system_users_granted", "is_active", "system_users_join", "os", 'domain', - "platform", "comment" + "platform", "comment", "protocol", ) @staticmethod diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 7abd09d29..a295f245c 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -18,6 +18,13 @@ class SystemUserSerializer(serializers.ModelSerializer): model = SystemUser exclude = ('_password', '_private_key', '_public_key') + def get_field_names(self, declared_fields, info): + fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) + fields.extend([ + 'get_login_mode_display', + ]) + return fields + @staticmethod def get_unreachable_assets(obj): return obj.unreachable_assets @@ -46,7 +53,7 @@ class SystemUserAuthSerializer(AuthSerializer): model = SystemUser fields = [ "id", "name", "username", "protocol", - "password", "private_key", + "login_mode", "password", "private_key", ] @@ -56,7 +63,10 @@ class AssetSystemUserSerializer(serializers.ModelSerializer): """ class Meta: model = SystemUser - fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',) + fields = ( + 'id', 'name', 'username', 'priority', + 'protocol', 'comment', 'login_mode' + ) class SystemUserSimpleSerializer(serializers.ModelSerializer): diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html index 314967d22..4e1bc51a8 100644 --- a/apps/assets/templates/assets/_system_user.html +++ b/apps/assets/templates/assets/_system_user.html @@ -36,12 +36,13 @@ {% endif %}

{% trans 'Basic' %}

{% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.login_mode layout="horizontal" %} {% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.priority layout="horizontal" %} {% bootstrap_field form.protocol layout="horizontal" %} +

{% trans 'Auth' %}

{% block auth %} -

{% trans 'Auth' %}

@@ -80,15 +81,22 @@ {% endblock %} {% block custom_foot_js %} {% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index bcb40fb29..15a870800 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -5,7 +5,7 @@ {% block help_message %}
- 管理用户是服务器的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。 + 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。 Windows或其它硬件可以随意设置一个
{% endblock %} @@ -107,6 +107,3 @@ $(document).ready(function(){ }); {% endblock %} - - - diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index 99703d2e3..55e233d0d 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -17,6 +17,7 @@ {% bootstrap_field form.hostname layout="horizontal" %} {% bootstrap_field form.platform layout="horizontal" %} {% bootstrap_field form.ip layout="horizontal" %} + {% bootstrap_field form.protocol layout="horizontal" %} {% bootstrap_field form.port layout="horizontal" %} {% bootstrap_field form.public_ip layout="horizontal" %} {% bootstrap_field form.domain layout="horizontal" %} @@ -85,14 +86,14 @@ $(document).ready(function () { allowClear: true, templateSelection: format }); - $("#id_platform").change(function (){ - var platform = $("#id_platform option:selected").text(); + $("#id_protocol").change(function (){ + var protocol = $("#id_protocol option:selected").text(); var port = 22; - if(platform === 'Windows'){ + if(protocol === 'rdp'){ port = 3389; } - if(platform === 'Other'){ - port = null; + if(protocol === 'telnet (beta)'){ + port = 23; } $("#id_port").val(port); }); diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html index 3d42ca2b5..7ed1da05a 100644 --- a/apps/assets/templates/assets/asset_update.html +++ b/apps/assets/templates/assets/asset_update.html @@ -21,6 +21,7 @@

{% trans 'Basic' %}

{% bootstrap_field form.hostname layout="horizontal" %} {% bootstrap_field form.ip layout="horizontal" %} + {% bootstrap_field form.protocol layout="horizontal" %} {% bootstrap_field form.port layout="horizontal" %} {% bootstrap_field form.platform layout="horizontal" %} {% bootstrap_field form.public_ip layout="horizontal" %} diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html index 926c4bbc3..a4042d57e 100644 --- a/apps/assets/templates/assets/domain_list.html +++ b/apps/assets/templates/assets/domain_list.html @@ -1,6 +1,14 @@ {% extends '_base_list.html' %} {% load i18n static %} {% block table_search %}{% endblock %} + +{% block help_message %} +
+ 网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登 +录。 +
+{% endblock %} + {% block table_container %}
{% trans "Create domain" %} @@ -69,6 +77,3 @@ $(document).ready(function(){ }); {% endblock %} - - - diff --git a/apps/assets/templates/assets/gateway_create_update.html b/apps/assets/templates/assets/gateway_create_update.html index e0b10ba73..fea540385 100644 --- a/apps/assets/templates/assets/gateway_create_update.html +++ b/apps/assets/templates/assets/gateway_create_update.html @@ -42,7 +42,7 @@ {% bootstrap_field form.domain layout="horizontal" %} {% block auth %} -

{% trans 'Auth' %}

+

{% trans 'Auth' %}

{% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.password layout="horizontal" %} @@ -72,14 +72,23 @@ var protocol_id = '#' + '{{ form.protocol.id_for_label }}'; var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}'; var port = '#' + '{{ form.port.id_for_label }}'; +var username = '#' + '{{ form.username.id_for_label }}'; +var password = '#' + '{{ form.password.id_for_label }}'; +var auth_title = '#auth_title'; function protocolChange() { if ($(protocol_id + " option:selected").text() === 'rdp') { - $(port).val(3389); - $(private_key_id).closest('.form-group').addClass('hidden') + {#$(port).val(3389);#} + $(private_key_id).closest('.form-group').addClass('hidden'); + $(username).closest('.form-group').addClass('hidden'); + $(password).closest('.form-group').addClass('hidden'); + $(auth_title).addClass('hidden'); } else { - $(port).val(22); - $(private_key_id).closest('.form-group').removeClass('hidden') + {#$(port).val(22);#} + $(private_key_id).closest('.form-group').removeClass('hidden'); + $(username).closest('.form-group').removeClass('hidden'); + $(password).closest('.form-group').removeClass('hidden'); + $(auth_title).removeClass('hidden'); } } diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html index 9a7bc255a..dd188bdab 100644 --- a/apps/assets/templates/assets/system_user_detail.html +++ b/apps/assets/templates/assets/system_user_detail.html @@ -62,6 +62,10 @@ {% trans 'Username' %}: {{ system_user.username }} + + {% trans 'Login mode' %}: + {{ system_user.get_login_mode_display }} + {% trans 'Protocol' %}: {{ system_user.protocol }} @@ -148,15 +152,14 @@ - - - {% trans 'Clear auth' %}: - - - - - - +{# #} +{# {% trans 'Clear auth' %}:#} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} {# #} {# {% trans 'Change auth period' %}:#} @@ -333,10 +336,22 @@ $(document).ready(function () { }); }).on('click', '.btn-clear-auth', function () { var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}'; - APIUpdateAttr({ - url: the_url, - method: 'DELETE', - success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}" + var name = '{{ system_user.name }}'; + swal({ + title: '你确定清除该系统用户的认证信息吗 ?', + text: " [" + name + "] ", + type: "warning", + showCancelButton: true, + cancelButtonText: '取消', + confirmButtonColor: "#ed5565", + confirmButtonText: '确认', + closeOnConfirm: true + }, function () { + APIUpdateAttr({ + url: the_url, + method: 'DELETE', + success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}" + }); }); }) diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index 3daa47a81..3fa887df6 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -26,6 +26,7 @@ {% trans 'Name' %} {% trans 'Username' %} {% trans 'Protocol' %} + {% trans 'Login mode' %} {% trans 'Asset' %} {% trans 'Reachable' %} {% trans 'Unreachable' %} @@ -48,7 +49,7 @@ function initTable() { var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, - {targets: 5, createdCell: function (td, cellData) { + {targets: 6, createdCell: function (td, cellData) { var innerHtml = ""; if (cellData !== 0) { innerHtml = "" + cellData + ""; @@ -57,7 +58,7 @@ function initTable() { } $(td).html('' + innerHtml + ''); }}, - {targets: 6, createdCell: function (td, cellData) { + {targets: 7, createdCell: function (td, cellData) { var innerHtml = ""; if (cellData !== 0) { innerHtml = "" + cellData + ""; @@ -66,7 +67,7 @@ function initTable() { } $(td).html('' + innerHtml + ''); }}, - {targets: 7, createdCell: function (td, cellData, rowData) { + {targets: 8, createdCell: function (td, cellData, rowData) { var val = 0; var innerHtml = ""; var total = rowData.assets_amount; @@ -84,14 +85,14 @@ function initTable() { $(td).html('' + innerHtml + ''); }}, - {targets: 9, createdCell: function (td, cellData, rowData) { + {targets: 10, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) }}], ajax_url: '{% url "api-assets:system-user-list" %}', columns: [ - {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "assets_amount" }, + {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" }, {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" } ], op_html: $('#actions').html() diff --git a/apps/assets/templates/assets/system_user_update.html b/apps/assets/templates/assets/system_user_update.html index 7e1590db5..977c3ac31 100644 --- a/apps/assets/templates/assets/system_user_update.html +++ b/apps/assets/templates/assets/system_user_update.html @@ -4,7 +4,6 @@ {% load bootstrap3 %} {% block auth %} -

{% trans 'Auth' %}

{% bootstrap_field form.password layout="horizontal" %} {% bootstrap_field form.private_key_file layout="horizontal" %}
diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index ce622d648..cf55d08ba 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -23,6 +23,8 @@ urlpatterns = [ api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'), url(r'^v1/assets/(?P[0-9a-zA-Z\-]{36})/alive/$', api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'), + url(r'^v1/assets/(?P[0-9a-zA-Z\-]{36})/gateway/$', + api.AssetGatewayApi.as_view(), name='asset-gateway'), url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/nodes/$', api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'), url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/auth/$', diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 418aa63a3..12c351154 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -50,4 +50,3 @@ urlpatterns = [ url(r'^domain/(?P[0-9a-zA-Z\-]{36})/gateway/create/$', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'), url(r'^domain/gateway/(?P[0-9a-zA-Z\-]{36})/update/$', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'), ] - diff --git a/apps/assets/utils.py b/apps/assets/utils.py index cc4942ade..f841e4a79 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -54,7 +54,8 @@ def test_gateway_connectability(gateway): proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: - proxy.connect(gateway.ip, username=gateway.username, + proxy.connect(gateway.ip, gateway.port, + username=gateway.username, password=gateway.password, pkey=gateway.private_key_obj) except(paramiko.AuthenticationException, diff --git a/apps/assets/views/domain.py b/apps/assets/views/domain.py index e6a353373..be6528219 100644 --- a/apps/assets/views/domain.py +++ b/apps/assets/views/domain.py @@ -140,11 +140,6 @@ class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView): domain = self.object.domain return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id}) - def form_valid(self, form): - response = super().form_valid(form) - print(form.cleaned_data) - return response - def get_context_data(self, **kwargs): context = { 'app': _('Assets'), diff --git a/apps/common/forms.py b/apps/common/forms.py index 87b2f4f12..a11420498 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -170,7 +170,7 @@ class TerminalSettingForm(BaseForm): class SecuritySettingForm(BaseForm): - # MFA全局设置 + # MFA global setting SECURITY_MFA_AUTH = forms.BooleanField( initial=False, required=False, label=_("MFA Secondary certification"), @@ -179,12 +179,26 @@ class SecuritySettingForm(BaseForm): 'authentication (valid for all users, including administrators)' ) ) - # 最小长度 + # limit login count + SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField( + initial=3, min_value=3, + label=_("Limit the number of login failures") + ) + # limit login time + SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField( + initial=30, min_value=5, + label=_("No logon interval"), + help_text=_( + "Tip :(unit/minute) if the user has failed to log in for a limited " + "number of times, no login is allowed during this time interval." + ) + ) + # min length SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( initial=6, label=_("Password minimum length"), min_value=6 ) - # 大写字母 + # upper case SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField( initial=False, required=False, @@ -193,21 +207,21 @@ class SecuritySettingForm(BaseForm): 'After opening, the user password changes ' 'and resets must contain uppercase letters') ) - # 小写字母 + # lower case SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField( initial=False, required=False, label=_("Must contain lowercase letters"), help_text=_('After opening, the user password changes ' 'and resets must contain lowercase letters') ) - # 数字 + # number SECURITY_PASSWORD_NUMBER = forms.BooleanField( initial=False, required=False, label=_("Must contain numeric characters"), help_text=_('After opening, the user password changes ' 'and resets must contain numeric characters') ) - # 特殊字符 + # special char SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField( initial=False, required=False, label=_("Must contain special characters"), diff --git a/apps/common/templates/common/security_setting.html b/apps/common/templates/common/security_setting.html index 2260b76b9..08d978d23 100644 --- a/apps/common/templates/common/security_setting.html +++ b/apps/common/templates/common/security_setting.html @@ -39,9 +39,9 @@ {% endif %} {% csrf_token %} -

{% trans "MFA setting" %}

+

{% trans "User login settings" %}

{% for field in form %} - {% if forloop.counter == 2 %} + {% if forloop.counter == 4 %}

{% trans "Password check rule" %}

{% endif %} diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index 7d2f547cd..50e22bf55 100644 Binary files a/apps/i18n/zh/LC_MESSAGES/django.mo and b/apps/i18n/zh/LC_MESSAGES/django.mo differ diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index 705f611dc..9e3766748 100644 --- a/apps/i18n/zh/LC_MESSAGES/django.po +++ b/apps/i18n/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-06-07 11:34+0800\n" +"POT-Creation-Date: 2018-07-13 19:20+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -29,38 +29,38 @@ msgstr "" msgid "测试节点下资产是否可连接: {}" msgstr "" -#: assets/forms/asset.py:24 assets/models/asset.py:75 assets/models/user.py:103 +#: assets/forms/asset.py:24 assets/models/asset.py:89 assets/models/user.py:112 #: assets/templates/assets/asset_detail.html:183 #: assets/templates/assets/asset_detail.html:191 -#: assets/templates/assets/system_user_detail.html:175 perms/models.py:33 +#: assets/templates/assets/system_user_detail.html:178 perms/models.py:33 msgid "Nodes" msgstr "节点管理" #: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109 -#: assets/forms/asset.py:113 assets/models/asset.py:80 +#: assets/forms/asset.py:113 assets/models/asset.py:94 #: assets/models/cluster.py:19 assets/models/user.py:72 #: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25 msgid "Admin user" msgstr "管理用户" #: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125 -#: assets/templates/assets/asset_create.html:35 -#: assets/templates/assets/asset_create.html:37 +#: assets/templates/assets/asset_create.html:36 +#: assets/templates/assets/asset_create.html:38 #: assets/templates/assets/asset_list.html:75 -#: assets/templates/assets/asset_update.html:40 -#: assets/templates/assets/asset_update.html:42 +#: assets/templates/assets/asset_update.html:41 +#: assets/templates/assets/asset_update.html:43 #: assets/templates/assets/user_asset_list.html:34 msgid "Label" msgstr "标签" -#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:71 +#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:85 #: assets/models/domain.py:46 msgid "Domain" msgstr "网域" #: assets/forms/asset.py:38 assets/forms/asset.py:63 assets/forms/asset.py:77 -#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:29 -#: assets/templates/assets/asset_update.html:34 perms/forms.py:40 +#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:30 +#: assets/templates/assets/asset_update.html:35 perms/forms.py:40 #: perms/forms.py:47 perms/models.py:76 #: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:142 @@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, msgid "Select assets" msgstr "选择资产" -#: assets/forms/asset.py:105 assets/models/asset.py:67 +#: assets/forms/asset.py:105 assets/models/asset.py:81 #: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53 #: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/domain_gateway_list.html:58 @@ -99,11 +99,11 @@ msgid "Port" msgstr "端口" #: assets/forms/domain.py:14 assets/forms/label.py:13 -#: assets/models/asset.py:223 assets/templates/assets/admin_user_list.html:25 +#: assets/models/asset.py:237 assets/templates/assets/admin_user_list.html:25 #: assets/templates/assets/domain_detail.html:60 -#: assets/templates/assets/domain_list.html:15 +#: assets/templates/assets/domain_list.html:23 #: assets/templates/assets/label_list.html:16 -#: assets/templates/assets/system_user_list.html:29 audits/models.py:11 +#: assets/templates/assets/system_user_list.html:30 audits/models.py:11 #: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:37 #: perms/models.py:32 @@ -118,14 +118,14 @@ msgstr "端口" msgid "Asset" msgstr "资产" -#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:120 +#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:139 #: assets/models/base.py:21 assets/models/cluster.py:18 #: assets/models/domain.py:17 assets/models/group.py:20 #: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_list.html:23 #: assets/templates/assets/domain_detail.html:56 #: assets/templates/assets/domain_gateway_list.html:56 -#: assets/templates/assets/domain_list.html:14 +#: assets/templates/assets/domain_list.html:22 #: assets/templates/assets/label_list.html:14 #: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_list.html:26 common/models.py:26 @@ -147,16 +147,16 @@ msgstr "资产" msgid "Name" msgstr "名称" -#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:121 +#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:140 #: assets/models/base.py:22 assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_list.html:24 #: assets/templates/assets/domain_gateway_list.html:60 #: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_list.html:27 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13 -#: users/forms.py:21 users/forms.py:30 users/models/authentication.py:45 -#: users/models/user.py:47 users/templates/users/_select_user_modal.html:14 -#: users/templates/users/login.html:56 +#: users/forms.py:31 users/models/authentication.py:70 users/models/user.py:47 +#: users/templates/users/_select_user_modal.html:14 +#: users/templates/users/login.html:60 #: users/templates/users/login_log_list.html:49 #: users/templates/users/user_detail.html:67 #: users/templates/users/user_list.html:24 @@ -169,8 +169,8 @@ msgid "Password or private key passphrase" msgstr "密码或密钥密码" #: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113 -#: users/forms.py:15 users/forms.py:23 users/forms.py:32 users/forms.py:44 -#: users/templates/users/login.html:59 +#: users/forms.py:15 users/forms.py:33 users/forms.py:45 +#: users/templates/users/login.html:63 #: users/templates/users/reset_password.html:53 #: users/templates/users/user_create.html:10 #: users/templates/users/user_password_authentication.html:14 @@ -192,17 +192,27 @@ msgstr "ssh密钥不合法" msgid "Password and private key file must be input one" msgstr "密码和私钥, 必须输入一个" -#: assets/forms/user.py:126 +#: assets/forms/user.py:125 +msgid "* Automatic login mode, must fill in the username." +msgstr "自动登录模式,必须填写用户名" + +#: assets/forms/user.py:145 msgid "Auto push system user to asset" msgstr "自动推送系统用户到资产" -#: assets/forms/user.py:127 +#: assets/forms/user.py:146 msgid "" "High level will be using login asset as default, if user was granted more " "than 2 system user" msgstr "高优先级的系统用户将会作为默认登录用户" -#: assets/models/asset.py:63 assets/models/domain.py:43 +#: assets/forms/user.py:148 +msgid "" +"If you choose manual login mode, you do not need to fill in the username and " +"password." +msgstr "如果选择手动登录模式,用户名和密码则不需要填写" + +#: assets/models/asset.py:74 assets/models/domain.py:43 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/asset_detail.html:61 @@ -217,7 +227,7 @@ msgstr "高优先级的系统用户将会作为默认登录用户" msgid "IP" msgstr "IP" -#: assets/models/asset.py:66 assets/templates/assets/_asset_list_modal.html:45 +#: assets/models/asset.py:77 assets/templates/assets/_asset_list_modal.html:45 #: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/asset_detail.html:57 #: assets/templates/assets/asset_list.html:86 @@ -229,98 +239,107 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:69 assets/templates/assets/asset_detail.html:97 +#: assets/models/asset.py:80 assets/models/domain.py:45 +#: assets/models/user.py:115 +#: assets/templates/assets/domain_gateway_list.html:59 +#: assets/templates/assets/system_user_detail.html:70 +#: assets/templates/assets/system_user_list.html:28 +#: terminal/templates/terminal/session_list.html:75 +msgid "Protocol" +msgstr "协议" + +#: assets/models/asset.py:83 assets/templates/assets/asset_detail.html:97 msgid "Platform" msgstr "系统平台" -#: assets/models/asset.py:76 assets/models/domain.py:48 +#: assets/models/asset.py:90 assets/models/domain.py:48 #: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:85 assets/templates/assets/asset_detail.html:65 +#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:65 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:113 +#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:113 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:77 +#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:77 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:81 +#: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:81 msgid "Model" msgstr "型号" -#: assets/models/asset.py:95 assets/templates/assets/asset_detail.html:109 +#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:109 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:98 +#: assets/models/asset.py:112 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:99 +#: assets/models/asset.py:113 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:100 +#: assets/models/asset.py:114 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:102 assets/templates/assets/asset_detail.html:89 +#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:89 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:104 +#: assets/models/asset.py:118 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:106 +#: assets/models/asset.py:120 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:101 +#: assets/models/asset.py:123 assets/templates/assets/asset_detail.html:101 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:111 +#: assets/models/asset.py:125 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:113 +#: assets/models/asset.py:127 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:115 +#: assets/models/asset.py:129 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:119 assets/templates/assets/asset_create.html:33 +#: assets/models/asset.py:133 assets/templates/assets/asset_create.html:34 #: assets/templates/assets/asset_detail.html:220 -#: assets/templates/assets/asset_update.html:38 templates/_nav.html:27 +#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27 msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:121 assets/models/base.py:29 +#: assets/models/asset.py:135 assets/models/base.py:29 #: assets/models/cluster.py:28 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/asset_detail.html:117 #: assets/templates/assets/domain_detail.html:72 -#: assets/templates/assets/system_user_detail.html:96 +#: assets/templates/assets/system_user_detail.html:100 #: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81 #: perms/templates/perms/asset_permission_detail.html:98 #: users/models/user.py:90 users/templates/users/user_detail.html:111 msgid "Created by" msgstr "创建者" -#: assets/models/asset.py:124 assets/models/cluster.py:26 +#: assets/models/asset.py:138 assets/models/cluster.py:26 #: assets/models/domain.py:20 assets/models/group.py:22 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/domain_detail.html:68 -#: assets/templates/assets/system_user_detail.html:92 +#: assets/templates/assets/system_user_detail.html:96 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63 #: perms/models.py:39 perms/models.py:82 #: perms/templates/perms/asset_permission_detail.html:94 @@ -329,7 +348,7 @@ msgstr "创建者" msgid "Date created" msgstr "创建日期" -#: assets/models/asset.py:126 assets/models/base.py:26 +#: assets/models/asset.py:140 assets/models/base.py:26 #: assets/models/cluster.py:29 assets/models/domain.py:18 #: assets/models/domain.py:47 assets/models/group.py:23 #: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72 @@ -337,9 +356,9 @@ msgstr "创建日期" #: assets/templates/assets/asset_detail.html:125 #: assets/templates/assets/domain_detail.html:76 #: assets/templates/assets/domain_gateway_list.html:61 -#: assets/templates/assets/domain_list.html:17 -#: assets/templates/assets/system_user_detail.html:100 -#: assets/templates/assets/system_user_list.html:33 common/models.py:30 +#: assets/templates/assets/domain_list.html:25 +#: assets/templates/assets/system_user_detail.html:104 +#: assets/templates/assets/system_user_list.html:34 common/models.py:30 #: ops/models/adhoc.py:42 perms/models.py:40 perms/models.py:83 #: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13 @@ -392,7 +411,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:13 -#: users/models/user.py:343 +#: users/models/user.py:345 msgid "System" msgstr "系统" @@ -404,14 +423,6 @@ msgstr "默认Cluster" msgid "Cluster" msgstr "集群" -#: assets/models/domain.py:45 assets/models/user.py:106 -#: assets/templates/assets/domain_gateway_list.html:59 -#: assets/templates/assets/system_user_detail.html:66 -#: assets/templates/assets/system_user_list.html:28 -#: terminal/templates/terminal/session_list.html:75 -msgid "Protocol" -msgstr "协议" - #: assets/models/group.py:30 msgid "Asset group" msgstr "资产组" @@ -431,10 +442,10 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:281 -#: users/models/user.py:31 users/models/user.py:331 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:282 +#: users/models/user.py:31 users/models/user.py:333 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:13 users/views/user.py:362 +#: users/templates/users/user_group_list.html:13 users/views/user.py:361 msgid "User" msgstr "用户" @@ -451,7 +462,15 @@ msgstr "分类" msgid "Key" msgstr "" -#: assets/models/user.py:104 +#: assets/models/user.py:108 +msgid "Automatic login" +msgstr "自动登录" + +#: assets/models/user.py:109 +msgid "Manually login" +msgstr "手动登录" + +#: assets/models/user.py:113 #: assets/templates/assets/_asset_group_bulk_update_modal.html:11 #: assets/templates/assets/system_user_asset.html:21 #: assets/views/admin_user.py:29 assets/views/admin_user.py:47 @@ -461,7 +480,7 @@ msgstr "" #: assets/views/asset.py:197 assets/views/domain.py:29 #: assets/views/domain.py:45 assets/views/domain.py:61 #: assets/views/domain.py:74 assets/views/domain.py:98 -#: assets/views/domain.py:126 assets/views/domain.py:150 +#: assets/views/domain.py:126 assets/views/domain.py:145 #: assets/views/label.py:26 assets/views/label.py:42 assets/views/label.py:58 #: assets/views/system_user.py:28 assets/views/system_user.py:44 #: assets/views/system_user.py:60 assets/views/system_user.py:74 @@ -469,25 +488,30 @@ msgstr "" msgid "Assets" msgstr "资产管理" -#: assets/models/user.py:105 +#: assets/models/user.py:114 msgid "Priority" msgstr "优先级" -#: assets/models/user.py:107 assets/templates/assets/_system_user.html:58 -#: assets/templates/assets/system_user_detail.html:118 -#: assets/templates/assets/system_user_update.html:11 +#: assets/models/user.py:116 assets/templates/assets/_system_user.html:59 +#: assets/templates/assets/system_user_detail.html:122 +#: assets/templates/assets/system_user_update.html:10 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:108 assets/templates/assets/system_user_detail.html:70 +#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:74 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:109 assets/templates/assets/system_user_detail.html:75 +#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:79 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:149 audits/models.py:12 +#: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:66 +#: assets/templates/assets/system_user_list.html:29 +msgid "Login mode" +msgstr "登录模式" + +#: assets/models/user.py:159 audits/models.py:12 #: audits/templates/audits/ftp_log_list.html:49 #: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:43 #: perms/models.py:34 perms/models.py:78 @@ -601,32 +625,31 @@ msgid "Basic" msgstr "基本" #: assets/templates/assets/_system_user.html:44 -#: assets/templates/assets/asset_create.html:25 -#: assets/templates/assets/asset_update.html:30 +#: assets/templates/assets/asset_create.html:26 +#: assets/templates/assets/asset_update.html:31 #: assets/templates/assets/gateway_create_update.html:45 -#: assets/templates/assets/system_user_update.html:7 #: users/templates/users/_user.html:21 msgid "Auth" msgstr "认证" -#: assets/templates/assets/_system_user.html:47 +#: assets/templates/assets/_system_user.html:48 msgid "Auto generate key" msgstr "自动生成密钥" -#: assets/templates/assets/_system_user.html:64 -#: assets/templates/assets/asset_create.html:59 -#: assets/templates/assets/asset_update.html:63 +#: assets/templates/assets/_system_user.html:65 +#: assets/templates/assets/asset_create.html:60 +#: assets/templates/assets/asset_update.html:64 #: assets/templates/assets/gateway_create_update.html:53 #: perms/templates/perms/asset_permission_create_update.html:45 #: terminal/templates/terminal/terminal_update.html:42 msgid "Other" msgstr "其它" -#: assets/templates/assets/_system_user.html:70 +#: assets/templates/assets/_system_user.html:71 #: assets/templates/assets/admin_user_create_update.html:45 #: assets/templates/assets/asset_bulk_update.html:23 -#: assets/templates/assets/asset_create.html:66 -#: assets/templates/assets/asset_update.html:70 +#: assets/templates/assets/asset_create.html:67 +#: assets/templates/assets/asset_update.html:71 #: assets/templates/assets/domain_create_update.html:16 #: assets/templates/assets/gateway_create_update.html:58 #: assets/templates/assets/label_create_update.html:18 @@ -647,12 +670,12 @@ msgstr "其它" msgid "Reset" msgstr "重置" -#: assets/templates/assets/_system_user.html:71 +#: assets/templates/assets/_system_user.html:72 #: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/asset_bulk_update.html:24 -#: assets/templates/assets/asset_create.html:67 +#: assets/templates/assets/asset_create.html:68 #: assets/templates/assets/asset_list.html:108 -#: assets/templates/assets/asset_update.html:71 +#: assets/templates/assets/asset_update.html:72 #: assets/templates/assets/domain_create_update.html:17 #: assets/templates/assets/gateway_create_update.html:59 #: assets/templates/assets/label_create_update.html:19 @@ -662,7 +685,7 @@ msgstr "重置" #: common/templates/common/security_setting.html:71 #: common/templates/common/terminal_setting.html:108 #: perms/templates/perms/asset_permission_create_update.html:70 -#: terminal/templates/terminal/session_list.html:124 +#: terminal/templates/terminal/session_list.html:126 #: terminal/templates/terminal/terminal_update.html:48 #: users/templates/users/_user.html:47 #: users/templates/users/forgot_password.html:44 @@ -702,14 +725,14 @@ msgstr "资产列表" #: assets/templates/assets/admin_user_assets.html:54 #: assets/templates/assets/admin_user_list.html:26 #: assets/templates/assets/system_user_asset.html:52 -#: assets/templates/assets/system_user_list.html:30 +#: assets/templates/assets/system_user_list.html:31 #: users/templates/users/user_group_granted_asset.html:47 msgid "Reachable" msgstr "可连接" #: assets/templates/assets/admin_user_assets.html:66 #: assets/templates/assets/system_user_asset.html:64 -#: assets/templates/assets/system_user_detail.html:112 +#: assets/templates/assets/system_user_detail.html:116 #: perms/templates/perms/asset_permission_detail.html:114 msgid "Quick update" msgstr "快速更新" @@ -722,7 +745,7 @@ msgstr "测试可连接性" #: assets/templates/assets/admin_user_assets.html:75 #: assets/templates/assets/asset_detail.html:171 #: assets/templates/assets/system_user_asset.html:81 -#: assets/templates/assets/system_user_detail.html:147 +#: assets/templates/assets/system_user_detail.html:151 msgid "Test" msgstr "测试" @@ -733,10 +756,10 @@ msgstr "测试" #: assets/templates/assets/domain_detail.html:24 #: assets/templates/assets/domain_detail.html:103 #: assets/templates/assets/domain_gateway_list.html:85 -#: assets/templates/assets/domain_list.html:42 +#: assets/templates/assets/domain_list.html:50 #: assets/templates/assets/label_list.html:38 #: assets/templates/assets/system_user_detail.html:26 -#: assets/templates/assets/system_user_list.html:88 +#: assets/templates/assets/system_user_list.html:89 #: perms/templates/perms/asset_permission_detail.html:30 #: perms/templates/perms/asset_permission_list.html:191 #: terminal/templates/terminal/terminal_detail.html:16 @@ -757,10 +780,10 @@ msgstr "更新" #: assets/templates/assets/domain_detail.html:28 #: assets/templates/assets/domain_detail.html:104 #: assets/templates/assets/domain_gateway_list.html:86 -#: assets/templates/assets/domain_list.html:43 +#: assets/templates/assets/domain_list.html:51 #: assets/templates/assets/label_list.html:39 #: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:89 +#: assets/templates/assets/system_user_list.html:90 #: ops/templates/ops/task_list.html:72 #: perms/templates/perms/asset_permission_detail.html:34 #: perms/templates/perms/asset_permission_list.html:192 @@ -785,12 +808,13 @@ msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:200 #: assets/templates/assets/asset_list.html:638 -#: assets/templates/assets/system_user_detail.html:192 -#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22 +#: assets/templates/assets/system_user_detail.html:195 +#: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22 #: terminal/templates/terminal/session_detail.html:108 -#: users/templates/users/user_detail.html:366 -#: users/templates/users/user_detail.html:391 -#: users/templates/users/user_detail.html:414 +#: users/templates/users/user_detail.html:374 +#: users/templates/users/user_detail.html:399 +#: users/templates/users/user_detail.html:422 +#: users/templates/users/user_detail.html:458 #: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_list.html:86 #: users/templates/users/user_list.html:200 @@ -804,12 +828,12 @@ msgid "Create admin user" msgstr "创建管理用户" #: assets/templates/assets/admin_user_list.html:27 -#: assets/templates/assets/system_user_list.html:31 +#: assets/templates/assets/system_user_list.html:32 msgid "Unreachable" msgstr "不可达" #: assets/templates/assets/admin_user_list.html:28 -#: assets/templates/assets/system_user_list.html:32 +#: assets/templates/assets/system_user_list.html:33 #: ops/templates/ops/adhoc_history.html:54 #: ops/templates/ops/task_history.html:60 msgid "Ratio" @@ -818,13 +842,13 @@ msgstr "比例" #: assets/templates/assets/admin_user_list.html:30 #: assets/templates/assets/asset_list.html:91 #: assets/templates/assets/domain_gateway_list.html:62 -#: assets/templates/assets/domain_list.html:18 +#: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/label_list.html:17 -#: assets/templates/assets/system_user_list.html:34 +#: assets/templates/assets/system_user_list.html:35 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 #: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:42 #: perms/templates/perms/asset_permission_list.html:60 -#: terminal/templates/terminal/session_list.html:80 +#: terminal/templates/terminal/session_list.html:81 #: terminal/templates/terminal/terminal_list.html:36 #: users/templates/users/user_group_list.html:15 #: users/templates/users/user_list.html:29 @@ -882,8 +906,8 @@ msgid "Refresh" msgstr "刷新" #: assets/templates/assets/asset_detail.html:300 -#: users/templates/users/user_detail.html:286 -#: users/templates/users/user_detail.html:313 +#: users/templates/users/user_detail.html:294 +#: users/templates/users/user_detail.html:321 msgid "Update successfully!" msgstr "更新成功" @@ -978,9 +1002,10 @@ msgid "Have assets, cancel" msgstr "存在资产,不能删除" #: assets/templates/assets/asset_list.html:633 -#: assets/templates/assets/system_user_list.html:133 -#: users/templates/users/user_detail.html:361 -#: users/templates/users/user_detail.html:386 +#: assets/templates/assets/system_user_list.html:134 +#: users/templates/users/user_detail.html:369 +#: users/templates/users/user_detail.html:394 +#: users/templates/users/user_detail.html:453 #: users/templates/users/user_group_list.html:81 #: users/templates/users/user_list.html:195 msgid "Are you sure?" @@ -1003,7 +1028,7 @@ msgstr "删除" msgid "Asset Deleting failed." msgstr "删除失败" -#: assets/templates/assets/asset_update.html:59 +#: assets/templates/assets/asset_update.html:60 msgid "Configuration" msgstr "配置" @@ -1020,7 +1045,7 @@ msgstr "您确定删除吗?" #: assets/templates/assets/domain_detail.html:21 #: assets/templates/assets/domain_detail.html:64 #: assets/templates/assets/domain_gateway_list.html:21 -#: assets/templates/assets/domain_list.html:16 +#: assets/templates/assets/domain_list.html:24 msgid "Gateway" msgstr "网关" @@ -1040,7 +1065,7 @@ msgstr "创建网关" msgid "Test connection" msgstr "测试连接" -#: assets/templates/assets/domain_list.html:6 assets/views/domain.py:46 +#: assets/templates/assets/domain_list.html:14 assets/views/domain.py:46 msgid "Create domain" msgstr "创建网域" @@ -1053,17 +1078,17 @@ msgid "Assets of " msgstr "资产" #: assets/templates/assets/system_user_asset.html:70 -#: assets/templates/assets/system_user_detail.html:135 +#: assets/templates/assets/system_user_detail.html:139 msgid "Push system user now" msgstr "立刻推送系统" #: assets/templates/assets/system_user_asset.html:73 -#: assets/templates/assets/system_user_detail.html:138 +#: assets/templates/assets/system_user_detail.html:142 msgid "Push" msgstr "推送" #: assets/templates/assets/system_user_asset.html:78 -#: assets/templates/assets/system_user_detail.html:144 +#: assets/templates/assets/system_user_detail.html:148 msgid "Test assets connective" msgstr "测试资产可连接性" @@ -1075,28 +1100,23 @@ msgstr "任务已下发,查看ops任务列表" msgid "Task has been send, seen left assets status" msgstr "任务已下发,查看左侧资产状态" -#: assets/templates/assets/system_user_detail.html:81 +#: assets/templates/assets/system_user_detail.html:85 msgid "Home" msgstr "家目录" -#: assets/templates/assets/system_user_detail.html:87 +#: assets/templates/assets/system_user_detail.html:91 msgid "Uid" msgstr "Uid" -#: assets/templates/assets/system_user_detail.html:153 -#: assets/templates/assets/system_user_detail.html:339 -msgid "Clear auth" -msgstr "清除认证信息" - -#: assets/templates/assets/system_user_detail.html:156 -msgid "Clear" -msgstr "清除" - -#: assets/templates/assets/system_user_detail.html:183 +#: assets/templates/assets/system_user_detail.html:186 msgid "Add to node" msgstr "添加到节点" -#: assets/templates/assets/system_user_detail.html:339 +#: assets/templates/assets/system_user_detail.html:353 +msgid "Clear auth" +msgstr "清除认证信息" + +#: assets/templates/assets/system_user_detail.html:353 msgid "success" msgstr "成功" @@ -1105,20 +1125,20 @@ msgstr "成功" msgid "Create system user" msgstr "创建系统用户" -#: assets/templates/assets/system_user_list.html:134 +#: assets/templates/assets/system_user_list.html:135 msgid "This will delete the selected System Users !!!" msgstr "删除选择系统用户" -#: assets/templates/assets/system_user_list.html:142 +#: assets/templates/assets/system_user_list.html:143 msgid "System Users Deleted." msgstr "已被删除" -#: assets/templates/assets/system_user_list.html:143 -#: assets/templates/assets/system_user_list.html:148 +#: assets/templates/assets/system_user_list.html:144 +#: assets/templates/assets/system_user_list.html:149 msgid "System Users Delete" msgstr "删除系统用户" -#: assets/templates/assets/system_user_list.html:147 +#: assets/templates/assets/system_user_list.html:148 msgid "System Users Deleting failed." msgstr "系统用户删除失败" @@ -1166,7 +1186,7 @@ msgstr "网域详情" msgid "Domain gateway list" msgstr "域网关列表" -#: assets/views/domain.py:151 +#: assets/views/domain.py:146 msgid "Update gateway" msgstr "创建网关" @@ -1214,7 +1234,8 @@ msgid "Filename" msgstr "文件名" #: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77 -#: ops/templates/ops/task_list.html:39 +#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66 +#: users/templates/users/user_detail.html:443 msgid "Success" msgstr "成功" @@ -1223,7 +1244,7 @@ msgstr "成功" #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/task_history.html:58 perms/models.py:36 #: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:137 -#: terminal/templates/terminal/session_list.html:77 +#: terminal/templates/terminal/session_list.html:78 msgid "Date start" msgstr "开始日期" @@ -1410,45 +1431,60 @@ msgid "" "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:184 +#: common/forms.py:185 +msgid "Limit the number of login failures" +msgstr "限制登录失败次数" + +#: common/forms.py:190 +msgid "No logon interval" +msgstr "禁止登录时间间隔" + +#: common/forms.py:192 +msgid "" +"Tip :(unit/minute) if the user has failed to log in for a limited number of " +"times, no login is allowed during this time interval." +msgstr "" +"提示:(单位 / 分钟)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录." + +#: common/forms.py:198 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:191 +#: common/forms.py:205 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:193 +#: common/forms.py:207 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:199 +#: common/forms.py:213 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:200 +#: common/forms.py:214 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:206 +#: common/forms.py:220 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:207 +#: common/forms.py:221 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:213 +#: common/forms.py:227 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:214 +#: common/forms.py:228 msgid "" "After opening, the user password changes and resets must contain special " "characters" @@ -1462,7 +1498,8 @@ msgstr "" msgid "discard time" msgstr "" -#: common/models.py:29 users/templates/users/user_detail.html:96 +#: common/models.py:29 users/models/authentication.py:51 +#: users/templates/users/user_detail.html:96 msgid "Enabled" msgstr "启用" @@ -1508,8 +1545,8 @@ msgid "Security setting" msgstr "安全设置" #: common/templates/common/security_setting.html:42 -msgid "MFA setting" -msgstr "MFA 设置" +msgid "User login settings" +msgstr "用户登录设置" #: common/templates/common/security_setting.html:46 msgid "Password check rule" @@ -1780,7 +1817,7 @@ msgid "Versions" msgstr "版本" #: ops/templates/ops/task_list.html:40 -#: users/templates/users/login_log_list.html:54 +#: users/templates/users/login_log_list.html:57 msgid "Date" msgstr "日期" @@ -1805,8 +1842,8 @@ msgstr "任务列表" msgid "Task run history" msgstr "执行历史" -#: perms/forms.py:18 users/forms.py:238 users/forms.py:243 users/forms.py:255 -#: users/forms.py:285 +#: perms/forms.py:18 users/forms.py:239 users/forms.py:244 users/forms.py:256 +#: users/forms.py:286 msgid "Select users" msgstr "选择用户" @@ -1815,7 +1852,7 @@ msgstr "选择用户" #: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14 #: users/models/group.py:23 users/models/user.py:55 #: users/templates/users/_select_user_modal.html:16 -#: users/templates/users/user_detail.html:192 +#: users/templates/users/user_detail.html:200 #: users/templates/users/user_list.html:26 msgid "User group" msgstr "用户组" @@ -1868,7 +1905,7 @@ msgid "Add node to this permission" msgstr "添加节点" #: perms/templates/perms/asset_permission_asset.html:125 -#: users/templates/users/user_detail.html:209 +#: users/templates/users/user_detail.html:217 msgid "Join" msgstr "加入" @@ -1958,14 +1995,14 @@ msgstr "商业支持" msgid "Docs" msgstr "文档" -#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:121 +#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:122 #: users/templates/users/_user.html:39 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:39 #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:344 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:343 msgid "Profile" msgstr "个人信息" @@ -1982,7 +2019,7 @@ msgid "Logout" msgstr "注销登录" #: templates/_header_bar.html:49 users/templates/users/login.html:44 -#: users/templates/users/login.html:64 +#: users/templates/users/login.html:68 msgid "Login" msgstr "登录" @@ -2022,13 +2059,13 @@ msgstr "关闭" #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95 -#: users/views/login.py:277 users/views/login.py:335 users/views/user.py:66 -#: users/views/user.py:81 users/views/user.py:103 users/views/user.py:174 -#: users/views/user.py:329 users/views/user.py:381 users/views/user.py:416 +#: users/views/login.py:330 users/views/login.py:388 users/views/user.py:65 +#: users/views/user.py:80 users/views/user.py:102 users/views/user.py:175 +#: users/views/user.py:330 users/views/user.py:380 users/views/user.py:415 msgid "Users" msgstr "用户管理" -#: templates/_nav.html:13 users/views/user.py:67 +#: templates/_nav.html:13 users/views/user.py:66 msgid "User list" msgstr "用户列表" @@ -2138,14 +2175,14 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:132 terminal/templates/terminal/session_list.html:102 +#: terminal/models.py:132 terminal/templates/terminal/session_list.html:104 msgid "Replay" msgstr "回放" #: terminal/models.py:133 terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/session_detail.html:48 -#: terminal/templates/terminal/session_list.html:76 +#: terminal/templates/terminal/session_list.html:77 msgid "Command" msgstr "命令" @@ -2196,24 +2233,28 @@ msgstr "监控" msgid "Terminate session" msgstr "终止会话" -#: terminal/templates/terminal/session_list.html:79 +#: terminal/templates/terminal/session_list.html:76 +msgid "Login from" +msgstr "登录来源" + +#: terminal/templates/terminal/session_list.html:80 msgid "Duration" msgstr "时长" -#: terminal/templates/terminal/session_list.html:104 +#: terminal/templates/terminal/session_list.html:106 msgid "Monitor" msgstr "监控" -#: terminal/templates/terminal/session_list.html:106 #: terminal/templates/terminal/session_list.html:108 +#: terminal/templates/terminal/session_list.html:110 msgid "Terminate" msgstr "终断" -#: terminal/templates/terminal/session_list.html:120 +#: terminal/templates/terminal/session_list.html:122 msgid "Terminate selected" msgstr "终断所选" -#: terminal/templates/terminal/session_list.html:140 +#: terminal/templates/terminal/session_list.html:142 msgid "Terminate task send, waiting ..." msgstr "终断任务已发送,请等待" @@ -2283,6 +2324,10 @@ msgid "" "You should use your ssh client tools connect terminal: {}

{}" msgstr "你可以使用ssh客户端工具连接终端" +#: users/api.py:221 users/templates/users/login.html:50 +msgid "Log in frequently and try again later" +msgstr "登录频繁, 稍后重试" + #: users/authentication.py:56 msgid "Invalid signature header. No credentials provided." msgstr "" @@ -2334,11 +2379,11 @@ msgstr "" msgid "Invalid token or cache refreshed." msgstr "" -#: users/forms.py:38 +#: users/forms.py:39 msgid "MFA code" msgstr "MFA 验证码" -#: users/forms.py:49 users/models/user.py:59 +#: users/forms.py:50 users/models/user.py:59 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:25 @@ -2346,31 +2391,31 @@ msgstr "MFA 验证码" msgid "Role" msgstr "角色" -#: users/forms.py:52 users/forms.py:201 +#: users/forms.py:53 users/forms.py:202 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:53 users/forms.py:202 +#: users/forms.py:54 users/forms.py:203 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:54 +#: users/forms.py:55 msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:72 users/templates/users/user_detail.html:200 +#: users/forms.py:73 users/templates/users/user_detail.html:208 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:83 users/forms.py:216 +#: users/forms.py:84 users/forms.py:217 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:87 users/forms.py:220 users/serializers.py:48 +#: users/forms.py:88 users/forms.py:221 users/serializers.py:48 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:127 +#: users/forms.py:128 msgid "" "Tip: when enabled, you will enter the MFA binding process the next time you " "log in. you can also directly bind in \"personal information -> quick " @@ -2379,16 +2424,17 @@ msgstr "" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "改->更改MFA设置)中直接绑定!" -#: users/forms.py:137 +#: users/forms.py:138 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全." -#: users/forms.py:142 users/models/user.py:71 +#: users/forms.py:143 users/models/authentication.py:75 users/models/user.py:71 #: users/templates/users/first_login.html:45 +#: users/templates/users/login_log_list.html:54 msgid "MFA" msgstr "MFA" -#: users/forms.py:147 +#: users/forms.py:148 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -2397,41 +2443,41 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:154 users/templates/users/first_login.html:48 +#: users/forms.py:155 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:130 msgid "Finish" msgstr "完成" -#: users/forms.py:160 +#: users/forms.py:161 msgid "Old password" msgstr "原来密码" -#: users/forms.py:165 +#: users/forms.py:166 msgid "New password" msgstr "新密码" -#: users/forms.py:170 +#: users/forms.py:171 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:180 +#: users/forms.py:181 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:188 +#: users/forms.py:189 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:199 +#: users/forms.py:200 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:203 +#: users/forms.py:204 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:231 users/models/user.py:79 +#: users/forms.py:232 users/models/user.py:79 #: users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:45 #: users/templates/users/user_profile.html:68 @@ -2444,27 +2490,57 @@ msgstr "ssh公钥" msgid "Private Token" msgstr "ssh密钥" -#: users/models/authentication.py:46 +#: users/models/authentication.py:50 users/templates/users/user_detail.html:98 +msgid "Disabled" +msgstr "禁用" + +#: users/models/authentication.py:52 users/models/authentication.py:60 +msgid "-" +msgstr "" + +#: users/models/authentication.py:61 +msgid "Username/password check failed" +msgstr "用户名/密码 校验失败" + +#: users/models/authentication.py:62 +msgid "MFA authentication failed" +msgstr "MFA 认证失败" + +#: users/models/authentication.py:67 +msgid "Failed" +msgstr "失败" + +#: users/models/authentication.py:71 msgid "Login type" msgstr "登录方式" -#: users/models/authentication.py:47 +#: users/models/authentication.py:72 msgid "Login ip" msgstr "登录IP" -#: users/models/authentication.py:48 +#: users/models/authentication.py:73 msgid "Login city" msgstr "登录城市" -#: users/models/authentication.py:49 +#: users/models/authentication.py:74 msgid "User agent" msgstr "Agent" -#: users/models/authentication.py:50 +#: users/models/authentication.py:76 +#: users/templates/users/login_log_list.html:55 +msgid "Reason" +msgstr "原因" + +#: users/models/authentication.py:77 +#: users/templates/users/login_log_list.html:56 +msgid "Status" +msgstr "状态" + +#: users/models/authentication.py:78 msgid "Date login" msgstr "登录日期" -#: users/models/user.py:30 users/models/user.py:339 +#: users/models/user.py:30 users/models/user.py:341 msgid "Administrator" msgstr "管理员" @@ -2506,7 +2582,7 @@ msgstr "微信" msgid "Source" msgstr "用户来源" -#: users/models/user.py:342 +#: users/models/user.py:344 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -2586,7 +2662,7 @@ msgid " for more information" msgstr "获取更多信息" #: users/templates/users/forgot_password.html:26 -#: users/templates/users/login.html:73 +#: users/templates/users/login.html:77 msgid "Forgot password" msgstr "忘记密码" @@ -2594,7 +2670,7 @@ msgstr "忘记密码" msgid "Input your email, that will send a mail to your" msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" -#: users/templates/users/login.html:50 +#: users/templates/users/login.html:53 msgid "Captcha invalid" msgstr "验证码错误" @@ -2623,7 +2699,7 @@ msgid "Can't provide security? Please contact the administrator!" msgstr "如果不能提供MFA验证码,请联系管理员!" #: users/templates/users/reset_password.html:46 -#: users/templates/users/user_detail.html:352 users/utils.py:80 +#: users/templates/users/user_detail.html:360 users/utils.py:80 msgid "Reset password" msgstr "重置密码" @@ -2649,7 +2725,7 @@ msgid "Setting" msgstr "设置" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:16 users/views/user.py:81 +#: users/templates/users/user_list.html:16 users/views/user.py:80 msgid "Create user" msgstr "创建用户" @@ -2658,7 +2734,7 @@ msgid "Reset link will be generated and sent to the user. " msgstr "生成重置密码连接,通过邮件发送给用户" #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:175 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:176 msgid "User detail" msgstr "用户详情" @@ -2673,10 +2749,6 @@ msgstr "授权的资产" msgid "Force enabled" msgstr "强制启用" -#: users/templates/users/user_detail.html:98 -msgid "Disabled" -msgstr "禁用" - #: users/templates/users/user_detail.html:119 #: users/templates/users/user_profile.html:108 msgid "Last login" @@ -2699,44 +2771,57 @@ msgstr "发送" msgid "Send reset ssh key mail" msgstr "发送重置密钥邮件" -#: users/templates/users/user_detail.html:295 +#: users/templates/users/user_detail.html:186 +#: users/templates/users/user_detail.html:444 +msgid "Unblock user" +msgstr "解除登录限制" + +#: users/templates/users/user_detail.html:189 +msgid "Unblock" +msgstr "解除" + +#: users/templates/users/user_detail.html:303 msgid "Goto profile page enable MFA" msgstr "请去个人信息页面启用自己的MFA" -#: users/templates/users/user_detail.html:351 +#: users/templates/users/user_detail.html:359 msgid "An e-mail has been sent to the user`s mailbox." msgstr "已发送邮件到用户邮箱" -#: users/templates/users/user_detail.html:362 +#: users/templates/users/user_detail.html:370 msgid "This will reset the user password and send a reset mail" msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" -#: users/templates/users/user_detail.html:376 +#: users/templates/users/user_detail.html:384 msgid "" "The reset-ssh-public-key E-mail has been sent successfully. Please inform " "the user to update his new ssh public key." msgstr "重设密钥邮件将会发送到用户邮箱" -#: users/templates/users/user_detail.html:377 +#: users/templates/users/user_detail.html:385 msgid "Reset SSH public key" msgstr "重置SSH密钥" -#: users/templates/users/user_detail.html:387 +#: users/templates/users/user_detail.html:395 msgid "This will reset the user public key and send a reset mail" msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" -#: users/templates/users/user_detail.html:404 +#: users/templates/users/user_detail.html:412 #: users/templates/users/user_profile.html:211 msgid "Successfully updated the SSH public key." msgstr "更新ssh密钥成功" -#: users/templates/users/user_detail.html:405 -#: users/templates/users/user_detail.html:409 +#: users/templates/users/user_detail.html:413 +#: users/templates/users/user_detail.html:417 #: users/templates/users/user_profile.html:212 #: users/templates/users/user_profile.html:217 msgid "User SSH public key update" msgstr "ssh密钥" +#: users/templates/users/user_detail.html:454 +msgid "After unlocking the user, the user can log in normally." +msgstr "解除用户登录限制后,此用户即可正常登录" + #: users/templates/users/user_group_create_update.html:31 msgid "Cancel" msgstr "取消" @@ -2793,8 +2878,8 @@ msgstr "用户删除失败" msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:116 users/views/user.py:204 -#: users/views/user.py:258 +#: users/templates/users/user_profile.html:116 users/views/user.py:205 +#: users/views/user.py:259 msgid "User groups" msgstr "用户组" @@ -2840,7 +2925,7 @@ msgid "" "corresponding private key." msgstr "新的公钥已设置成功,请下载对应的私钥" -#: users/templates/users/user_update.html:4 users/views/user.py:104 +#: users/templates/users/user_update.html:4 users/views/user.py:103 msgid "Update user" msgstr "更新用户" @@ -2849,7 +2934,7 @@ msgid "Create account successfully" msgstr "创建账户成功" #: users/utils.py:43 -#, fuzzy, python-format +#, python-format msgid "" "\n" " Hello %(name)s:\n" @@ -2978,7 +3063,7 @@ msgstr "禁用或失效" msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" -#: users/utils.py:290 users/utils.py:300 +#: users/utils.py:289 users/utils.py:299 msgid "Bit" msgstr " 位" @@ -2994,103 +3079,112 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:62 +#: users/views/login.py:75 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:128 users/views/user.py:501 users/views/user.py:526 +#: users/views/login.py:178 users/views/user.py:500 users/views/user.py:525 msgid "MFA code invalid" msgstr "MFA码认证失败" -#: users/views/login.py:154 +#: users/views/login.py:207 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:155 +#: users/views/login.py:208 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:171 +#: users/views/login.py:224 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:184 +#: users/views/login.py:237 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:185 +#: users/views/login.py:238 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:198 +#: users/views/login.py:251 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:199 +#: users/views/login.py:252 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:220 users/views/login.py:233 +#: users/views/login.py:273 users/views/login.py:286 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:229 +#: users/views/login.py:282 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:239 users/views/user.py:116 users/views/user.py:399 +#: users/views/login.py:292 users/views/user.py:118 users/views/user.py:398 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:277 +#: users/views/login.py:330 msgid "First login" msgstr "首次登陆" -#: users/views/login.py:336 +#: users/views/login.py:389 msgid "Login log list" msgstr "登录日志" -#: users/views/user.py:128 +#: users/views/user.py:129 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:233 +#: users/views/user.py:234 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:330 +#: users/views/user.py:331 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:363 +#: users/views/user.py:362 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:382 +#: users/views/user.py:381 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:417 +#: users/views/user.py:416 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:458 +#: users/views/user.py:457 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:552 +#: users/views/user.py:551 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:553 +#: users/views/user.py:552 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:555 +#: users/views/user.py:554 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:556 +#: users/views/user.py:555 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" + +#~ msgid "Unblock user successfully. " +#~ msgstr "解除登录限制成功" + +#~ msgid "Clear" +#~ msgstr "清除" + +#~ msgid "MFA setting" +#~ msgstr "MFA 设置" diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 6fd9f0fdd..c0a536276 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -343,10 +343,11 @@ if AUTH_LDAP: AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND) # Celery using redis as broker -CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % { +CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', 'host': CONFIG.REDIS_HOST or '127.0.0.1', 'port': CONFIG.REDIS_PORT or 6379, + 'db':CONFIG.REDIS_DB_CELERY_BROKER or 3, } CELERY_TASK_SERIALIZER = 'pickle' CELERY_RESULT_SERIALIZER = 'pickle' @@ -367,10 +368,11 @@ CELERY_WORKER_HIJACK_ROOT_LOGGER = False CACHES = { 'default': { 'BACKEND': 'redis_cache.RedisCache', - 'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/4' % { + 'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', 'host': CONFIG.REDIS_HOST or '127.0.0.1', 'port': CONFIG.REDIS_PORT or 6379, + 'db':CONFIG.REDIS_DB_CACHE or 4, } } } @@ -403,6 +405,8 @@ TERMINAL_REPLAY_STORAGE = { DEFAULT_PASSWORD_MIN_LENGTH = 6 +DEFAULT_LOGIN_LIMIT_COUNT = 3 +DEFAULT_LOGIN_LIMIT_TIME = 30 # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html BOOTSTRAP3 = { diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index b7d3ef487..7ed89e75c 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -93,7 +93,7 @@ class JMSInventory(BaseInventory): if gateway.password: proxy_command_list.insert( - 0, "sshpass -p {}".format(gateway.password) + 0, "sshpass -p '{}'".format(gateway.password) ) if gateway.private_key: proxy_command_list.append("-i {}".format(gateway.private_key_file)) diff --git a/apps/perms/api.py b/apps/perms/api.py index d990d4db4..090f1988a 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -77,9 +77,9 @@ class UserGrantedAssetsApi(ListAPIView): util = AssetPermissionUtil(user) for k, v in util.get_assets().items(): if k.is_unixlike(): - system_users_granted = [s for s in v if s.protocol == 'ssh'] + system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']] else: - system_users_granted = [s for s in v if s.protocol == 'rdp'] + system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']] k.system_users_granted = system_users_granted queryset.append(k) return queryset @@ -128,9 +128,9 @@ class UserGrantedNodesWithAssetsApi(ListAPIView): assets = _assets.keys() for k, v in _assets.items(): if k.is_unixlike(): - system_users_granted = [s for s in v if s.protocol == 'ssh'] + system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']] else: - system_users_granted = [s for s in v if s.protocol == 'rdp'] + system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']] k.system_users_granted = system_users_granted node.assets_granted = assets queryset.append(node) diff --git a/apps/perms/urls/views_urls.py b/apps/perms/urls/views_urls.py index 5c3fa3b48..a3cf7ff42 100644 --- a/apps/perms/urls/views_urls.py +++ b/apps/perms/urls/views_urls.py @@ -6,13 +6,11 @@ from .. import views app_name = 'perms' urlpatterns = [ - url(r'^asset-permission$', views.AssetPermissionListView.as_view(), name='asset-permission-list'), - url(r'^asset-permission/create$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/update$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/delete$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), + url(r'^asset-permission/$', views.AssetPermissionListView.as_view(), name='asset-permission-list'), + url(r'^asset-permission/create/$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'), + url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/update/$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'), + url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), + url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/delete/$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'), + url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/user/$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), + url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/asset/$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), ] - - diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 0000a3ccf..07405fb4f 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -173,14 +173,14 @@ function APIUpdateAttr(props) { } if (typeof props.success === 'function') { return props.success(data); - } + } }).fail(function(jqXHR, textStatus, errorThrown) { if (flash_message) { toastr.error(fail_message); } if (typeof props.error === 'function') { return props.error(jqXHR.responseText); - } + } }); // return true; } @@ -198,7 +198,8 @@ function objectDelete(obj, name, url, redirectTo) { } }; var fail = function() { - swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error"); + // swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error"); + swal("错误", "[ "+name+" ]"+"正在被资产使用中,请先解除资产绑定", "error"); }; APIUpdateAttr({ url: url, @@ -219,7 +220,7 @@ function objectDelete(obj, name, url, redirectTo) { confirmButtonText: '确认', closeOnConfirm: true, }, function () { - doDelete() + doDelete() }); } @@ -272,7 +273,7 @@ jumpserver.initDataTable = function (options) { $(td).html(''.replace('99991937', cellData)); } }, - {className: 'text-center', targets: '_all'} + {className: 'text-center', render: $.fn.dataTable.render.text(), targets: '_all'} ]; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; var select = { diff --git a/apps/templates/_footer.html b/apps/templates/_footer.html index c78f0da33..70e0c8dcd 100644 --- a/apps/templates/_footer.html +++ b/apps/templates/_footer.html @@ -1,6 +1,6 @@ @@ -275,7 +283,7 @@ $(document).ready(function() { .on('select2:unselect', function(evt) { var data = evt.params.data; delete jumpserver.nodes_selected[data.id]; - }) + }); }) .on('click', '#is_active', function() { var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}"; @@ -293,7 +301,7 @@ $(document).ready(function() { .on('click', '#force_enable_otp', function() { {% if request.user == user_object %} toastr.error("{% trans 'Goto profile page enable MFA' %}"); - return + return; {% endif %} var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}"; @@ -421,11 +429,45 @@ $(document).ready(function() { APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail}); }).on('click', '.btn-delete-user', function () { var $this = $(this); - var name = "{{ user.name }}"; - var uid = "{{ user.id }}"; + var name = "{{ user_object.name }}"; + var uid = "{{ user_object.id }}"; var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); var redirect_url = "{% url 'users:user-list' %}"; objectDelete($this, name, the_url, redirect_url); +}).on('click', '#btn-unblock-user', function () { + function doReset() { + {#var the_url = '{% url "api-users:user-reset-password" pk=user_object.id %}';#} + var the_url = '{% url "api-users:user-unblock" pk=user_object.id %}'; + var body = {}; + var success = function() { + var msg = "{% trans "Success" %}"; + {#swal("{% trans 'Unblock user' %}", msg, "success");#} + swal({ + title: "{% trans 'Unblock user' %}", + text: msg, + type: "success" + }, function() { + location.reload() + } + ); + }; + APIUpdateAttr({ + url: the_url, + body: JSON.stringify(body), + success: success + }); + } + swal({ + title: "{% trans 'Are you sure?' %}", + text: "{% trans "After unlocking the user, the user can log in normally."%}", + type: "warning", + showCancelButton: true, + confirmButtonColor: "#DD6B55", + confirmButtonText: "{% trans 'Confirm' %}", + closeOnConfirm: false + }, function() { + doReset(); + }); }) {% endblock %} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 052c5b7c0..27c07c538 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -59,7 +59,7 @@ function initTable() { ele: $('#user_list_table'), columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { - var detail_btn = '' + cellData + ''; + var detail_btn = '' + escape(cellData) + ''; $(td).html(detail_btn.replace("{{ DEFAULT_PK }}", rowData.id)); }}, {targets: 4, createdCell: function (td, cellData) { diff --git a/apps/users/urls/api_urls.py b/apps/users/urls/api_urls.py index 683638a4e..017224421 100644 --- a/apps/users/urls/api_urls.py +++ b/apps/users/urls/api_urls.py @@ -29,6 +29,8 @@ urlpatterns = [ api.UserResetPKApi.as_view(), name='user-public-key-reset'), url(r'^v1/users/(?P[0-9a-zA-Z\-]{36})/pubkey/update/$', api.UserUpdatePKApi.as_view(), name='user-public-key-update'), + url(r'^v1/users/(?P[0-9a-zA-Z\-]{36})/unblock/$', + api.UserUnblockPKApi.as_view(), name='user-unblock'), url(r'^v1/users/(?P[0-9a-zA-Z\-]{36})/groups/$', api.UserUpdateGroupApi.as_view(), name='user-update-group'), url(r'^v1/groups/(?P[0-9a-zA-Z\-]{36})/users/$', diff --git a/apps/users/urls/views_urls.py b/apps/users/urls/views_urls.py index 2aaf4a9ae..8052f1384 100644 --- a/apps/users/urls/views_urls.py +++ b/apps/users/urls/views_urls.py @@ -8,13 +8,13 @@ app_name = 'users' urlpatterns = [ # Login view - url(r'^login$', views.UserLoginView.as_view(), name='login'), - url(r'^logout$', views.UserLogoutView.as_view(), name='logout'), - url(r'^login/otp$', views.UserLoginOtpView.as_view(), name='login-otp'), - url(r'^password/forgot$', views.UserForgotPasswordView.as_view(), name='forgot-password'), - url(r'^password/forgot/sendmail-success$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'), - url(r'^password/reset$', views.UserResetPasswordView.as_view(), name='reset-password'), - url(r'^password/reset/success$', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'), + url(r'^login/$', views.UserLoginView.as_view(), name='login'), + url(r'^logout/$', views.UserLogoutView.as_view(), name='logout'), + url(r'^login/otp/$', views.UserLoginOtpView.as_view(), name='login-otp'), + url(r'^password/forgot/$', views.UserForgotPasswordView.as_view(), name='forgot-password'), + url(r'^password/forgot/sendmail-success/$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'), + url(r'^password/reset/$', views.UserResetPasswordView.as_view(), name='reset-password'), + url(r'^password/reset/success/$', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'), # Profile url(r'^profile/$', views.UserProfileView.as_view(), name='user-profile'), @@ -29,23 +29,23 @@ urlpatterns = [ url(r'^profile/otp/settings-success/$', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'), # User view - url(r'^user$', views.UserListView.as_view(), name='user-list'), - url(r'^user/export/', views.UserExportView.as_view(), name='user-export'), + url(r'^user/$', views.UserListView.as_view(), name='user-list'), + url(r'^user/export/$', views.UserExportView.as_view(), name='user-export'), url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'), url(r'^user/import/$', views.UserBulkImportView.as_view(), name='user-import'), - url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'), - url(r'^user/(?P[0-9a-zA-Z\-]{36})/update$', views.UserUpdateView.as_view(), name='user-update'), - url(r'^user/update$', views.UserBulkUpdateView.as_view(), name='user-bulk-update'), - url(r'^user/(?P[0-9a-zA-Z\-]{36})$', views.UserDetailView.as_view(), name='user-detail'), - url(r'^user/(?P[0-9a-zA-Z\-]{36})/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), - url(r'^user/(?P[0-9a-zA-Z\-]{36})/login-history', views.UserDetailView.as_view(), name='user-login-history'), + url(r'^user/create/$', views.UserCreateView.as_view(), name='user-create'), + url(r'^user/(?P[0-9a-zA-Z\-]{36})/update/$', views.UserUpdateView.as_view(), name='user-update'), + url(r'^user/update/$', views.UserBulkUpdateView.as_view(), name='user-bulk-update'), + url(r'^user/(?P[0-9a-zA-Z\-]{36})/$', views.UserDetailView.as_view(), name='user-detail'), + url(r'^user/(?P[0-9a-zA-Z\-]{36})/assets/$', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), + url(r'^user/(?P[0-9a-zA-Z\-]{36})/login-history/$', views.UserDetailView.as_view(), name='user-login-history'), # User group view - url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'), - url(r'^user-group/(?P[0-9a-zA-Z\-]{36})$', views.UserGroupDetailView.as_view(), name='user-group-detail'), - url(r'^user-group/create$', views.UserGroupCreateView.as_view(), name='user-group-create'), - url(r'^user-group/(?P[0-9a-zA-Z\-]{36})/update$', views.UserGroupUpdateView.as_view(), name='user-group-update'), - url(r'^user-group/(?P[0-9a-zA-Z\-]{36})/assets', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'), + url(r'^user-group/$', views.UserGroupListView.as_view(), name='user-group-list'), + url(r'^user-group/(?P[0-9a-zA-Z\-]{36})/$', views.UserGroupDetailView.as_view(), name='user-group-detail'), + url(r'^user-group/create/$', views.UserGroupCreateView.as_view(), name='user-group-create'), + url(r'^user-group/(?P[0-9a-zA-Z\-]{36})/update/$', views.UserGroupUpdateView.as_view(), name='user-group-update'), + url(r'^user-group/(?P[0-9a-zA-Z\-]{36})/assets/$', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'), # Login log url(r'^login-log/$', views.LoginLogListView.as_view(), name='login-log-list'), diff --git a/apps/users/utils.py b/apps/users/utils.py index 989632e2c..7cbaa75f0 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -13,7 +13,7 @@ import ipaddress from django.http import Http404 from django.conf import settings from django.contrib.auth.mixins import UserPassesTestMixin -from django.contrib.auth import authenticate, login as auth_login +from django.contrib.auth import authenticate from django.utils.translation import ugettext as _ from django.core.cache import cache @@ -200,16 +200,15 @@ def get_login_ip(request): return login_ip -def write_login_log(username, type='', ip='', user_agent=''): +def write_login_log(*args, **kwargs): + ip = kwargs.get('ip', '') if not (ip and validate_ip(ip)): ip = ip[:15] city = "Unknown" else: city = get_ip_city(ip) - LoginLog.objects.create( - username=username, type=type, - ip=ip, city=city, user_agent=user_agent - ) + kwargs.update({'ip': ip, 'city': city}) + LoginLog.objects.create(**kwargs) def get_ip_city(ip, timeout=10): @@ -332,3 +331,44 @@ def check_password_rules(password): match_obj = re.match(pattern, password) return bool(match_obj) + + +def set_user_login_failed_count_to_cache(key_limit, key_block): + count = cache.get(key_limit) + count = count + 1 if count else 1 + + setting_limit_time = Setting.objects.filter( + name='SECURITY_LOGIN_LIMIT_TIME' + ).first() + limit_time = setting_limit_time.cleaned_value if setting_limit_time \ + else settings.DEFAULT_LOGIN_LIMIT_TIME + + setting_limit_count = Setting.objects.filter( + name='SECURITY_LOGIN_LIMIT_COUNT' + ).first() + limit_count = setting_limit_count.cleaned_value if setting_limit_count \ + else settings.DEFAULT_LOGIN_LIMIT_COUNT + + if count >= limit_count: + cache.set(key_block, 1, int(limit_time)*60) + + cache.set(key_limit, count, int(limit_time)*60) + + +def is_block_login(key_limit): + count = cache.get(key_limit) + + setting_limit_count = Setting.objects.filter( + name='SECURITY_LOGIN_LIMIT_COUNT' + ).first() + limit_count = setting_limit_count.cleaned_value if setting_limit_count \ + else settings.DEFAULT_LOGIN_LIMIT_COUNT + + if count and count >= limit_count: + return True + + +def is_need_unblock(key_block): + if not cache.get(key_block): + return False + return True diff --git a/apps/users/views/login.py b/apps/users/views/login.py index feaf47e89..fc7caf305 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -25,8 +25,10 @@ from common.utils import get_object_or_none from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from common.models import Setting from ..models import User, LoginLog -from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \ - get_user_or_tmp_user, set_tmp_user_to_cache, get_password_check_rules, check_password_rules +from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \ + redirect_user_first_login_or_index, get_user_or_tmp_user, \ + set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \ + is_block_login, set_user_login_failed_count_to_cache from ..tasks import write_login_log_async from .. import forms @@ -47,7 +49,9 @@ class UserLoginView(FormView): form_class = forms.UserLoginForm form_class_captcha = forms.UserLoginCaptchaForm redirect_field_name = 'next' - key_prefix = "_LOGIN_INVALID_{}" + key_prefix_captcha = "_LOGIN_INVALID_{}" + key_prefix_limit = "_LOGIN_LIMIT_{}_{}" + key_prefix_block = "_LOGIN_BLOCK_{}" def get(self, request, *args, **kwargs): if request.user.is_staff: @@ -57,6 +61,16 @@ class UserLoginView(FormView): request.session.set_test_cookie() return super().get(request, *args, **kwargs) + def post(self, request, *args, **kwargs): + # limit login authentication + ip = get_login_ip(request) + username = self.request.POST.get('username') + key_limit = self.key_prefix_limit.format(username, ip) + if is_block_login(key_limit): + return self.render_to_response(self.get_context_data(block_login=True)) + + return super().post(request, *args, **kwargs) + def form_valid(self, form): if not self.request.session.test_cookie_worked(): return HttpResponse(_("Please enable cookies and try again.")) @@ -65,8 +79,24 @@ class UserLoginView(FormView): return redirect(self.get_success_url()) def form_invalid(self, form): + # write login failed log + username = form.cleaned_data.get('username') + data = { + 'username': username, + 'mfa': LoginLog.MFA_UNKNOWN, + 'reason': LoginLog.REASON_PASSWORD, + 'status': False + } + self.write_login_log(data) + + # limit user login failed count ip = get_login_ip(self.request) - cache.set(self.key_prefix.format(ip), 1, 3600) + key_limit = self.key_prefix_limit.format(username, ip) + key_block = self.key_prefix_block.format(username) + set_user_login_failed_count_to_cache(key_limit, key_block) + + # show captcha + cache.set(self.key_prefix_captcha.format(ip), 1, 3600) old_form = form form = self.form_class_captcha(data=form.data) form._errors = old_form.errors @@ -74,7 +104,7 @@ class UserLoginView(FormView): def get_form_class(self): ip = get_login_ip(self.request) - if cache.get(self.key_prefix.format(ip)): + if cache.get(self.key_prefix_captcha.format(ip)): return self.form_class_captcha else: return self.form_class @@ -91,7 +121,13 @@ class UserLoginView(FormView): elif not user.otp_enabled: # 0 & T,F auth_login(self.request, user) - self.write_login_log() + data = { + 'username': self.request.user.username, + 'mfa': int(self.request.user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(data) return redirect_user_first_login_or_index(self.request, self.redirect_field_name) def get_context_data(self, **kwargs): @@ -101,13 +137,16 @@ class UserLoginView(FormView): kwargs.update(context) return super().get_context_data(**kwargs) - def write_login_log(self): + def write_login_log(self, data): login_ip = get_login_ip(self.request) user_agent = self.request.META.get('HTTP_USER_AGENT', '') - write_login_log_async.delay( - self.request.user.username, type='W', - ip=login_ip, user_agent=user_agent - ) + tmp_data = { + 'ip': login_ip, + 'type': 'W', + 'user_agent': user_agent + } + data.update(tmp_data) + write_login_log_async.delay(**data) class UserLoginOtpView(FormView): @@ -122,22 +161,38 @@ class UserLoginOtpView(FormView): if check_otp_code(otp_secret_key, otp_code): auth_login(self.request, user) - self.write_login_log() + data = { + 'username': self.request.user.username, + 'mfa': int(self.request.user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(data) return redirect(self.get_success_url()) else: + data = { + 'username': user.username, + 'mfa': int(user.otp_enabled), + 'reason': LoginLog.REASON_MFA, + 'status': False + } + self.write_login_log(data) form.add_error('otp_code', _('MFA code invalid')) return super().form_invalid(form) def get_success_url(self): return redirect_user_first_login_or_index(self.request, self.redirect_field_name) - def write_login_log(self): + def write_login_log(self, data): login_ip = get_login_ip(self.request) user_agent = self.request.META.get('HTTP_USER_AGENT', '') - write_login_log_async.delay( - self.request.user.username, type='W', - ip=login_ip, user_agent=user_agent - ) + tmp_data = { + 'ip': login_ip, + 'type': 'W', + 'user_agent': user_agent + } + data.update(tmp_data) + write_login_log_async.delay(**data) @method_decorator(never_cache, name='dispatch') diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 094d2ced2..56d551efa 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -36,7 +36,9 @@ from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen from common.models import Setting from .. import forms from ..models import User, UserGroup -from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_user_or_tmp_user, get_password_check_rules, check_password_rules +from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, \ + get_user_or_tmp_user, get_password_check_rules, check_password_rules, \ + is_need_unblock from ..signals import post_user_create from ..tasks import write_login_log_async @@ -168,13 +170,17 @@ class UserDetailView(AdminUserRequiredMixin, DetailView): model = User template_name = 'users/user_detail.html' context_object_name = "user_object" + key_prefix_block = "_LOGIN_BLOCK_{}" def get_context_data(self, **kwargs): + user = self.get_object() + key_block = self.key_prefix_block.format(user.username) groups = UserGroup.objects.exclude(id__in=self.object.groups.all()) context = { 'app': _('Users'), 'action': _('User detail'), - 'groups': groups + 'groups': groups, + 'unblock': is_need_unblock(key_block), } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/config_example.py b/config_example.py index 0c8d87094..a96f0d7c9 100644 --- a/config_example.py +++ b/config_example.py @@ -21,10 +21,10 @@ class Config: ALLOWED_HOSTS = ['*'] # Development env open this, when error occur display the full process track, Production disable it - DEBUG = True + DEBUG = os.environ.get("DEBUG") or True # DEBUG, INFO, WARNING, ERROR, CRITICAL can set. See https://docs.djangoproject.com/en/1.10/topics/logging/ - LOG_LEVEL = 'DEBUG' + LOG_LEVEL = os.environ.get("LOG_LEVEL") or 'DEBUG' LOG_DIR = os.path.join(BASE_DIR, 'logs') # Database setting, Support sqlite3, mysql, postgres .... @@ -35,12 +35,12 @@ class Config: DB_NAME = os.path.join(BASE_DIR, 'data', 'db.sqlite3') # MySQL or postgres setting like: - # DB_ENGINE = 'mysql' - # DB_HOST = '127.0.0.1' - # DB_PORT = 3306 - # DB_USER = 'root' - # DB_PASSWORD = '' - # DB_NAME = 'jumpserver' + # DB_ENGINE = os.environ.get("DB_ENGINE") or 'mysql' + # DB_HOST = os.environ.get("DB_HOST") or '127.0.0.1' + # DB_PORT = os.environ.get("DB_PORT") or 3306 + # DB_USER = os.environ.get("DB_USER") or 'jumpserver' + # DB_PASSWORD = os.environ.get("DB_PASSWORD") or 'weakPassword' + # DB_NAME = os.environ.get("DB_NAME") or 'jumpserver' # When Django start it will bind this host and port # ./manage.py runserver 127.0.0.1:8080 @@ -48,9 +48,11 @@ class Config: HTTP_LISTEN_PORT = 8080 # Use Redis as broker for celery and web socket - REDIS_HOST = '127.0.0.1' - REDIS_PORT = 6379 - REDIS_PASSWORD = '' + REDIS_HOST = os.environ.get("REDIS_HOST") or '127.0.0.1' + REDIS_PORT = os.environ.get("REDIS_PORT") or 6379 + REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD") or '' + REDIS_DB_CELERY = os.environ.get('REDIS_DB') or 3 + REDIS_DB_CACHE = os.environ.get('REDIS_DB') or 4 def __init__(self): pass diff --git a/requirements/deb_requirements.txt b/requirements/deb_requirements.txt index a0ddb7642..f4131a3ea 100644 --- a/requirements/deb_requirements.txt +++ b/requirements/deb_requirements.txt @@ -1 +1 @@ -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 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index bcba2627c..25d28f259 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -61,7 +61,7 @@ pytz==2018.3 PyYAML==3.12 redis==2.10.6 requests==2.18.4 -jms-storage==0.0.17 +jms-storage==0.0.18 s3transfer==0.1.13 simplejson==3.13.2 six==1.11.0 diff --git a/utils/make_migrations.sh b/utils/make_migrations.sh index fdf1f6efb..4fb8fdacf 100755 --- a/utils/make_migrations.sh +++ b/utils/make_migrations.sh @@ -4,3 +4,5 @@ python3 ../apps/manage.py makemigrations python3 ../apps/manage.py migrate + +python3 ../apps/manage.py makemigrations --merge