|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
#
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
|
|
|
|
from users.utils import LoginBlockUtil, MFABlockUtils, LoginIpBlockUtil
|
|
|
|
|
from . import const
|
|
|
|
|
from ..signals import post_auth_failed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthFailedNeedLogMixin:
|
|
|
|
|
username = ''
|
|
|
|
|
request = None
|
|
|
|
|
error = ''
|
|
|
|
|
msg = ''
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
post_auth_failed.send(
|
|
|
|
|
sender=self.__class__, username=self.username,
|
|
|
|
|
request=self.request, reason=self.msg
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthFailedNeedBlockMixin:
|
|
|
|
|
username = ''
|
|
|
|
|
ip = ''
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
LoginBlockUtil(self.username, self.ip).incr_failed_count()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthFailedError(Exception):
|
|
|
|
|
username = ''
|
|
|
|
|
msg = ''
|
|
|
|
|
error = ''
|
|
|
|
|
request = None
|
|
|
|
|
ip = ''
|
|
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
for k, v in kwargs.items():
|
|
|
|
|
setattr(self, k, v)
|
|
|
|
|
|
|
|
|
|
def as_data(self):
|
|
|
|
|
return {
|
|
|
|
|
'error': self.error,
|
|
|
|
|
'msg': self.msg,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return str(self.msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BlockGlobalIpLoginError(AuthFailedError):
|
|
|
|
|
error = 'block_global_ip_login'
|
|
|
|
|
|
|
|
|
|
def __init__(self, username, ip, **kwargs):
|
|
|
|
|
if not self.msg:
|
|
|
|
|
self.msg = const.block_ip_login_msg.format(settings.SECURITY_LOGIN_IP_LIMIT_TIME)
|
|
|
|
|
LoginIpBlockUtil(ip).set_block_if_need()
|
|
|
|
|
super().__init__(username=username, ip=ip, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CredentialError(
|
|
|
|
|
AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin,
|
|
|
|
|
BlockGlobalIpLoginError, AuthFailedError
|
|
|
|
|
):
|
|
|
|
|
def __init__(self, error, username, ip, request):
|
|
|
|
|
util = LoginBlockUtil(username, ip)
|
|
|
|
|
times_remainder = util.get_remainder_times()
|
|
|
|
|
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
|
|
|
|
|
if times_remainder < 1:
|
|
|
|
|
self.msg = const.block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
|
|
|
|
|
else:
|
|
|
|
|
default_msg = const.invalid_login_msg.format(
|
|
|
|
|
times_try=times_remainder, block_time=block_time
|
|
|
|
|
)
|
|
|
|
|
if error == const.reason_password_failed:
|
|
|
|
|
self.msg = default_msg
|
|
|
|
|
else:
|
|
|
|
|
self.msg = const.reason_choices.get(error, default_msg)
|
|
|
|
|
# 先处理 msg 在 super,记录日志时原因才准确
|
|
|
|
|
super().__init__(error=error, username=username, ip=ip, request=request)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
|
|
|
|
|
error = const.reason_mfa_failed
|
|
|
|
|
msg: str
|
|
|
|
|
|
|
|
|
|
def __init__(self, username, request, ip, mfa_type, error):
|
|
|
|
|
util = MFABlockUtils(username, ip)
|
|
|
|
|
times_remainder = util.incr_failed_count()
|
|
|
|
|
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
|
|
|
|
|
|
|
|
|
|
if times_remainder:
|
|
|
|
|
self.msg = const.mfa_error_msg.format(
|
|
|
|
|
error=error, times_try=times_remainder, block_time=block_time
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self.msg = const.block_mfa_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
|
|
|
|
|
super().__init__(username=username, request=request)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BlockMFAError(AuthFailedNeedLogMixin, AuthFailedError):
|
|
|
|
|
error = 'block_mfa'
|
|
|
|
|
|
|
|
|
|
def __init__(self, username, request, ip):
|
|
|
|
|
self.msg = const.block_mfa_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
|
|
|
|
|
super().__init__(username=username, request=request, ip=ip)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
|
|
|
|
|
error = 'block_login'
|
|
|
|
|
|
|
|
|
|
def __init__(self, username, ip):
|
|
|
|
|
self.msg = const.block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
|
|
|
|
|
super().__init__(username=username, ip=ip)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SessionEmptyError(AuthFailedError):
|
|
|
|
|
msg = const.session_empty_msg
|
|
|
|
|
error = 'session_empty'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ACLError(AuthFailedNeedLogMixin, AuthFailedError):
|
|
|
|
|
msg = const.reason_acl_not_allow
|
|
|
|
|
error = 'acl_error'
|
|
|
|
|
|
|
|
|
|
def __init__(self, msg, **kwargs):
|
|
|
|
|
self.msg = msg
|
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
def as_data(self):
|
|
|
|
|
return {
|
|
|
|
|
"error": const.reason_acl_not_allow,
|
|
|
|
|
"msg": self.msg
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginACLIPAndTimePeriodNotAllowed(ACLError):
|
|
|
|
|
def __init__(self, username, request, **kwargs):
|
|
|
|
|
self.username = username
|
|
|
|
|
self.request = request
|
|
|
|
|
super().__init__(_("Current IP and Time period is not allowed"), **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MFACodeRequiredError(AuthFailedError):
|
|
|
|
|
error = 'mfa_code_required'
|
|
|
|
|
msg = _("Please enter MFA code")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SMSCodeRequiredError(AuthFailedError):
|
|
|
|
|
error = 'sms_code_required'
|
|
|
|
|
msg = _("Please enter SMS code")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserPhoneNotSet(AuthFailedError):
|
|
|
|
|
error = 'phone_not_set'
|
|
|
|
|
msg = _('Phone not set')
|