From e1be86791351e987e706126e6a2c74a0fa20910d Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 3 Jul 2018 17:47:12 +0800 Subject: [PATCH 1/7] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=99=BB=E5=BD=95=E6=97=A5=E5=BF=97=EF=BC=8C=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E7=99=BB=E5=BD=95=E5=A4=B1=E8=B4=A5=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0MFA=E5=90=AF=E7=94=A8=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/i18n/zh/LC_MESSAGES/django.mo | Bin 35966 -> 36216 bytes apps/i18n/zh/LC_MESSAGES/django.po | 118 +++++++++++------- apps/users/api.py | 66 +++++++--- apps/users/models/authentication.py | 28 +++++ .../users/templates/users/login_log_list.html | 6 + apps/users/utils.py | 12 +- apps/users/views/login.py | 66 +++++++--- 7 files changed, 217 insertions(+), 79 deletions(-) diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index d842930e4e5b0eece76196f4cd257d50a9117912..339af4456cb72a4e21841737e7c98bce3a39fc6a 100644 GIT binary patch delta 12337 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\n" "Language-Team: Jumpserver team\n" @@ -118,7 +118,7 @@ msgstr "端口" msgid "Asset" msgstr "资产" -#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:138 +#: 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 @@ -147,14 +147,14 @@ msgstr "资产" msgid "Name" msgstr "名称" -#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:139 +#: 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:31 users/models/authentication.py:45 users/models/user.py:47 +#: 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:56 #: users/templates/users/login_log_list.html:49 @@ -192,21 +192,21 @@ msgstr "ssh密钥不合法" msgid "Password and private key file must be input one" msgstr "密码和私钥, 必须输入一个" -#: assets/forms/user.py:124 +#: assets/forms/user.py:125 msgid "* Automatic login mode, must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/forms/user.py:144 +#: assets/forms/user.py:145 msgid "Auto push system user to asset" msgstr "自动推送系统用户到资产" -#: assets/forms/user.py:145 +#: 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/forms/user.py:147 +#: assets/forms/user.py:148 msgid "" "If you choose manual login mode, you do not need to fill in the username and " "password." @@ -1237,7 +1237,7 @@ 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 msgid "Success" msgstr "成功" @@ -1485,7 +1485,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 "启用" @@ -1803,7 +1804,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 "日期" @@ -2045,7 +2046,7 @@ 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:65 +#: users/views/login.py:311 users/views/login.py:369 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" @@ -2406,8 +2407,9 @@ msgstr "" msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全." -#: users/forms.py:143 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" @@ -2467,23 +2469,53 @@ 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 "登录日期" @@ -2646,7 +2678,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:352 users/utils.py:81 msgid "Reset password" msgstr "重置密码" @@ -2696,10 +2728,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" @@ -2867,11 +2895,11 @@ msgstr "新的公钥已设置成功,请下载对应的私钥" msgid "Update user" msgstr "更新用户" -#: users/utils.py:41 +#: users/utils.py:42 msgid "Create account successfully" msgstr "创建账户成功" -#: users/utils.py:43 +#: users/utils.py:44 #, python-format msgid "" "\n" @@ -2916,7 +2944,7 @@ msgstr "" "
\n" " " -#: users/utils.py:82 +#: users/utils.py:83 #, python-format msgid "" "\n" @@ -2960,11 +2988,11 @@ msgstr "" "
\n" " " -#: users/utils.py:113 +#: users/utils.py:114 msgid "SSH Key Reset" msgstr "重置ssh密钥" -#: users/utils.py:115 +#: users/utils.py:116 #, python-format msgid "" "\n" @@ -2989,15 +3017,15 @@ msgstr "" "
\n" " " -#: users/utils.py:148 +#: users/utils.py:149 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:150 +#: users/utils.py:151 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:163 +#: users/utils.py:164 msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" @@ -3017,60 +3045,60 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:62 +#: users/views/login.py:63 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:128 users/views/user.py:500 users/views/user.py:525 +#: users/views/login.py:159 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:188 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:155 +#: users/views/login.py:189 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:171 +#: users/views/login.py:205 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:184 +#: users/views/login.py:218 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:185 +#: users/views/login.py:219 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:232 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:199 +#: users/views/login.py:233 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:220 users/views/login.py:233 +#: users/views/login.py:254 users/views/login.py:267 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:229 +#: users/views/login.py:263 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:239 users/views/user.py:118 users/views/user.py:398 +#: users/views/login.py:273 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:311 msgid "First login" msgstr "首次登陆" -#: users/views/login.py:336 +#: users/views/login.py:370 msgid "Login log list" msgstr "登录日志" diff --git a/apps/users/api.py b/apps/users/api.py index dbc5b66a8..5df312fcf 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -14,7 +14,7 @@ from .serializers import UserSerializer, UserGroupSerializer, \ UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \ UserUpdateGroupSerializer, ChangeUserPasswordSerializer from .tasks import write_login_log_async -from .models import User, UserGroup +from .models import User, UserGroup, LoginLog from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \ IsSuperUserOrAppUser from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code @@ -153,10 +153,25 @@ class UserOtpAuthApi(APIView): return Response({'msg': '请先进行用户名和密码验证'}, status=401) if not check_otp_code(user.otp_secret_key, otp_code): + # Write login failed log + kwargs = { + 'username': user.username, + 'mfa': int(user.otp_enabled), + 'reason': LoginLog.REASON_MFA, + 'status': False + } + self.write_login_log(request, **kwargs) return Response({'msg': 'MFA认证失败'}, status=401) + # Write login success log + kwargs = { + 'username': user.username, + 'mfa': int(user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(request, **kwargs) token = generate_token(request, user) - self.write_login_log(request, user) return Response( { 'token': token, @@ -165,7 +180,7 @@ class UserOtpAuthApi(APIView): ) @staticmethod - def write_login_log(request, user): + def write_login_log(request, **kwargs): login_ip = request.data.get('remote_addr', None) login_type = request.data.get('login_type', '') user_agent = request.data.get('HTTP_USER_AGENT', '') @@ -173,10 +188,13 @@ class UserOtpAuthApi(APIView): if not login_ip: login_ip = get_login_ip(request) - write_login_log_async.delay( - user.username, ip=login_ip, - type=login_type, user_agent=user_agent, - ) + data = { + 'ip': login_ip, + 'type': login_type, + 'user_agent': user_agent + } + kwargs.update(data) + write_login_log_async.delay(**kwargs) class UserAuthApi(APIView): @@ -187,11 +205,26 @@ class UserAuthApi(APIView): user, msg = self.check_user_valid(request) if not user: + # Write login failed log + kwargs = { + 'username': request.data.get('username', ''), + 'mfa': LoginLog.MFA_UNKNOWN, + 'reason': LoginLog.REASON_PASSWORD, + 'status': False + } + self.write_login_log(request, **kwargs) return Response({'msg': msg}, status=401) if not user.otp_enabled: + # Write login success log + kwargs = { + 'username': user.username, + 'mfa': int(user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(request, **kwargs) token = generate_token(request, user) - self.write_login_log(request, user) return Response( { 'token': token, @@ -208,7 +241,8 @@ class UserAuthApi(APIView): 'otp_url': reverse('api-users:user-otp-auth'), 'seed': seed, 'user': self.serializer_class(user).data - }, status=300) + }, status=300 + ) @staticmethod def check_user_valid(request): @@ -222,7 +256,7 @@ class UserAuthApi(APIView): return user, msg @staticmethod - def write_login_log(request, user): + def write_login_log(request, **kwargs): login_ip = request.data.get('remote_addr', None) login_type = request.data.get('login_type', '') user_agent = request.data.get('HTTP_USER_AGENT', '') @@ -230,10 +264,14 @@ class UserAuthApi(APIView): if not login_ip: login_ip = get_login_ip(request) - write_login_log_async.delay( - user.username, ip=login_ip, - type=login_type, user_agent=user_agent, - ) + data = { + 'ip': login_ip, + 'type': login_type, + 'user_agent': user_agent, + } + kwargs.update(data) + + write_login_log_async.delay(**kwargs) class UserConnectionTokenApi(APIView): diff --git a/apps/users/models/authentication.py b/apps/users/models/authentication.py index 5169a79d2..493e4bb59 100644 --- a/apps/users/models/authentication.py +++ b/apps/users/models/authentication.py @@ -41,12 +41,40 @@ class LoginLog(models.Model): ('W', 'Web'), ('T', 'Terminal'), ) + + MFA_DISABLED = 0 + MFA_ENABLED = 1 + MFA_UNKNOWN = 2 + + MFA_CHOICE = ( + (MFA_DISABLED, _('Disabled')), + (MFA_ENABLED, _('Enabled')), + (MFA_UNKNOWN, _('-')), + ) + + REASON_NOTHING = 0 + REASON_PASSWORD = 1 + REASON_MFA = 2 + + REASON_CHOICE = ( + (REASON_NOTHING, _('-')), + (REASON_PASSWORD, _('Username/password check failed')), + (REASON_MFA, _('MFA authentication failed')), + ) + + STATUS_CHOICE = ( + (True, _('Success')), + (False, _('Failed')) + ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) username = models.CharField(max_length=20, verbose_name=_('Username')) type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) ip = models.GenericIPAddressField(verbose_name=_('Login ip')) city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city')) user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent')) + mfa = models.SmallIntegerField(default=MFA_DISABLED, choices=MFA_CHOICE, verbose_name=_('MFA')) + reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason')) + status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login')) class Meta: diff --git a/apps/users/templates/users/login_log_list.html b/apps/users/templates/users/login_log_list.html index 4a08c28db..afaf671a5 100644 --- a/apps/users/templates/users/login_log_list.html +++ b/apps/users/templates/users/login_log_list.html @@ -51,6 +51,9 @@ {% trans 'UA' %} {% trans 'IP' %} {% trans 'City' %} + {% trans 'MFA' %} + {% trans 'Reason' %} + {% trans 'Status' %} {% trans 'Date' %} {% endblock %} @@ -65,6 +68,9 @@ {{ login_log.ip }} {{ login_log.city }} + {{ login_log.get_mfa_display }} + {{ login_log.get_reason_display }} + {{ login_log.get_status_display }} {{ login_log.datetime }} {% endfor %} diff --git a/apps/users/utils.py b/apps/users/utils.py index 989632e2c..fb2a8d93e 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 @@ -22,6 +22,7 @@ from common.utils import reverse, get_object_or_none from common.models import Setting from common.forms import SecuritySettingForm from .models import User, LoginLog +# from .tasks import write_login_log_async logger = logging.getLogger('jumpserver') @@ -200,16 +201,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): diff --git a/apps/users/views/login.py b/apps/users/views/login.py index feaf47e89..02f3b66af 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -25,8 +25,9 @@ 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 from ..tasks import write_login_log_async from .. import forms @@ -65,6 +66,15 @@ class UserLoginView(FormView): return redirect(self.get_success_url()) def form_invalid(self, form): + # Write login failed log + kwargs = { + 'username': form.cleaned_data.get('username'), + 'mfa': LoginLog.MFA_UNKNOWN, + 'reason': LoginLog.REASON_PASSWORD, + 'status': False + } + self.write_login_log(**kwargs) + ip = get_login_ip(self.request) cache.set(self.key_prefix.format(ip), 1, 3600) old_form = form @@ -91,7 +101,14 @@ class UserLoginView(FormView): elif not user.otp_enabled: # 0 & T,F auth_login(self.request, user) - self.write_login_log() + # Write login success log + kwargs = { + 'username': self.request.user.username, + 'mfa': int(self.request.user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(**kwargs) return redirect_user_first_login_or_index(self.request, self.redirect_field_name) def get_context_data(self, **kwargs): @@ -101,13 +118,16 @@ class UserLoginView(FormView): kwargs.update(context) return super().get_context_data(**kwargs) - def write_login_log(self): + def write_login_log(self, **kwargs): 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 - ) + data = { + 'ip': login_ip, + 'type': 'W', + 'user_agent': user_agent + } + kwargs.update(data) + write_login_log_async.delay(**kwargs) class UserLoginOtpView(FormView): @@ -122,22 +142,40 @@ class UserLoginOtpView(FormView): if check_otp_code(otp_secret_key, otp_code): auth_login(self.request, user) - self.write_login_log() + # Write login success log + kwargs = { + 'username': self.request.user.username, + 'mfa': int(self.request.user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(**kwargs) return redirect(self.get_success_url()) else: + # Write login failed log + kwargs = { + 'username': user.username, + 'mfa': int(user.otp_enabled), + 'reason': LoginLog.REASON_MFA, + 'status': False + } + self.write_login_log(**kwargs) 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, **kwargs): 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 - ) + data = { + 'ip': login_ip, + 'type': 'W', + 'user_agent': user_agent + } + kwargs.update(data) + write_login_log_async.delay(**kwargs) @method_decorator(never_cache, name='dispatch') From 43370c547ae94ed74aa5e37cbc14e3a396f64b28 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Wed, 4 Jul 2018 10:57:37 +0800 Subject: [PATCH 2/7] =?UTF-8?q?[Update]=20=E7=99=BB=E5=BD=95=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E9=80=BB=E8=BE=91=E4=BF=AE=E6=94=B9=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api.py | 32 ++++++++++++++++---------------- apps/users/views/login.py | 32 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/users/api.py b/apps/users/api.py index 18457a2b4..dd1b76ba3 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -150,23 +150,23 @@ class UserOtpAuthApi(APIView): if not check_otp_code(user.otp_secret_key, otp_code): # Write login failed log - kwargs = { + data = { 'username': user.username, 'mfa': int(user.otp_enabled), 'reason': LoginLog.REASON_MFA, 'status': False } - self.write_login_log(request, **kwargs) + self.write_login_log(request, data) return Response({'msg': 'MFA认证失败'}, status=401) # Write login success log - kwargs = { + data = { 'username': user.username, 'mfa': int(user.otp_enabled), 'reason': LoginLog.REASON_NOTHING, 'status': True } - self.write_login_log(request, **kwargs) + self.write_login_log(request, data) token = generate_token(request, user) return Response( { @@ -176,7 +176,7 @@ class UserOtpAuthApi(APIView): ) @staticmethod - def write_login_log(request, **kwargs): + def write_login_log(request, data): login_ip = request.data.get('remote_addr', None) login_type = request.data.get('login_type', '') user_agent = request.data.get('HTTP_USER_AGENT', '') @@ -184,13 +184,13 @@ class UserOtpAuthApi(APIView): if not login_ip: login_ip = get_login_ip(request) - data = { + tmp_data = { 'ip': login_ip, 'type': login_type, 'user_agent': user_agent } - kwargs.update(data) - write_login_log_async.delay(**kwargs) + data.update(tmp_data) + write_login_log_async.delay(**data) class UserAuthApi(APIView): @@ -202,24 +202,24 @@ class UserAuthApi(APIView): if not user: # Write login failed log - kwargs = { + data = { 'username': request.data.get('username', ''), 'mfa': LoginLog.MFA_UNKNOWN, 'reason': LoginLog.REASON_PASSWORD, 'status': False } - self.write_login_log(request, **kwargs) + self.write_login_log(request, data) return Response({'msg': msg}, status=401) if not user.otp_enabled: # Write login success log - kwargs = { + data = { 'username': user.username, 'mfa': int(user.otp_enabled), 'reason': LoginLog.REASON_NOTHING, 'status': True } - self.write_login_log(request, **kwargs) + self.write_login_log(request, data) token = generate_token(request, user) return Response( { @@ -252,7 +252,7 @@ class UserAuthApi(APIView): return user, msg @staticmethod - def write_login_log(request, **kwargs): + def write_login_log(request, data): login_ip = request.data.get('remote_addr', None) login_type = request.data.get('login_type', '') user_agent = request.data.get('HTTP_USER_AGENT', '') @@ -260,14 +260,14 @@ class UserAuthApi(APIView): if not login_ip: login_ip = get_login_ip(request) - data = { + tmp_data = { 'ip': login_ip, 'type': login_type, 'user_agent': user_agent, } - kwargs.update(data) + data.update(tmp_data) - write_login_log_async.delay(**kwargs) + write_login_log_async.delay(**data) class UserConnectionTokenApi(APIView): diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 02f3b66af..7e853e45f 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -67,13 +67,13 @@ class UserLoginView(FormView): def form_invalid(self, form): # Write login failed log - kwargs = { + data = { 'username': form.cleaned_data.get('username'), 'mfa': LoginLog.MFA_UNKNOWN, 'reason': LoginLog.REASON_PASSWORD, 'status': False } - self.write_login_log(**kwargs) + self.write_login_log(data) ip = get_login_ip(self.request) cache.set(self.key_prefix.format(ip), 1, 3600) @@ -102,13 +102,13 @@ class UserLoginView(FormView): # 0 & T,F auth_login(self.request, user) # Write login success log - kwargs = { + data = { 'username': self.request.user.username, 'mfa': int(self.request.user.otp_enabled), 'reason': LoginLog.REASON_NOTHING, 'status': True } - self.write_login_log(**kwargs) + self.write_login_log(data) return redirect_user_first_login_or_index(self.request, self.redirect_field_name) def get_context_data(self, **kwargs): @@ -118,16 +118,16 @@ class UserLoginView(FormView): kwargs.update(context) return super().get_context_data(**kwargs) - def write_login_log(self, **kwargs): + def write_login_log(self, data): login_ip = get_login_ip(self.request) user_agent = self.request.META.get('HTTP_USER_AGENT', '') - data = { + tmp_data = { 'ip': login_ip, 'type': 'W', 'user_agent': user_agent } - kwargs.update(data) - write_login_log_async.delay(**kwargs) + data.update(tmp_data) + write_login_log_async.delay(**data) class UserLoginOtpView(FormView): @@ -143,39 +143,39 @@ class UserLoginOtpView(FormView): if check_otp_code(otp_secret_key, otp_code): auth_login(self.request, user) # Write login success log - kwargs = { + data = { 'username': self.request.user.username, 'mfa': int(self.request.user.otp_enabled), 'reason': LoginLog.REASON_NOTHING, 'status': True } - self.write_login_log(**kwargs) + self.write_login_log(data) return redirect(self.get_success_url()) else: # Write login failed log - kwargs = { + data = { 'username': user.username, 'mfa': int(user.otp_enabled), 'reason': LoginLog.REASON_MFA, 'status': False } - self.write_login_log(**kwargs) + 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, **kwargs): + def write_login_log(self, data): login_ip = get_login_ip(self.request) user_agent = self.request.META.get('HTTP_USER_AGENT', '') - data = { + tmp_data = { 'ip': login_ip, 'type': 'W', 'user_agent': user_agent } - kwargs.update(data) - write_login_log_async.delay(**kwargs) + data.update(tmp_data) + write_login_log_async.delay(**data) @method_decorator(never_cache, name='dispatch') From ff9b1a887fe914defd9db127cd1392c090c50e4a Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Wed, 4 Jul 2018 16:55:11 +0800 Subject: [PATCH 3/7] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=99=BB=E5=BD=95=E5=A4=B1=E8=B4=A5=E6=AC=A1=E6=95=B0?= =?UTF-8?q?=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/i18n/zh/LC_MESSAGES/django.mo | Bin 36216 -> 36297 bytes apps/i18n/zh/LC_MESSAGES/django.po | 44 ++++++++++++++------------ apps/users/templates/users/login.html | 6 +++- apps/users/views/login.py | 31 +++++++++++++----- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index 339af4456cb72a4e21841737e7c98bce3a39fc6a..b5474cbe1cdec6a6fb164cf841611e659e75bd94 100644 GIT binary patch delta 11843 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 delta 11771 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 V4I4LTwEf6{bJ6V6y-K diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index 4396c01fb..56fabe11a 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-03 16:48+0800\n" +"POT-Creation-Date: 2018-07-04 16:46+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -156,7 +156,7 @@ msgstr "名称" #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13 #: 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:56 +#: 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 @@ -170,7 +170,7 @@ msgstr "密码或密钥密码" #: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113 #: users/forms.py:15 users/forms.py:33 users/forms.py:45 -#: users/templates/users/login.html:59 +#: users/templates/users/login.html:63 #: users/templates/users/reset_password.html:53 #: users/templates/users/user_create.html:10 #: users/templates/users/user_password_authentication.html:14 @@ -2006,7 +2006,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 "登录" @@ -2046,7 +2046,7 @@ 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:311 users/views/login.py:369 users/views/user.py:65 +#: 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" @@ -2641,7 +2641,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 "忘记密码" @@ -2650,6 +2650,10 @@ 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 "验证码错误" @@ -3045,60 +3049,60 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:63 +#: users/views/login.py:74 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:159 users/views/user.py:500 users/views/user.py:525 +#: 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:188 +#: users/views/login.py:207 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:189 +#: users/views/login.py:208 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:205 +#: users/views/login.py:224 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:218 +#: users/views/login.py:237 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:219 +#: 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:232 +#: users/views/login.py:251 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:233 +#: users/views/login.py:252 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:254 users/views/login.py:267 +#: users/views/login.py:273 users/views/login.py:286 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:263 +#: users/views/login.py:282 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:273 users/views/user.py:118 users/views/user.py:398 +#: 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:311 +#: users/views/login.py:330 msgid "First login" msgstr "首次登陆" -#: users/views/login.py:370 +#: users/views/login.py:389 msgid "Login log list" msgstr "登录日志" diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html index 7dd3f5d0a..7ea824502 100644 --- a/apps/users/templates/users/login.html +++ b/apps/users/templates/users/login.html @@ -45,13 +45,17 @@
{% csrf_token %} - {% if form.errors %} + + {% if login_limit %} +

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

