diff --git a/apps/assets/templates/assets/gateway_create_update.html b/apps/assets/templates/assets/gateway_create_update.html index 7c1bf14b2..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') + $(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') + $(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 f0ed6030c..dd188bdab 100644 --- a/apps/assets/templates/assets/system_user_detail.html +++ b/apps/assets/templates/assets/system_user_detail.html @@ -152,15 +152,14 @@ - - - {% trans 'Clear auth' %}: - - - - - - +{# #} +{# {% trans 'Clear auth' %}:#} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} {# #} {# {% trans 'Change auth period' %}:#} diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index 78d56177e..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 0cf576ebe..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-07-06 13:11+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" @@ -32,7 +32,7 @@ msgstr "" #: 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:179 perms/models.py:33 +#: assets/templates/assets/system_user_detail.html:178 perms/models.py:33 msgid "Nodes" msgstr "节点管理" @@ -101,7 +101,7 @@ msgstr "端口" #: assets/forms/domain.py:14 assets/forms/label.py:13 #: 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:30 audits/models.py:11 #: audits/templates/audits/ftp_log_list.html:41 @@ -125,7 +125,7 @@ msgstr "资产" #: 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 @@ -356,7 +356,7 @@ 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/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 @@ -756,7 +756,7 @@ 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:89 @@ -780,7 +780,7 @@ 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:90 @@ -808,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:196 +#: 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 @@ -841,7 +842,7 @@ 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:35 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 @@ -905,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 "更新成功" @@ -1002,8 +1003,9 @@ msgstr "存在资产,不能删除" #: assets/templates/assets/asset_list.html:633 #: assets/templates/assets/system_user_list.html:134 -#: users/templates/users/user_detail.html:361 -#: users/templates/users/user_detail.html:386 +#: 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?" @@ -1043,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 "网关" @@ -1063,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 "创建网域" @@ -1106,20 +1108,15 @@ msgstr "家目录" msgid "Uid" msgstr "Uid" -#: assets/templates/assets/system_user_detail.html:157 -#: assets/templates/assets/system_user_detail.html:343 -msgid "Clear auth" -msgstr "清除认证信息" - -#: assets/templates/assets/system_user_detail.html:160 -msgid "Clear" -msgstr "清除" - -#: assets/templates/assets/system_user_detail.html:187 +#: assets/templates/assets/system_user_detail.html:186 msgid "Add to node" msgstr "添加到节点" -#: assets/templates/assets/system_user_detail.html:343 +#: assets/templates/assets/system_user_detail.html:353 +msgid "Clear auth" +msgstr "清除认证信息" + +#: assets/templates/assets/system_user_detail.html:353 msgid "success" msgstr "成功" @@ -1238,6 +1235,7 @@ msgstr "文件名" #: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:66 +#: users/templates/users/user_detail.html:443 msgid "Success" msgstr "成功" @@ -1854,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 "用户组" @@ -1907,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 "加入" @@ -2326,7 +2324,7 @@ msgid "" "You should use your ssh client tools connect terminal: {}

{}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api.py:208 users/templates/users/login.html:50 +#: users/api.py:221 users/templates/users/login.html:50 msgid "Log in frequently and try again later" msgstr "登录频繁, 稍后重试" @@ -2405,7 +2403,7 @@ msgstr "" msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:73 users/templates/users/user_detail.html:200 +#: users/forms.py:73 users/templates/users/user_detail.html:208 msgid "Join user groups" msgstr "添加到用户组" @@ -2701,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 "重置密码" @@ -2773,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 "取消" @@ -3169,5 +3180,11 @@ msgstr "MFA 解绑成功" 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/users/api.py b/apps/users/api.py index c23112384..840b8c913 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -95,6 +95,22 @@ class UserUpdatePKApi(generics.UpdateAPIView): user.save() +class UserUnblockPKApi(generics.UpdateAPIView): + queryset = User.objects.all() + permission_classes = (IsSuperUser,) + serializer_class = UserSerializer + key_prefix_limit = "_LOGIN_LIMIT_{}_{}" + key_prefix_block = "_LOGIN_BLOCK_{}" + + def perform_update(self, serializer): + user = self.get_object() + username = user.username if user else '' + key_limit = self.key_prefix_limit.format(username, '*') + key_block = self.key_prefix_block.format(username) + cache.delete_pattern(key_limit) + cache.delete(key_block) + + class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): queryset = UserGroup.objects.all() serializer_class = UserGroupSerializer @@ -197,13 +213,15 @@ class UserAuthApi(APIView): permission_classes = (AllowAny,) serializer_class = UserSerializer key_prefix_limit = "_LOGIN_LIMIT_{}_{}" + key_prefix_block = "_LOGIN_BLOCK_{}" def post(self, request): # limit login username = request.data.get('username') ip = request.data.get('remote_addr', None) ip = ip if ip else get_login_ip(request) - key_limit = self.key_prefix_limit.format(ip, username) + key_limit = self.key_prefix_limit.format(username, ip) + key_block = self.key_prefix_block.format(username) if is_block_login(key_limit): msg = _("Log in frequently and try again later") return Response({'msg': msg}, status=401) @@ -218,7 +236,7 @@ class UserAuthApi(APIView): } self.write_login_log(request, data) - set_user_login_failed_count_to_cache(key_limit) + set_user_login_failed_count_to_cache(key_limit, key_block) return Response({'msg': msg}, status=401) if not user.otp_enabled: diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html index d40251563..b664d917f 100644 --- a/apps/users/templates/users/user_detail.html +++ b/apps/users/templates/users/user_detail.html @@ -182,6 +182,14 @@ + + {% trans 'Unblock user' %} + + + + + +
@@ -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 %}"; @@ -426,6 +434,40 @@ $(document).ready(function() { 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/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/utils.py b/apps/users/utils.py index 937a90867..7cbaa75f0 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -333,7 +333,7 @@ def check_password_rules(password): return bool(match_obj) -def set_user_login_failed_count_to_cache(key_limit): +def set_user_login_failed_count_to_cache(key_limit, key_block): count = cache.get(key_limit) count = count + 1 if count else 1 @@ -343,6 +343,15 @@ def set_user_login_failed_count_to_cache(key_limit): 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) @@ -357,3 +366,9 @@ def is_block_login(key_limit): 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 94071924f..fc7caf305 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -51,6 +51,7 @@ class UserLoginView(FormView): redirect_field_name = 'next' 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: @@ -64,7 +65,7 @@ class UserLoginView(FormView): # limit login authentication ip = get_login_ip(request) username = self.request.POST.get('username') - key_limit = self.key_prefix_limit.format(ip, 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)) @@ -90,8 +91,9 @@ class UserLoginView(FormView): # limit user login failed count ip = get_login_ip(self.request) - key_limit = self.key_prefix_limit.format(ip, username) - set_user_login_failed_count_to_cache(key_limit) + 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) 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)