From 512fc8f8f0fa2ce46f3d03850c2871acf426a15c Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 5 Jul 2018 16:23:33 +0800 Subject: [PATCH] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=AC=A1=E6=95=B0=E9=99=90=E5=88=B6=E7=9A=84?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=AE=89=E5=85=A8=E8=AE=BE=E7=BD=AE=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/forms.py | 26 +++++++-- .../templates/common/security_setting.html | 4 +- apps/i18n/zh/LC_MESSAGES/django.mo | Bin 36297 -> 36708 bytes apps/i18n/zh/LC_MESSAGES/django.po | 52 ++++++++++++------ apps/jumpserver/settings.py | 2 + apps/users/api.py | 20 +++++-- apps/users/templates/users/login.html | 2 +- apps/users/utils.py | 26 +++++++++ apps/users/views/login.py | 16 +++--- 9 files changed, 109 insertions(+), 39 deletions(-) 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 b5474cbe1cdec6a6fb164cf841611e659e75bd94..c9b42d9f4194e85347abe5bcb51aa9c2a1901b5a 100644 GIT binary patch delta 12198 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 delta 11803 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 diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index 56fabe11a..b1e588650 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-04 16:46+0800\n" +"POT-Creation-Date: 2018-07-05 14:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -1433,45 +1433,60 @@ msgid "" "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:184 +#: common/forms.py:186 +msgid "Limit the number of login failures" +msgstr "限制登录失败次数" + +#: common/forms.py:193 +msgid "No logon interval" +msgstr "禁止登录时间间隔" + +#: common/forms.py:195 +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:201 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:191 +#: common/forms.py:208 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:193 +#: common/forms.py:210 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:199 +#: common/forms.py:216 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:200 +#: common/forms.py:217 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:206 +#: common/forms.py:223 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:207 +#: common/forms.py:224 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:213 +#: common/forms.py:230 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:214 +#: common/forms.py:231 msgid "" "After opening, the user password changes and resets must contain special " "characters" @@ -1532,8 +1547,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" @@ -2307,6 +2322,10 @@ msgid "" "You should use your ssh client tools connect terminal: {}

{}" msgstr "你可以使用ssh客户端工具连接终端" +#: users/api.py:210 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 "" @@ -2649,10 +2668,6 @@ msgstr "忘记密码" msgid "Input your email, that will send a mail to your" msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" -#: users/templates/users/login.html:50 -msgid "Log in frequently and try again later" -msgstr "登录频繁, 稍后重试" - #: users/templates/users/login.html:53 msgid "Captcha invalid" msgstr "验证码错误" @@ -3049,7 +3064,7 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:74 +#: users/views/login.py:75 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" @@ -3149,3 +3164,6 @@ msgstr "MFA 解绑成功" #: users/views/user.py:555 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" + +#~ msgid "MFA setting" +#~ msgstr "MFA 设置" diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index c8ed1505e..c0a536276 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -405,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/users/api.py b/apps/users/api.py index dd1b76ba3..cf13b9aea 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -3,6 +3,7 @@ import uuid from django.core.cache import cache from django.urls import reverse +from django.utils.translation import ugettext as _ from rest_framework import generics from rest_framework.permissions import AllowAny, IsAuthenticated @@ -17,7 +18,8 @@ from .tasks import write_login_log_async from .models import User, UserGroup, LoginLog from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \ IsSuperUserOrAppUser -from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code +from .utils import check_user_valid, generate_token, get_login_ip, \ + check_otp_code, set_user_login_failed_count_to_cache, is_block_login from common.mixins import IDInFilterMixin from common.utils import get_logger @@ -149,7 +151,6 @@ class UserOtpAuthApi(APIView): return Response({'msg': '请先进行用户名和密码验证'}, status=401) if not check_otp_code(user.otp_secret_key, otp_code): - # Write login failed log data = { 'username': user.username, 'mfa': int(user.otp_enabled), @@ -159,7 +160,6 @@ class UserOtpAuthApi(APIView): self.write_login_log(request, data) return Response({'msg': 'MFA认证失败'}, status=401) - # Write login success log data = { 'username': user.username, 'mfa': int(user.otp_enabled), @@ -196,12 +196,21 @@ class UserOtpAuthApi(APIView): class UserAuthApi(APIView): permission_classes = (AllowAny,) serializer_class = UserSerializer + key_prefix_limit = "_LOGIN_LIMIT_{}_{}" def post(self, request): user, msg = self.check_user_valid(request) + username = request.data.get('username') + ip = request.data.get('remote_addr', None) + if not ip: + ip = get_login_ip(request) + key_limit = self.key_prefix_limit.format(ip, username) + if is_block_login(key_limit): + msg = _("Log in frequently and try again later") + return Response({'msg': msg}, status=401) + if not user: - # Write login failed log data = { 'username': request.data.get('username', ''), 'mfa': LoginLog.MFA_UNKNOWN, @@ -209,10 +218,11 @@ class UserAuthApi(APIView): 'status': False } self.write_login_log(request, data) + + set_user_login_failed_count_to_cache(key_limit) return Response({'msg': msg}, status=401) if not user.otp_enabled: - # Write login success log data = { 'username': user.username, 'mfa': int(user.otp_enabled), diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html index 7ea824502..6b55a58bf 100644 --- a/apps/users/templates/users/login.html +++ b/apps/users/templates/users/login.html @@ -46,7 +46,7 @@
{% csrf_token %} - {% if login_limit %} + {% if block_login %}

{% trans 'Log in frequently and try again later' %}

{% elif form.errors %} {% if 'captcha' in form.errors %} diff --git a/apps/users/utils.py b/apps/users/utils.py index fb2a8d93e..bc03eecbd 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -332,3 +332,29 @@ 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): + 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 + + 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 diff --git a/apps/users/views/login.py b/apps/users/views/login.py index f58ef7b8f..94071924f 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -27,7 +27,8 @@ 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 + 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 @@ -63,9 +64,9 @@ class UserLoginView(FormView): # limit login authentication ip = get_login_ip(request) username = self.request.POST.get('username') - count = cache.get(self.key_prefix_limit.format(ip, username)) - if count and count >= 3: - return self.render_to_response(self.get_context_data(login_limit=True)) + key_limit = self.key_prefix_limit.format(ip, username) + if is_block_login(key_limit): + return self.render_to_response(self.get_context_data(block_login=True)) return super().post(request, *args, **kwargs) @@ -87,13 +88,12 @@ class UserLoginView(FormView): } self.write_login_log(data) - # limit user login failed times + # limit user login failed count ip = get_login_ip(self.request) key_limit = self.key_prefix_limit.format(ip, username) - count = cache.get(key_limit) - count = count + 1 if count else 1 - cache.set(key_limit, count, 1800) + set_user_login_failed_count_to_cache(key_limit) + # show captcha cache.set(self.key_prefix_captcha.format(ip), 1, 3600) old_form = form form = self.form_class_captcha(data=form.data)