+ {% elif form.errors %} {% if 'captcha' in form.errors %}

{% trans 'Captcha invalid' %}

{% else %}

{{ form.non_field_errors.as_text }}

{% endif %} {% endif %} +
diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 7e853e45f..f58ef7b8f 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -48,7 +48,8 @@ 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_{}_{}" def get(self, request, *args, **kwargs): if request.user.is_staff: @@ -58,6 +59,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') + 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)) + + 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.")) @@ -66,17 +77,24 @@ class UserLoginView(FormView): return redirect(self.get_success_url()) def form_invalid(self, form): - # Write login failed log + # write login failed log + username = form.cleaned_data.get('username') data = { - 'username': form.cleaned_data.get('username'), + 'username': username, 'mfa': LoginLog.MFA_UNKNOWN, 'reason': LoginLog.REASON_PASSWORD, 'status': False } self.write_login_log(data) + # limit user login failed times ip = get_login_ip(self.request) - cache.set(self.key_prefix.format(ip), 1, 3600) + 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) + + 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 @@ -84,7 +102,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 @@ -101,7 +119,6 @@ class UserLoginView(FormView): elif not user.otp_enabled: # 0 & T,F auth_login(self.request, user) - # Write login success log data = { 'username': self.request.user.username, 'mfa': int(self.request.user.otp_enabled), @@ -142,7 +159,6 @@ class UserLoginOtpView(FormView): if check_otp_code(otp_secret_key, otp_code): auth_login(self.request, user) - # Write login success log data = { 'username': self.request.user.username, 'mfa': int(self.request.user.otp_enabled), @@ -152,7 +168,6 @@ class UserLoginOtpView(FormView): self.write_login_log(data) return redirect(self.get_success_url()) else: - # Write login failed log data = { 'username': user.username, 'mfa': int(user.otp_enabled), From 512fc8f8f0fa2ce46f3d03850c2871acf426a15c Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 5 Jul 2018 16:23:33 +0800 Subject: [PATCH 4/7] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E5=A4=B1=E8=B4=A5=E6=AC=A1=E6=95=B0=E9=99=90=E5=88=B6?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=AE=89=E5=85=A8=E8=AE=BE=E7=BD=AE=E9=80=89?= =?UTF-8?q?=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) From bd14266abd0168dc23236b45193d5da469cbf07d Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 5 Jul 2018 16:45:05 +0800 Subject: [PATCH 5/7] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=97=A5=E5=BF=97=E6=A8=A1=E5=9E=8B=E7=B1=BB=E7=9A=84?= =?UTF-8?q?MFA=E5=AD=97=E6=AE=B5=E9=BB=98=E8=AE=A4=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=BA-?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/authentication.py | 2 +- apps/users/utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/users/models/authentication.py b/apps/users/models/authentication.py index 493e4bb59..cb0b7d85f 100644 --- a/apps/users/models/authentication.py +++ b/apps/users/models/authentication.py @@ -72,7 +72,7 @@ class LoginLog(models.Model): ip = models.GenericIPAddressField(verbose_name=_('Login ip')) city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city')) user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent')) - mfa = models.SmallIntegerField(default=MFA_DISABLED, choices=MFA_CHOICE, verbose_name=_('MFA')) + mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA')) reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason')) status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login')) diff --git a/apps/users/utils.py b/apps/users/utils.py index bc03eecbd..937a90867 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -22,7 +22,6 @@ from common.utils import reverse, get_object_or_none from common.models import Setting from common.forms import SecuritySettingForm from .models import User, LoginLog -# from .tasks import write_login_log_async logger = logging.getLogger('jumpserver') From 5d800fa629010a96268b62bf8948b53350b057bd Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 5 Jul 2018 17:07:03 +0800 Subject: [PATCH 6/7] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9coco=E7=AB=AF?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=99=90=E5=88=B6=E6=AC=A1=E6=95=B0=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/users/api.py b/apps/users/api.py index cf13b9aea..c23112384 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -199,17 +199,16 @@ class UserAuthApi(APIView): key_prefix_limit = "_LOGIN_LIMIT_{}_{}" def post(self, request): - user, msg = self.check_user_valid(request) - + # limit login username = request.data.get('username') ip = request.data.get('remote_addr', None) - if not ip: - ip = get_login_ip(request) + ip = ip if ip else 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) + user, msg = self.check_user_valid(request) if not user: data = { 'username': request.data.get('username', ''), From 435acafccd608708bf872168fcac1a8378615ae1 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Fri, 6 Jul 2018 13:22:20 +0800 Subject: [PATCH 7/7] =?UTF-8?q?[Update]=20=E5=9C=A8=E7=BA=BF=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=EF=BC=8C=E5=8E=86=E5=8F=B2=E4=BC=9A=E8=AF=9D=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E7=99=BB=E5=BD=95=E6=9D=A5=E6=BA=90=20Web/SSH=20Termi?= =?UTF-8?q?nal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/i18n/zh/LC_MESSAGES/django.mo | Bin 36708 -> 36764 bytes apps/i18n/zh/LC_MESSAGES/django.po | 76 +++++++++--------- .../templates/terminal/session_list.html | 2 + 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index c9b42d9f4194e85347abe5bcb51aa9c2a1901b5a..78d56177ea093bf518cc027eff07aab406c1a7c5 100644 GIT binary patch delta 12334 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 Dc~FH! delta 12257 zcmY+~2YgT0|HttwA(9Z0gd~I*A&40go7f|Et%|K$ikdB=xHi?IMr*Y-J5V}MEj5cO ztskwu)gHAd|pnw;2V z$El93unbPe8n_vYJC5r-_6kmsijEUNLo?Ju+F({3WcBIh7!0L;GWz3ORR4t-j^AP+ z?!y>7gxTh84Jf_Ydm5mRH2^vozWDs;6c z*c2nNBkKIYRzDimZyIWx1=juXwy8JqyXG{#8&D)x~IRYwi85eXu#QI{U8^CRxS1sE20(au=LssEJOaCc21P z=uK3=Cs-D<@JQOfOF{lN^qZU{OHGT?ehZ>{CZH_F^bvjx_Pt<^eP$!N;ZQ*ot zKI)2=qZYCnHPKeo&i#mbTaKb`*$vcq_pJSy)#s??otGN}_5SClpcCU!E3AsX6{8l= z7z<(>jKw!l{pO%1nvdGK#i()CpeEjdTJU~Mz@wJ?)%Ko|Y?zDroiGX|Fdj8fOVq$! zP&@H5Y9T{WS3DLq@LQ;Uvn-#Fdia*2`h8~YU!um{fCX_E>io;-s^e`6TJb~Ffc|y7 zJlKpx-Lt}|fy$z8K@HT_w?K{471gg7YKLCOe3*{f(f3gkFGlUuiaPASp2E#k=pj3d zo$xB^wW?p&o9GSH0w<#eo{bu49%{lRs4HKCnrI!y;}-P8OXgK9On$@6QIGvEM@3>i zZ^9nfj(iq2#%rh@sZigWs48k94e>i{jhQi|fwzEg)WV}t3v6VzLhW1{>dJeg7CO?U zpaCbL?(r_1#-jK(YJ#s&6Kp^YxC3?GKC3@rUNY~X7V;R?&##f=6v8m{#|p>? zlj~HXkcW!asE6t`3`7@o#ZypQHXq}0HR@S8gj(Pg)C3{iFZIudnz%G-XOmDDPz$qS z1Jn*Q#|XXuT`1@&9gKRvXJ7$bWA4Ww@>{4|@d!2WQ`AlcrF!*|sD3dRh(%EsRL0tC zVK8}p)Ph=KnBM<(6twc*s9&q;sHb)gYQ=Lg7#E=?T#4F&4X7*Mi|ThAHSu}W7GJme zTd4kjp(YM$;_Xld=RZ@;Cx@MXOO0ZAJZh-HF-pxOo}1@IO%l{*8L* z{F{4UID%0Nh(qmINz{c^NA+uuIk1aMK`VX@_0=lf@)=m1d?D(DotTFEFbBrB@JvQ6 zq#kOasaOHKTKjC&d%hG);dhuHFQYEd&D_#^Sb|U!g`lo97iwXJP`9Eq>Pl;wsTe}u z#_}F!KdT>#+VTnJGz=wwA9VqrAoID-dJ5X|?=dItM=jti>fyVIn#gJ8)#pIf$DkHi z7PY`?*4`X-WgSu9{kma2Oh-M0TTl=42@KKue~*IhmA<5FWr1cCYQ^!Wt*vPFO;8I+ zLtSBi9Du`c5MDuz*P*r7-W_#|`k)p(5R2hR4AA?(oPt)m3iVX(R0ICUJc1hVG-`pD zP|wDF)RpFM<4sf;bzVt~!1AbPssZYrcS0>-1cu=xbXAx~AskntZoy{ME!dAb;UreT zD_9a^+Ir7IL$ej?ypE{z`k`)FI_kRsJ{!AqHf74)PgReZrwE;i} NNLVwWWD?b?X|S`nN@W#HYCw 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 J7lwZq`CrhyeTV=6 diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index b1e588650..0cf576ebe 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-05 14:58+0800\n" +"POT-Creation-Date: 2018-07-06 13:11+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -480,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 @@ -685,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 @@ -847,7 +847,7 @@ msgstr "比例" #: 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 @@ -1189,7 +1189,7 @@ msgstr "网域详情" msgid "Domain gateway list" msgstr "域网关列表" -#: assets/views/domain.py:151 +#: assets/views/domain.py:146 msgid "Update gateway" msgstr "创建网关" @@ -1246,7 +1246,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 "开始日期" @@ -1433,60 +1433,60 @@ msgid "" "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:186 +#: common/forms.py:185 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: common/forms.py:193 +#: common/forms.py:190 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: common/forms.py:195 +#: 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:201 +#: common/forms.py:198 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:208 +#: common/forms.py:205 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:210 +#: common/forms.py:207 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:216 +#: common/forms.py:213 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:217 +#: common/forms.py:214 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:223 +#: common/forms.py:220 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:224 +#: common/forms.py:221 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:230 +#: common/forms.py:227 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:231 +#: common/forms.py:228 msgid "" "After opening, the user password changes and resets must contain special " "characters" @@ -2177,14 +2177,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 "命令" @@ -2235,24 +2235,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 "终断任务已发送,请等待" @@ -2322,7 +2326,7 @@ msgid "" "You should use your ssh client tools connect terminal: {}

    {}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api.py:210 users/templates/users/login.html:50 +#: users/api.py:208 users/templates/users/login.html:50 msgid "Log in frequently and try again later" msgstr "登录频繁, 稍后重试" @@ -2697,7 +2701,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:81 +#: users/templates/users/user_detail.html:352 users/utils.py:80 msgid "Reset password" msgstr "重置密码" @@ -2914,11 +2918,11 @@ msgstr "新的公钥已设置成功,请下载对应的私钥" msgid "Update user" msgstr "更新用户" -#: users/utils.py:42 +#: users/utils.py:41 msgid "Create account successfully" msgstr "创建账户成功" -#: users/utils.py:44 +#: users/utils.py:43 #, python-format msgid "" "\n" @@ -2963,7 +2967,7 @@ msgstr "" "
    \n" " " -#: users/utils.py:83 +#: users/utils.py:82 #, python-format msgid "" "\n" @@ -3007,11 +3011,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:114 +#: users/utils.py:113 msgid "SSH Key Reset" msgstr "重置ssh密钥" -#: users/utils.py:116 +#: users/utils.py:115 #, python-format msgid "" "\n" @@ -3036,19 +3040,19 @@ msgstr "" "
    \n" " " -#: users/utils.py:149 +#: users/utils.py:148 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:151 +#: users/utils.py:150 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:164 +#: users/utils.py:163 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 " 位" diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html index 8cea8e217..33ae09877 100644 --- a/apps/terminal/templates/terminal/session_list.html +++ b/apps/terminal/templates/terminal/session_list.html @@ -73,6 +73,7 @@ {% trans 'System user' %} {% trans 'Remote addr' %} {% trans 'Protocol' %} + {% trans 'Login from' %} {% trans 'Command' %} {% trans 'Date start' %} {# {% trans 'Date last active' %}#} @@ -92,6 +93,7 @@ {{ session.system_user }} {{ session.remote_addr|default:"" }} {{ session.protocol }} + {{ session.get_login_from_display }} {{ session.id | get_session_command_amount }} {{ session.date_start }}