Commit e1be8679 authored by BaiJiangJie's avatar BaiJiangJie

[Update] 更新用户登录日志,记录登录失败日志并添加MFA启用状态信息

parent 442d4e72
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-25 12:19+0800\n" "POT-Creation-Date: 2018-07-03 16:48+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -118,7 +118,7 @@ msgstr "端口" ...@@ -118,7 +118,7 @@ msgstr "端口"
msgid "Asset" msgid "Asset"
msgstr "资产" 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/base.py:21 assets/models/cluster.py:18
#: assets/models/domain.py:17 assets/models/group.py:20 #: assets/models/domain.py:17 assets/models/group.py:20
#: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56 #: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56
...@@ -147,14 +147,14 @@ msgstr "资产" ...@@ -147,14 +147,14 @@ msgstr "资产"
msgid "Name" msgid "Name"
msgstr "名称" 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/models/base.py:22 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:24 #: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/domain_gateway_list.html:60 #: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:27 #: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
#: users/forms.py: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/_select_user_modal.html:14
#: users/templates/users/login.html:56 #: users/templates/users/login.html:56
#: users/templates/users/login_log_list.html:49 #: users/templates/users/login_log_list.html:49
...@@ -192,21 +192,21 @@ msgstr "ssh密钥不合法" ...@@ -192,21 +192,21 @@ msgstr "ssh密钥不合法"
msgid "Password and private key file must be input one" msgid "Password and private key file must be input one"
msgstr "密码和私钥, 必须输入一个" msgstr "密码和私钥, 必须输入一个"
#: assets/forms/user.py:124 #: assets/forms/user.py:125
msgid "* Automatic login mode, must fill in the username." msgid "* Automatic login mode, must fill in the username."
msgstr "自动登录模式,必须填写用户名" msgstr "自动登录模式,必须填写用户名"
#: assets/forms/user.py:144 #: assets/forms/user.py:145
msgid "Auto push system user to asset" msgid "Auto push system user to asset"
msgstr "自动推送系统用户到资产" msgstr "自动推送系统用户到资产"
#: assets/forms/user.py:145 #: assets/forms/user.py:146
msgid "" msgid ""
"High level will be using login asset as default, if user was granted more " "High level will be using login asset as default, if user was granted more "
"than 2 system user" "than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户" msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/forms/user.py:147 #: assets/forms/user.py:148
msgid "" msgid ""
"If you choose manual login mode, you do not need to fill in the username and " "If you choose manual login mode, you do not need to fill in the username and "
"password." "password."
...@@ -1237,7 +1237,7 @@ msgid "Filename" ...@@ -1237,7 +1237,7 @@ msgid "Filename"
msgstr "文件名" msgstr "文件名"
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77 #: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/task_list.html:39 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
msgid "Success" msgid "Success"
msgstr "成功" msgstr "成功"
...@@ -1485,7 +1485,8 @@ msgstr "" ...@@ -1485,7 +1485,8 @@ msgstr ""
msgid "discard time" msgid "discard time"
msgstr "" msgstr ""
#: common/models.py:29 users/templates/users/user_detail.html:96 #: common/models.py:29 users/models/authentication.py:51
#: users/templates/users/user_detail.html:96
msgid "Enabled" msgid "Enabled"
msgstr "启用" msgstr "启用"
...@@ -1803,7 +1804,7 @@ msgid "Versions" ...@@ -1803,7 +1804,7 @@ msgid "Versions"
msgstr "版本" msgstr "版本"
#: ops/templates/ops/task_list.html:40 #: ops/templates/ops/task_list.html:40
#: users/templates/users/login_log_list.html:54 #: users/templates/users/login_log_list.html:57
msgid "Date" msgid "Date"
msgstr "日期" msgstr "日期"
...@@ -2045,7 +2046,7 @@ msgstr "关闭" ...@@ -2045,7 +2046,7 @@ msgstr "关闭"
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95 #: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
#: users/views/login.py:277 users/views/login.py:335 users/views/user.py: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: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 #: users/views/user.py:330 users/views/user.py:380 users/views/user.py:415
msgid "Users" msgid "Users"
...@@ -2406,8 +2407,9 @@ msgstr "" ...@@ -2406,8 +2407,9 @@ msgstr ""
msgid "* Enable MFA authentication to make the account more secure." msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全." msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py: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/first_login.html:45
#: users/templates/users/login_log_list.html:54
msgid "MFA" msgid "MFA"
msgstr "MFA" msgstr "MFA"
...@@ -2467,23 +2469,53 @@ msgstr "ssh公钥" ...@@ -2467,23 +2469,53 @@ msgstr "ssh公钥"
msgid "Private Token" msgid "Private Token"
msgstr "ssh密钥" msgstr "ssh密钥"
#: users/models/authentication.py:46 #: users/models/authentication.py:50 users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: users/models/authentication.py:52 users/models/authentication.py:60
msgid "-"
msgstr ""
#: users/models/authentication.py:61
msgid "Username/password check failed"
msgstr "用户名/密码 校验失败"
#: users/models/authentication.py:62
msgid "MFA authentication failed"
msgstr "MFA 认证失败"
#: users/models/authentication.py:67
msgid "Failed"
msgstr "失败"
#: users/models/authentication.py:71
msgid "Login type" msgid "Login type"
msgstr "登录方式" msgstr "登录方式"
#: users/models/authentication.py:47 #: users/models/authentication.py:72
msgid "Login ip" msgid "Login ip"
msgstr "登录IP" msgstr "登录IP"
#: users/models/authentication.py:48 #: users/models/authentication.py:73
msgid "Login city" msgid "Login city"
msgstr "登录城市" msgstr "登录城市"
#: users/models/authentication.py:49 #: users/models/authentication.py:74
msgid "User agent" msgid "User agent"
msgstr "Agent" msgstr "Agent"
#: users/models/authentication.py:50 #: users/models/authentication.py:76
#: users/templates/users/login_log_list.html:55
msgid "Reason"
msgstr "原因"
#: users/models/authentication.py:77
#: users/templates/users/login_log_list.html:56
msgid "Status"
msgstr "状态"
#: users/models/authentication.py:78
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
...@@ -2646,7 +2678,7 @@ msgid "Can't provide security? Please contact the administrator!" ...@@ -2646,7 +2678,7 @@ msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!" msgstr "如果不能提供MFA验证码,请联系管理员!"
#: users/templates/users/reset_password.html:46 #: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:352 users/utils.py:80 #: users/templates/users/user_detail.html:352 users/utils.py:81
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
...@@ -2696,10 +2728,6 @@ msgstr "授权的资产" ...@@ -2696,10 +2728,6 @@ msgstr "授权的资产"
msgid "Force enabled" msgid "Force enabled"
msgstr "强制启用" msgstr "强制启用"
#: users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: users/templates/users/user_detail.html:119 #: users/templates/users/user_detail.html:119
#: users/templates/users/user_profile.html:108 #: users/templates/users/user_profile.html:108
msgid "Last login" msgid "Last login"
...@@ -2867,11 +2895,11 @@ msgstr "新的公钥已设置成功,请下载对应的私钥" ...@@ -2867,11 +2895,11 @@ msgstr "新的公钥已设置成功,请下载对应的私钥"
msgid "Update user" msgid "Update user"
msgstr "更新用户" msgstr "更新用户"
#: users/utils.py:41 #: users/utils.py:42
msgid "Create account successfully" msgid "Create account successfully"
msgstr "创建账户成功" msgstr "创建账户成功"
#: users/utils.py:43 #: users/utils.py:44
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2916,7 +2944,7 @@ msgstr "" ...@@ -2916,7 +2944,7 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:82 #: users/utils.py:83
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2960,11 +2988,11 @@ msgstr "" ...@@ -2960,11 +2988,11 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:113 #: users/utils.py:114
msgid "SSH Key Reset" msgid "SSH Key Reset"
msgstr "重置ssh密钥" msgstr "重置ssh密钥"
#: users/utils.py:115 #: users/utils.py:116
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2989,15 +3017,15 @@ msgstr "" ...@@ -2989,15 +3017,15 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:148 #: users/utils.py:149
msgid "User not exist" msgid "User not exist"
msgstr "用户不存在" msgstr "用户不存在"
#: users/utils.py:150 #: users/utils.py:151
msgid "Disabled or expired" msgid "Disabled or expired"
msgstr "禁用或失效" msgstr "禁用或失效"
#: users/utils.py:163 #: users/utils.py:164
msgid "Password or SSH public key invalid" msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法" msgstr "密码或密钥不合法"
...@@ -3017,60 +3045,60 @@ msgstr "更新用户组" ...@@ -3017,60 +3045,60 @@ msgstr "更新用户组"
msgid "User group granted asset" msgid "User group granted asset"
msgstr "用户组授权资产" msgstr "用户组授权资产"
#: users/views/login.py:62 #: users/views/login.py:63
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:128 users/views/user.py: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" msgid "MFA code invalid"
msgstr "MFA码认证失败" msgstr "MFA码认证失败"
#: users/views/login.py:154 #: users/views/login.py:188
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: users/views/login.py:155 #: users/views/login.py:189
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:171 #: users/views/login.py:205
msgid "Email address invalid, please input again" msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入" msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:184 #: users/views/login.py:218
msgid "Send reset password message" msgid "Send reset password message"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/views/login.py:185 #: users/views/login.py:219
msgid "Send reset password mail success, login your mail box and follow it " msgid "Send reset password mail success, login your mail box and follow it "
msgstr "" msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:198 #: users/views/login.py:232
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/views/login.py:199 #: users/views/login.py:233
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:220 users/views/login.py:233 #: users/views/login.py:254 users/views/login.py:267
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/login.py:229 #: users/views/login.py:263
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" 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" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/views/login.py:277 #: users/views/login.py:311
msgid "First login" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/login.py:336 #: users/views/login.py:370
msgid "Login log list" msgid "Login log list"
msgstr "登录日志" msgstr "登录日志"
......
...@@ -14,7 +14,7 @@ from .serializers import UserSerializer, UserGroupSerializer, \ ...@@ -14,7 +14,7 @@ from .serializers import UserSerializer, UserGroupSerializer, \
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \ UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async from .tasks import write_login_log_async
from .models import User, UserGroup from .models import User, UserGroup, LoginLog
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \ from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
IsSuperUserOrAppUser IsSuperUserOrAppUser
from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code
...@@ -153,10 +153,25 @@ class UserOtpAuthApi(APIView): ...@@ -153,10 +153,25 @@ class UserOtpAuthApi(APIView):
return Response({'msg': '请先进行用户名和密码验证'}, status=401) return Response({'msg': '请先进行用户名和密码验证'}, status=401)
if not check_otp_code(user.otp_secret_key, otp_code): if not check_otp_code(user.otp_secret_key, otp_code):
# 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) 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) token = generate_token(request, user)
self.write_login_log(request, user)
return Response( return Response(
{ {
'token': token, 'token': token,
...@@ -165,7 +180,7 @@ class UserOtpAuthApi(APIView): ...@@ -165,7 +180,7 @@ class UserOtpAuthApi(APIView):
) )
@staticmethod @staticmethod
def write_login_log(request, user): def write_login_log(request, **kwargs):
login_ip = request.data.get('remote_addr', None) login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '') login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '') user_agent = request.data.get('HTTP_USER_AGENT', '')
...@@ -173,10 +188,13 @@ class UserOtpAuthApi(APIView): ...@@ -173,10 +188,13 @@ class UserOtpAuthApi(APIView):
if not login_ip: if not login_ip:
login_ip = get_login_ip(request) login_ip = get_login_ip(request)
write_login_log_async.delay( data = {
user.username, ip=login_ip, 'ip': login_ip,
type=login_type, user_agent=user_agent, 'type': login_type,
) 'user_agent': user_agent
}
kwargs.update(data)
write_login_log_async.delay(**kwargs)
class UserAuthApi(APIView): class UserAuthApi(APIView):
...@@ -187,11 +205,26 @@ class UserAuthApi(APIView): ...@@ -187,11 +205,26 @@ class UserAuthApi(APIView):
user, msg = self.check_user_valid(request) user, msg = self.check_user_valid(request)
if not user: 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) return Response({'msg': msg}, status=401)
if not user.otp_enabled: 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) token = generate_token(request, user)
self.write_login_log(request, user)
return Response( return Response(
{ {
'token': token, 'token': token,
...@@ -208,7 +241,8 @@ class UserAuthApi(APIView): ...@@ -208,7 +241,8 @@ class UserAuthApi(APIView):
'otp_url': reverse('api-users:user-otp-auth'), 'otp_url': reverse('api-users:user-otp-auth'),
'seed': seed, 'seed': seed,
'user': self.serializer_class(user).data 'user': self.serializer_class(user).data
}, status=300) }, status=300
)
@staticmethod @staticmethod
def check_user_valid(request): def check_user_valid(request):
...@@ -222,7 +256,7 @@ class UserAuthApi(APIView): ...@@ -222,7 +256,7 @@ class UserAuthApi(APIView):
return user, msg return user, msg
@staticmethod @staticmethod
def write_login_log(request, user): def write_login_log(request, **kwargs):
login_ip = request.data.get('remote_addr', None) login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '') login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '') user_agent = request.data.get('HTTP_USER_AGENT', '')
...@@ -230,10 +264,14 @@ class UserAuthApi(APIView): ...@@ -230,10 +264,14 @@ class UserAuthApi(APIView):
if not login_ip: if not login_ip:
login_ip = get_login_ip(request) login_ip = get_login_ip(request)
write_login_log_async.delay( data = {
user.username, ip=login_ip, 'ip': login_ip,
type=login_type, user_agent=user_agent, 'type': login_type,
) 'user_agent': user_agent,
}
kwargs.update(data)
write_login_log_async.delay(**kwargs)
class UserConnectionTokenApi(APIView): class UserConnectionTokenApi(APIView):
......
...@@ -41,12 +41,40 @@ class LoginLog(models.Model): ...@@ -41,12 +41,40 @@ class LoginLog(models.Model):
('W', 'Web'), ('W', 'Web'),
('T', 'Terminal'), ('T', 'Terminal'),
) )
MFA_DISABLED = 0
MFA_ENABLED = 1
MFA_UNKNOWN = 2
MFA_CHOICE = (
(MFA_DISABLED, _('Disabled')),
(MFA_ENABLED, _('Enabled')),
(MFA_UNKNOWN, _('-')),
)
REASON_NOTHING = 0
REASON_PASSWORD = 1
REASON_MFA = 2
REASON_CHOICE = (
(REASON_NOTHING, _('-')),
(REASON_PASSWORD, _('Username/password check failed')),
(REASON_MFA, _('MFA authentication failed')),
)
STATUS_CHOICE = (
(True, _('Success')),
(False, _('Failed'))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(max_length=20, verbose_name=_('Username')) username = models.CharField(max_length=20, verbose_name=_('Username'))
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
ip = models.GenericIPAddressField(verbose_name=_('Login ip')) ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city')) city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent')) user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
mfa = models.SmallIntegerField(default=MFA_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')) datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
class Meta: class Meta:
......
...@@ -51,6 +51,9 @@ ...@@ -51,6 +51,9 @@
<th class="text-center">{% trans 'UA' %}</th> <th class="text-center">{% trans 'UA' %}</th>
<th class="text-center">{% trans 'IP' %}</th> <th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'City' %}</th> <th class="text-center">{% trans 'City' %}</th>
<th class="text-center">{% trans 'MFA' %}</th>
<th class="text-center">{% trans 'Reason' %}</th>
<th class="text-center">{% trans 'Status' %}</th>
<th class="text-center">{% trans 'Date' %}</th> <th class="text-center">{% trans 'Date' %}</th>
{% endblock %} {% endblock %}
...@@ -65,6 +68,9 @@ ...@@ -65,6 +68,9 @@
</td> </td>
<td class="text-center">{{ login_log.ip }}</td> <td class="text-center">{{ login_log.ip }}</td>
<td class="text-center">{{ login_log.city }}</td> <td class="text-center">{{ login_log.city }}</td>
<td class="text-center">{{ login_log.get_mfa_display }}</td>
<td class="text-center">{{ login_log.get_reason_display }}</td>
<td class="text-center">{{ login_log.get_status_display }}</td>
<td class="text-center">{{ login_log.datetime }}</td> <td class="text-center">{{ login_log.datetime }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
......
...@@ -13,7 +13,7 @@ import ipaddress ...@@ -13,7 +13,7 @@ import ipaddress
from django.http import Http404 from django.http import Http404
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth import authenticate, login as auth_login from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.cache import cache from django.core.cache import cache
...@@ -22,6 +22,7 @@ from common.utils import reverse, get_object_or_none ...@@ -22,6 +22,7 @@ from common.utils import reverse, get_object_or_none
from common.models import Setting from common.models import Setting
from common.forms import SecuritySettingForm from common.forms import SecuritySettingForm
from .models import User, LoginLog from .models import User, LoginLog
# from .tasks import write_login_log_async
logger = logging.getLogger('jumpserver') logger = logging.getLogger('jumpserver')
...@@ -200,16 +201,15 @@ def get_login_ip(request): ...@@ -200,16 +201,15 @@ def get_login_ip(request):
return login_ip return login_ip
def write_login_log(username, type='', ip='', user_agent=''): def write_login_log(*args, **kwargs):
ip = kwargs.get('ip', '')
if not (ip and validate_ip(ip)): if not (ip and validate_ip(ip)):
ip = ip[:15] ip = ip[:15]
city = "Unknown" city = "Unknown"
else: else:
city = get_ip_city(ip) city = get_ip_city(ip)
LoginLog.objects.create( kwargs.update({'ip': ip, 'city': city})
username=username, type=type, LoginLog.objects.create(**kwargs)
ip=ip, city=city, user_agent=user_agent
)
def get_ip_city(ip, timeout=10): def get_ip_city(ip, timeout=10):
......
...@@ -25,8 +25,9 @@ from common.utils import get_object_or_none ...@@ -25,8 +25,9 @@ from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from common.models import Setting from common.models import Setting
from ..models import User, LoginLog from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \ from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \
get_user_or_tmp_user, set_tmp_user_to_cache, get_password_check_rules, check_password_rules redirect_user_first_login_or_index, get_user_or_tmp_user, \
set_tmp_user_to_cache, get_password_check_rules, check_password_rules
from ..tasks import write_login_log_async from ..tasks import write_login_log_async
from .. import forms from .. import forms
...@@ -65,6 +66,15 @@ class UserLoginView(FormView): ...@@ -65,6 +66,15 @@ class UserLoginView(FormView):
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def form_invalid(self, form): def form_invalid(self, form):
# Write login failed log
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) ip = get_login_ip(self.request)
cache.set(self.key_prefix.format(ip), 1, 3600) cache.set(self.key_prefix.format(ip), 1, 3600)
old_form = form old_form = form
...@@ -91,7 +101,14 @@ class UserLoginView(FormView): ...@@ -91,7 +101,14 @@ class UserLoginView(FormView):
elif not user.otp_enabled: elif not user.otp_enabled:
# 0 & T,F # 0 & T,F
auth_login(self.request, user) auth_login(self.request, user)
self.write_login_log() # 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) return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
...@@ -101,13 +118,16 @@ class UserLoginView(FormView): ...@@ -101,13 +118,16 @@ class UserLoginView(FormView):
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def write_login_log(self): def write_login_log(self, **kwargs):
login_ip = get_login_ip(self.request) login_ip = get_login_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay( data = {
self.request.user.username, type='W', 'ip': login_ip,
ip=login_ip, user_agent=user_agent 'type': 'W',
) 'user_agent': user_agent
}
kwargs.update(data)
write_login_log_async.delay(**kwargs)
class UserLoginOtpView(FormView): class UserLoginOtpView(FormView):
...@@ -122,22 +142,40 @@ class UserLoginOtpView(FormView): ...@@ -122,22 +142,40 @@ class UserLoginOtpView(FormView):
if check_otp_code(otp_secret_key, otp_code): if check_otp_code(otp_secret_key, otp_code):
auth_login(self.request, user) auth_login(self.request, user)
self.write_login_log() # 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()) return redirect(self.get_success_url())
else: 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')) form.add_error('otp_code', _('MFA code invalid'))
return super().form_invalid(form) return super().form_invalid(form)
def get_success_url(self): def get_success_url(self):
return redirect_user_first_login_or_index(self.request, self.redirect_field_name) return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def write_login_log(self): def write_login_log(self, **kwargs):
login_ip = get_login_ip(self.request) login_ip = get_login_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay( data = {
self.request.user.username, type='W', 'ip': login_ip,
ip=login_ip, user_agent=user_agent 'type': 'W',
) 'user_agent': user_agent
}
kwargs.update(data)
write_login_log_async.delay(**kwargs)
@method_decorator(never_cache, name='dispatch') @method_decorator(never_cache, name='dispatch')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment