Unverified Commit df95c93b authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #1503 from jumpserver/update_loginlog

[Update] 记录用户登录失败日志,限制用户登录失败次数
parents 75a9deeb 435acafc
...@@ -170,7 +170,7 @@ class TerminalSettingForm(BaseForm): ...@@ -170,7 +170,7 @@ class TerminalSettingForm(BaseForm):
class SecuritySettingForm(BaseForm): class SecuritySettingForm(BaseForm):
# MFA全局设置 # MFA global setting
SECURITY_MFA_AUTH = forms.BooleanField( SECURITY_MFA_AUTH = forms.BooleanField(
initial=False, required=False, initial=False, required=False,
label=_("MFA Secondary certification"), label=_("MFA Secondary certification"),
...@@ -179,12 +179,26 @@ class SecuritySettingForm(BaseForm): ...@@ -179,12 +179,26 @@ class SecuritySettingForm(BaseForm):
'authentication (valid for all users, including administrators)' '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( SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
initial=6, label=_("Password minimum length"), initial=6, label=_("Password minimum length"),
min_value=6 min_value=6
) )
# 大写字母 # upper case
SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField( SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField(
initial=False, required=False, initial=False, required=False,
...@@ -193,21 +207,21 @@ class SecuritySettingForm(BaseForm): ...@@ -193,21 +207,21 @@ class SecuritySettingForm(BaseForm):
'After opening, the user password changes ' 'After opening, the user password changes '
'and resets must contain uppercase letters') 'and resets must contain uppercase letters')
) )
# 小写字母 # lower case
SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField( SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField(
initial=False, required=False, initial=False, required=False,
label=_("Must contain lowercase letters"), label=_("Must contain lowercase letters"),
help_text=_('After opening, the user password changes ' help_text=_('After opening, the user password changes '
'and resets must contain lowercase letters') 'and resets must contain lowercase letters')
) )
# 数字 # number
SECURITY_PASSWORD_NUMBER = forms.BooleanField( SECURITY_PASSWORD_NUMBER = forms.BooleanField(
initial=False, required=False, initial=False, required=False,
label=_("Must contain numeric characters"), label=_("Must contain numeric characters"),
help_text=_('After opening, the user password changes ' help_text=_('After opening, the user password changes '
'and resets must contain numeric characters') 'and resets must contain numeric characters')
) )
# 特殊字符 # special char
SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField( SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField(
initial=False, required=False, initial=False, required=False,
label=_("Must contain special characters"), label=_("Must contain special characters"),
......
...@@ -39,9 +39,9 @@ ...@@ -39,9 +39,9 @@
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
<h3>{% trans "MFA setting" %}</h3> <h3>{% trans "User login settings" %}</h3>
{% for field in form %} {% for field in form %}
{% if forloop.counter == 2 %} {% if forloop.counter == 4 %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans "Password check rule" %}</h3> <h3>{% trans "Password check rule" %}</h3>
{% endif %} {% endif %}
......
This diff is collapsed.
...@@ -405,6 +405,8 @@ TERMINAL_REPLAY_STORAGE = { ...@@ -405,6 +405,8 @@ TERMINAL_REPLAY_STORAGE = {
DEFAULT_PASSWORD_MIN_LENGTH = 6 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 # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = { BOOTSTRAP3 = {
......
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
<th class="text-center">{% trans 'System user' %}</th> <th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Remote addr' %}</th> <th class="text-center">{% trans 'Remote addr' %}</th>
<th class="text-center">{% trans 'Protocol' %}</th> <th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Login from' %}</th>
<th class="text-center">{% trans 'Command' %}</th> <th class="text-center">{% trans 'Command' %}</th>
<th class="text-center">{% trans 'Date start' %}</th> <th class="text-center">{% trans 'Date start' %}</th>
{# <th class="text-center">{% trans 'Date last active' %}</th>#} {# <th class="text-center">{% trans 'Date last active' %}</th>#}
...@@ -92,6 +93,7 @@ ...@@ -92,6 +93,7 @@
<td class="text-center">{{ session.system_user }}</td> <td class="text-center">{{ session.system_user }}</td>
<td class="text-center">{{ session.remote_addr|default:"" }}</td> <td class="text-center">{{ session.remote_addr|default:"" }}</td>
<td class="text-center">{{ session.protocol }}</td> <td class="text-center">{{ session.protocol }}</td>
<td class="text-center">{{ session.get_login_from_display }}</td>
<td class="text-center">{{ session.id | get_session_command_amount }}</td> <td class="text-center">{{ session.id | get_session_command_amount }}</td>
<td class="text-center">{{ session.date_start }}</td> <td class="text-center">{{ session.date_start }}</td>
......
...@@ -3,6 +3,7 @@ import uuid ...@@ -3,6 +3,7 @@ import uuid
from django.core.cache import cache from django.core.cache import cache
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext as _
from rest_framework import generics from rest_framework import generics
from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.permissions import AllowAny, IsAuthenticated
...@@ -14,10 +15,11 @@ from .serializers import UserSerializer, UserGroupSerializer, \ ...@@ -14,10 +15,11 @@ 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, set_user_login_failed_count_to_cache, is_block_login
from common.mixins import IDInFilterMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger from common.utils import get_logger
...@@ -149,10 +151,23 @@ class UserOtpAuthApi(APIView): ...@@ -149,10 +151,23 @@ 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):
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(request, data)
return Response({'msg': 'MFA认证失败'}, status=401) return Response({'msg': 'MFA认证失败'}, status=401)
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
token = generate_token(request, user) token = generate_token(request, user)
self.write_login_log(request, user)
return Response( return Response(
{ {
'token': token, 'token': token,
...@@ -161,7 +176,7 @@ class UserOtpAuthApi(APIView): ...@@ -161,7 +176,7 @@ class UserOtpAuthApi(APIView):
) )
@staticmethod @staticmethod
def write_login_log(request, user): def write_login_log(request, data):
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', '')
...@@ -169,25 +184,52 @@ class UserOtpAuthApi(APIView): ...@@ -169,25 +184,52 @@ 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( tmp_data = {
user.username, ip=login_ip, 'ip': login_ip,
type=login_type, user_agent=user_agent, 'type': login_type,
) 'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserAuthApi(APIView): class UserAuthApi(APIView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
serializer_class = UserSerializer serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
def post(self, request): 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)
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: if not user:
data = {
'username': request.data.get('username', ''),
'mfa': LoginLog.MFA_UNKNOWN,
'reason': LoginLog.REASON_PASSWORD,
'status': False
}
self.write_login_log(request, data)
set_user_login_failed_count_to_cache(key_limit)
return Response({'msg': msg}, status=401) return Response({'msg': msg}, status=401)
if not user.otp_enabled: if not user.otp_enabled:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
token = generate_token(request, user) token = generate_token(request, user)
self.write_login_log(request, user)
return Response( return Response(
{ {
'token': token, 'token': token,
...@@ -204,7 +246,8 @@ class UserAuthApi(APIView): ...@@ -204,7 +246,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):
...@@ -218,7 +261,7 @@ class UserAuthApi(APIView): ...@@ -218,7 +261,7 @@ class UserAuthApi(APIView):
return user, msg return user, msg
@staticmethod @staticmethod
def write_login_log(request, user): def write_login_log(request, data):
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', '')
...@@ -226,10 +269,14 @@ class UserAuthApi(APIView): ...@@ -226,10 +269,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( tmp_data = {
user.username, ip=login_ip, 'ip': login_ip,
type=login_type, user_agent=user_agent, 'type': login_type,
) 'user_agent': user_agent,
}
data.update(tmp_data)
write_login_log_async.delay(**data)
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_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')) datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
class Meta: class Meta:
......
...@@ -45,13 +45,17 @@ ...@@ -45,13 +45,17 @@
</div> </div>
<form class="m-t" role="form" method="post" action=""> <form class="m-t" role="form" method="post" action="">
{% csrf_token %} {% csrf_token %}
{% if form.errors %}
{% if block_login %}
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
{% elif form.errors %}
{% if 'captcha' in form.errors %} {% if 'captcha' in form.errors %}
<p class="red-fonts">{% trans 'Captcha invalid' %}</p> <p class="red-fonts">{% trans 'Captcha invalid' %}</p>
{% else %} {% else %}
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p> <p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
{% endif %} {% endif %}
{% endif %} {% endif %}
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}"> <input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
</div> </div>
......
...@@ -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
...@@ -200,16 +200,15 @@ def get_login_ip(request): ...@@ -200,16 +200,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):
...@@ -332,3 +331,29 @@ def check_password_rules(password): ...@@ -332,3 +331,29 @@ def check_password_rules(password):
match_obj = re.match(pattern, password) match_obj = re.match(pattern, password)
return bool(match_obj) 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
...@@ -25,8 +25,10 @@ from common.utils import get_object_or_none ...@@ -25,8 +25,10 @@ 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, \
is_block_login, set_user_login_failed_count_to_cache
from ..tasks import write_login_log_async from ..tasks import write_login_log_async
from .. import forms from .. import forms
...@@ -47,7 +49,8 @@ class UserLoginView(FormView): ...@@ -47,7 +49,8 @@ class UserLoginView(FormView):
form_class = forms.UserLoginForm form_class = forms.UserLoginForm
form_class_captcha = forms.UserLoginCaptchaForm form_class_captcha = forms.UserLoginCaptchaForm
redirect_field_name = 'next' redirect_field_name = 'next'
key_prefix = "_LOGIN_INVALID_{}" key_prefix_captcha = "_LOGIN_INVALID_{}"
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if request.user.is_staff: if request.user.is_staff:
...@@ -57,6 +60,16 @@ class UserLoginView(FormView): ...@@ -57,6 +60,16 @@ class UserLoginView(FormView):
request.session.set_test_cookie() request.session.set_test_cookie()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# limit login authentication
ip = get_login_ip(request)
username = self.request.POST.get('username')
key_limit = self.key_prefix_limit.format(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)
def form_valid(self, form): def form_valid(self, form):
if not self.request.session.test_cookie_worked(): if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again.")) return HttpResponse(_("Please enable cookies and try again."))
...@@ -65,8 +78,23 @@ class UserLoginView(FormView): ...@@ -65,8 +78,23 @@ 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
username = form.cleaned_data.get('username')
data = {
'username': username,
'mfa': LoginLog.MFA_UNKNOWN,
'reason': LoginLog.REASON_PASSWORD,
'status': False
}
self.write_login_log(data)
# limit user login failed count
ip = get_login_ip(self.request) ip = get_login_ip(self.request)
cache.set(self.key_prefix.format(ip), 1, 3600) key_limit = self.key_prefix_limit.format(ip, username)
set_user_login_failed_count_to_cache(key_limit)
# show captcha
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
old_form = form old_form = form
form = self.form_class_captcha(data=form.data) form = self.form_class_captcha(data=form.data)
form._errors = old_form.errors form._errors = old_form.errors
...@@ -74,7 +102,7 @@ class UserLoginView(FormView): ...@@ -74,7 +102,7 @@ class UserLoginView(FormView):
def get_form_class(self): def get_form_class(self):
ip = get_login_ip(self.request) 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 return self.form_class_captcha
else: else:
return self.form_class return self.form_class
...@@ -91,7 +119,13 @@ class UserLoginView(FormView): ...@@ -91,7 +119,13 @@ 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() data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect_user_first_login_or_index(self.request, self.redirect_field_name) 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 +135,16 @@ class UserLoginView(FormView): ...@@ -101,13 +135,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, data):
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( tmp_data = {
self.request.user.username, type='W', 'ip': login_ip,
ip=login_ip, user_agent=user_agent 'type': 'W',
) 'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserLoginOtpView(FormView): class UserLoginOtpView(FormView):
...@@ -122,22 +159,38 @@ class UserLoginOtpView(FormView): ...@@ -122,22 +159,38 @@ 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() data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect(self.get_success_url()) return redirect(self.get_success_url())
else: else:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(data)
form.add_error('otp_code', _('MFA code invalid')) 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, data):
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( tmp_data = {
self.request.user.username, type='W', 'ip': login_ip,
ip=login_ip, user_agent=user_agent 'type': 'W',
) 'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
@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