Compare commits

...

14 Commits

Author SHA1 Message Date
ibuler
4def7bc5ec fix: 修复 close connection 的问题 2021-11-25 18:29:39 +08:00
ibuler
d401a44317 fix: 修复重置 mfa 的提示问题 2021-11-25 17:50:17 +08:00
xinwen
7e793a6e0a fix: 按资产ip搜索数据不全 2021-11-25 17:29:28 +08:00
ibuler
b61559d078 perf: 去掉登录页面更好 2021-11-25 16:04:25 +08:00
ibuler
0e8260a37c fix: 修复 oidc cas 登录时跳转问题
perf: 优化一波,容易debug

perf: 还原回来的世界
2021-11-25 15:00:38 +08:00
ibuler
b8af86b163 fix: 修复 cas/oidc 登录 MFA 产生的bug
perf: 优化更严谨
2021-11-24 18:37:42 +08:00
Michael Bai
5000a1f586 fix: 修复根据节点/资产查询授权时报错的问题 2021-11-24 18:25:38 +08:00
ibuler
a72bb192c3 fix: 修复 mfa radius 登录的bug 2021-11-24 12:50:55 +08:00
feng626
225b315b7a fix: 修复重置mfa 500 bug 2021-11-23 16:12:12 +08:00
ibuler
084050a4cc perf: 系统用户密码不再trim空格 2021-11-23 10:41:26 +08:00
feng626
2bdaf73d49 翻译mo 2021-11-19 16:04:09 +08:00
feng626
3052c4cc19 perf: 优化ip黑白名单 2021-11-19 16:04:09 +08:00
xinwen
bd92045b50 fix: 验证码逻辑错误 2021-11-19 10:47:40 +08:00
Michael Bai
23d867dce6 fix(i18n): 修改翻译文件 2021-11-18 19:06:54 +08:00
25 changed files with 292 additions and 441 deletions

View File

@@ -8,7 +8,7 @@ from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['RuleSerializer'] __all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text']
def ip_group_child_validator(ip_group_child): def ip_group_child_validator(ip_group_child):
@@ -21,13 +21,14 @@ def ip_group_child_validator(ip_group_child):
raise serializers.ValidationError(error) raise serializers.ValidationError(error)
class RuleSerializer(serializers.Serializer): ip_group_help_text = _(
ip_group_help_text = _( 'Format for comma-delimited string, with * indicating a match all. '
'Format for comma-delimited string, with * indicating a match all. ' 'Such as: '
'Such as: ' '192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 '
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 ' )
)
class RuleSerializer(serializers.Serializer):
ip_group = serializers.ListField( ip_group = serializers.ListField(
default=['*'], label=_('IP'), help_text=ip_group_help_text, default=['*'], label=_('IP'), help_text=ip_group_help_text,
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator])) child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]))

View File

@@ -48,6 +48,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
extra_kwargs = { extra_kwargs = {
'password': { 'password': {
"write_only": True, "write_only": True,
'trim_whitespace': False,
"validators": [validate_password_contains_left_double_curly_bracket] "validators": [validate_password_contains_left_double_curly_bracket]
}, },
'public_key': {"write_only": True}, 'public_key': {"write_only": True},

View File

@@ -84,10 +84,10 @@ def subscribe_node_assets_mapping_expire(sender, **kwargs):
subscribe = node_assets_mapping_for_memory_pub_sub.subscribe() subscribe = node_assets_mapping_for_memory_pub_sub.subscribe()
msgs = subscribe.listen() msgs = subscribe.listen()
# 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中 # 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中
close_old_connections()
for message in msgs: for message in msgs:
if message["type"] != "message": if message["type"] != "message":
continue continue
close_old_connections()
org_id = message['data'].decode() org_id = message['data'].decode()
root_org_id = Organization.ROOT_ID root_org_id = Organization.ROOT_ID
Node.expire_node_all_asset_ids_mapping_from_memory(org_id) Node.expire_node_all_asset_ids_mapping_from_memory(org_id)
@@ -96,6 +96,7 @@ def subscribe_node_assets_mapping_expire(sender, **kwargs):
"Expire node assets id mapping from memory of org={}, pid={}" "Expire node assets id mapping from memory of org={}, pid={}"
"".format(str(org_id), os.getpid()) "".format(str(org_id), os.getpid())
) )
close_old_connections()
except Exception as e: except Exception as e:
logger.exception(f'subscribe_node_assets_mapping_expire: {e}') logger.exception(f'subscribe_node_assets_mapping_expire: {e}')
Node.expire_all_orgs_node_all_asset_ids_mapping_from_memory() Node.expire_all_orgs_node_all_asset_ids_mapping_from_memory()

View File

@@ -51,10 +51,14 @@ invalid_login_msg = _(
"You can also try {times_try} times " "You can also try {times_try} times "
"(The account will be temporarily locked for {block_time} minutes)" "(The account will be temporarily locked for {block_time} minutes)"
) )
block_login_msg = _( block_user_login_msg = _(
"The account has been locked " "The account has been locked "
"(please contact admin to unlock it or try again after {} minutes)" "(please contact admin to unlock it or try again after {} minutes)"
) )
block_ip_login_msg = _(
"The ip has been locked "
"(please contact admin to unlock it or try again after {} minutes)"
)
block_mfa_msg = _( block_mfa_msg = _(
"The account has been locked " "The account has been locked "
"(please contact admin to unlock it or try again after {} minutes)" "(please contact admin to unlock it or try again after {} minutes)"
@@ -118,7 +122,7 @@ class BlockGlobalIpLoginError(AuthFailedError):
error = 'block_global_ip_login' error = 'block_global_ip_login'
def __init__(self, username, ip, **kwargs): def __init__(self, username, ip, **kwargs):
self.msg = _("IP is not allowed") self.msg = block_ip_login_msg.format(settings.SECURITY_LOGIN_IP_LIMIT_TIME)
LoginIpBlockUtil(ip).set_block_if_need() LoginIpBlockUtil(ip).set_block_if_need()
super().__init__(username=username, ip=ip, **kwargs) super().__init__(username=username, ip=ip, **kwargs)
@@ -133,7 +137,7 @@ class CredentialError(
block_time = settings.SECURITY_LOGIN_LIMIT_TIME block_time = settings.SECURITY_LOGIN_LIMIT_TIME
if times_remainder < 1: if times_remainder < 1:
self.msg = block_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME) self.msg = block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
return return
default_msg = invalid_login_msg.format( default_msg = invalid_login_msg.format(
@@ -184,7 +188,7 @@ class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
error = 'block_login' error = 'block_login'
def __init__(self, username, ip): def __init__(self, username, ip):
self.msg = block_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME) self.msg = block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
super().__init__(username=username, ip=ip) super().__init__(username=username, ip=ip)

View File

@@ -43,8 +43,8 @@ class UserLoginForm(forms.Form):
class UserCheckOtpCodeForm(forms.Form): class UserCheckOtpCodeForm(forms.Form):
code = forms.CharField(label=_('MFA Code'), max_length=6, required=False) code = forms.CharField(label=_('MFA Code'), max_length=128, required=False)
mfa_type = forms.CharField(label=_('MFA type'), max_length=6) mfa_type = forms.CharField(label=_('MFA type'), max_length=128)
class CustomCaptchaTextInput(CaptchaTextInput): class CustomCaptchaTextInput(CaptchaTextInput):
@@ -57,7 +57,7 @@ class CaptchaMixin(forms.Form):
class ChallengeMixin(forms.Form): class ChallengeMixin(forms.Form):
challenge = forms.CharField( challenge = forms.CharField(
label=_('MFA code'), max_length=6, required=False, label=_('MFA code'), max_length=128, required=False,
widget=forms.TextInput(attrs={ widget=forms.TextInput(attrs={
'placeholder': _("Dynamic code"), 'placeholder': _("Dynamic code"),
'style': 'width: 50%' 'style': 'width: 50%'

View File

@@ -1,14 +1,36 @@
from django.shortcuts import redirect from django.shortcuts import redirect, reverse
from django.http import HttpResponse
class MFAMiddleware: class MFAMiddleware:
"""
这个 中间件 是用来全局拦截开启了 MFA 却没有认证的,如 OIDC, CAS使用第三方库做的登录直接 login 了,
所以只能在 Middleware 中控制
"""
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
def __call__(self, request): def __call__(self, request):
response = self.get_response(request) response = self.get_response(request)
if request.path.find('/auth/login/otp/') > -1: # 没有校验
if not request.session.get('auth_mfa_required'):
return response return response
if request.session.get('auth_mfa_required'): # 没有认证过,证明不是从 第三方 来的
return redirect('authentication:login-mfa') if request.user.is_anonymous:
return response return response
# 这个是 mfa 登录页需要的请求, 也得放出来, 用户其实已经在 CAS/OIDC 中完成登录了
white_urls = [
'login/mfa', 'mfa/select', 'jsi18n/', '/static/',
'/profile/otp', '/logout/',
]
for url in white_urls:
if request.path.find(url) > -1:
return response
# 因为使用 CAS/OIDC 登录的,不小心去了别的页面就回不来了
if request.path.find('users/profile') > -1:
return HttpResponse('', status=401)
url = reverse('authentication:login-mfa') + '?_=middleware'
return redirect(url)

View File

@@ -257,7 +257,8 @@ class MFAMixin:
def _check_login_page_mfa_if_need(self, user): def _check_login_page_mfa_if_need(self, user):
if not settings.SECURITY_MFA_IN_LOGIN_PAGE: if not settings.SECURITY_MFA_IN_LOGIN_PAGE:
return return
self._check_if_no_active_mfa(user) if not user.active_mfa_backends:
return
request = self.request request = self.request
data = request.data if hasattr(request, 'data') else request.POST data = request.data if hasattr(request, 'data') else request.POST
@@ -274,10 +275,8 @@ class MFAMixin:
if not user.mfa_enabled: if not user.mfa_enabled:
return return
self._check_if_no_active_mfa(user) active_mfa_names = user.active_mfa_backends_mapper.keys()
raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names))
active_mfa_mapper = user.active_mfa_backends_mapper
raise errors.MFARequiredError(mfa_types=tuple(active_mfa_mapper.keys()))
def mark_mfa_ok(self, mfa_type): def mark_mfa_ok(self, mfa_type):
self.request.session['auth_mfa'] = 1 self.request.session['auth_mfa'] = 1
@@ -417,12 +416,10 @@ class AuthACLMixin:
self.request.session["auth_confirm"] = "1" self.request.session["auth_confirm"] = "1"
return return
elif ticket.state_reject: elif ticket.state_reject:
self.clean_mfa_mark()
raise errors.LoginConfirmOtherError( raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_state_display() ticket.id, ticket.get_state_display()
) )
elif ticket.state_close: elif ticket.state_close:
self.clean_mfa_mark()
raise errors.LoginConfirmOtherError( raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_state_display() ticket.id, ticket.get_state_display()
) )

View File

@@ -7,7 +7,6 @@ from django.dispatch import receiver
from django_cas_ng.signals import cas_user_authenticated from django_cas_ng.signals import cas_user_authenticated
from jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success from jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success
from .signals import post_auth_success, post_auth_failed from .signals import post_auth_success, post_auth_failed

View File

@@ -3,6 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.shortcuts import redirect
from common.utils import get_logger from common.utils import get_logger
from .. import forms, errors, mixins from .. import forms, errors, mixins
@@ -19,9 +20,15 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
try: try:
self.get_user_from_session() user = self.get_user_from_session()
except errors.SessionEmptyError: except errors.SessionEmptyError:
return redirect_to_guard_view() return redirect_to_guard_view('session_empty')
try:
self._check_if_no_active_mfa(user)
except errors.MFAUnsetError as e:
return redirect(e.url + '?_=login_mfa')
return super().get(*args, **kwargs) return super().get(*args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
@@ -30,17 +37,17 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
try: try:
self._do_check_user_mfa(code, mfa_type) self._do_check_user_mfa(code, mfa_type)
return redirect_to_guard_view() return redirect_to_guard_view('mfa_ok')
except (errors.MFAFailedError, errors.BlockMFAError) as e: except (errors.MFAFailedError, errors.BlockMFAError) as e:
form.add_error('code', e.msg) form.add_error('code', e.msg)
return super().form_invalid(form) return super().form_invalid(form)
except errors.SessionEmptyError: except errors.SessionEmptyError:
return redirect_to_guard_view() return redirect_to_guard_view('session_empty')
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return redirect_to_guard_view() return redirect_to_guard_view('unexpect')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
user = self.get_user_from_session() user = self.get_user_from_session()

View File

@@ -3,6 +3,6 @@
from django.shortcuts import reverse, redirect from django.shortcuts import reverse, redirect
def redirect_to_guard_view(): def redirect_to_guard_view(comment=''):
continue_url = reverse('authentication:login-guard') continue_url = reverse('authentication:login-guard') + '?_=' + comment
return redirect(continue_url) return redirect(continue_url)

View File

@@ -37,12 +37,17 @@ class SendAndVerifySMSUtil:
self.code = '' self.code = ''
self.timeout = timeout or self.TIMEOUT self.timeout = timeout or self.TIMEOUT
self.key_suffix = key_suffix or str(phone) self.key_suffix = key_suffix or str(phone)
self.key = self.KEY_TMPL.format(key_suffix) self.key = self.KEY_TMPL.format(self.key_suffix)
def gen_and_send(self): def gen_and_send(self):
""" """
生成,保存,发送 生成,保存,发送
""" """
ttl = self.ttl()
if ttl > 0:
logger.error('Send sms too frequently, delay {}'.format(ttl))
raise CodeSendTooFrequently(ttl)
try: try:
code = self.generate() code = self.generate()
self.send(code) self.send(code)
@@ -62,10 +67,6 @@ class SendAndVerifySMSUtil:
""" """
发送信息的方法,如果有错误直接抛出 api 异常 发送信息的方法,如果有错误直接抛出 api 异常
""" """
ttl = self.ttl()
if ttl > 0:
logger.error('Send sms too frequently, delay {}'.format(ttl))
raise CodeSendTooFrequently(ttl)
sms = SMS() sms = SMS()
sms.send_verify_code(self.phone, code) sms.send_verify_code(self.phone, code)
cache.set(self.key, self.code, self.timeout) cache.set(self.key, self.code, self.timeout)

View File

@@ -291,9 +291,6 @@ class Config(dict):
'SECURITY_COMMAND_EXECUTION': True, 'SECURITY_COMMAND_EXECUTION': True,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True, 'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_VIEW_AUTH_NEED_MFA': True, 'SECURITY_VIEW_AUTH_NEED_MFA': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_IP_BLACK_LIST': [],
'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30, 'SECURITY_MAX_IDLE_TIME': 30,
'SECURITY_PASSWORD_EXPIRATION_TIME': 9999, 'SECURITY_PASSWORD_EXPIRATION_TIME': 9999,
'SECURITY_PASSWORD_MIN_LENGTH': 6, 'SECURITY_PASSWORD_MIN_LENGTH': 6,
@@ -318,6 +315,14 @@ class Config(dict):
'USER_LOGIN_SINGLE_MACHINE_ENABLED': False, 'USER_LOGIN_SINGLE_MACHINE_ENABLED': False,
'ONLY_ALLOW_EXIST_USER_AUTH': False, 'ONLY_ALLOW_EXIST_USER_AUTH': False,
'ONLY_ALLOW_AUTH_FROM_SOURCE': False, 'ONLY_ALLOW_AUTH_FROM_SOURCE': False,
# 用户登录限制的规则
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_LIMIT_TIME': 30,
# 登录IP限制的规则
'SECURITY_LOGIN_IP_BLACK_LIST': [],
'SECURITY_LOGIN_IP_WHITE_LIST': [],
'SECURITY_LOGIN_IP_LIMIT_COUNT': 99999,
'SECURITY_LOGIN_IP_LIMIT_TIME': 30,
# 启动前 # 启动前
'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_BIND_HOST': '0.0.0.0',

View File

@@ -32,11 +32,8 @@ TERMINAL_REPLAY_STORAGE = CONFIG.TERMINAL_REPLAY_STORAGE
# Security settings # Security settings
SECURITY_MFA_AUTH = CONFIG.SECURITY_MFA_AUTH SECURITY_MFA_AUTH = CONFIG.SECURITY_MFA_AUTH
SECURITY_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION
SECURITY_LOGIN_LIMIT_COUNT = CONFIG.SECURITY_LOGIN_LIMIT_COUNT
SECURITY_LOGIN_IP_BLACK_LIST = CONFIG.SECURITY_LOGIN_IP_BLACK_LIST
SECURITY_LOGIN_LIMIT_TIME = CONFIG.SECURITY_LOGIN_LIMIT_TIME # Unit: minute
SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute
SECURITY_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION
SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day
SECURITY_PASSWORD_MIN_LENGTH = CONFIG.SECURITY_PASSWORD_MIN_LENGTH # Unit: bit SECURITY_PASSWORD_MIN_LENGTH = CONFIG.SECURITY_PASSWORD_MIN_LENGTH # Unit: bit
SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH = CONFIG.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH # Unit: bit SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH = CONFIG.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH # Unit: bit
@@ -63,6 +60,14 @@ SECURITY_INSECURE_COMMAND = CONFIG.SECURITY_INSECURE_COMMAND
SECURITY_INSECURE_COMMAND_LEVEL = CONFIG.SECURITY_INSECURE_COMMAND_LEVEL SECURITY_INSECURE_COMMAND_LEVEL = CONFIG.SECURITY_INSECURE_COMMAND_LEVEL
SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = CONFIG.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = CONFIG.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER
SECURITY_CHECK_DIFFERENT_CITY_LOGIN = CONFIG.SECURITY_CHECK_DIFFERENT_CITY_LOGIN SECURITY_CHECK_DIFFERENT_CITY_LOGIN = CONFIG.SECURITY_CHECK_DIFFERENT_CITY_LOGIN
# 用户登录限制的规则
SECURITY_LOGIN_LIMIT_COUNT = CONFIG.SECURITY_LOGIN_LIMIT_COUNT
SECURITY_LOGIN_LIMIT_TIME = CONFIG.SECURITY_LOGIN_LIMIT_TIME # Unit: minute
# 登录IP限制的规则
SECURITY_LOGIN_IP_BLACK_LIST = CONFIG.SECURITY_LOGIN_IP_BLACK_LIST
SECURITY_LOGIN_IP_WHITE_LIST = CONFIG.SECURITY_LOGIN_IP_WHITE_LIST
SECURITY_LOGIN_IP_LIMIT_COUNT = CONFIG.SECURITY_LOGIN_IP_LIMIT_COUNT
SECURITY_LOGIN_IP_LIMIT_TIME = CONFIG.SECURITY_LOGIN_IP_LIMIT_TIME # Unit: minute
# Terminal other setting # Terminal other setting
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4fea2cdf5a5477757cb95ff36016ed754fd65f839c12adbac9247ebdcca138ef oid sha256:c9823f96465943a304034daf10ad6a98f2e02d8fe80a145f6e0196693a933387
size 93440 size 93547

View File

@@ -7,7 +7,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: 2021-11-17 16:39+0800\n" "POT-Creation-Date: 2021-11-19 15:28+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\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"
@@ -180,7 +180,7 @@ msgstr ""
"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
#: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:32 #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33
#: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: applications/serializers/attrs/application_type/mysql_workbench.py:18
#: assets/models/asset.py:211 assets/models/domain.py:61 #: assets/models/asset.py:211 assets/models/domain.py:61
#: assets/serializers/account.py:12 #: assets/serializers/account.py:12
@@ -225,12 +225,12 @@ msgstr "组织 `{}` 不存在"
msgid "None of the reviewers belong to Organization `{}`" msgid "None of the reviewers belong to Organization `{}`"
msgstr "所有复核人都不属于组织 `{}`" msgstr "所有复核人都不属于组织 `{}`"
#: acls/serializers/rules/rules.py:20 settings/serializers/security.py:35 #: acls/serializers/rules/rules.py:20
#: xpack/plugins/cloud/serializers/task.py:23 #: xpack/plugins/cloud/serializers/task.py:23
msgid "IP address invalid: `{}`" msgid "IP address invalid: `{}`"
msgstr "IP 地址无效: `{}`" msgstr "IP 地址无效: `{}`"
#: acls/serializers/rules/rules.py:26 #: acls/serializers/rules/rules.py:25
msgid "" msgid ""
"Format for comma-delimited string, with * indicating a match all. Such as: " "Format for comma-delimited string, with * indicating a match all. Such as: "
"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:"
@@ -239,7 +239,7 @@ msgstr ""
"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
#: acls/serializers/rules/rules.py:34 #: acls/serializers/rules/rules.py:35
msgid "Time Period" msgid "Time Period"
msgstr "时段" msgstr "时段"
@@ -1619,13 +1619,19 @@ msgstr ""
"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将" "您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将"
"被临时 锁定 {block_time} 分钟)" "被临时 锁定 {block_time} 分钟)"
#: authentication/errors.py:55 authentication/errors.py:59 #: authentication/errors.py:55 authentication/errors.py:63
msgid "" msgid ""
"The account has been locked (please contact admin to unlock it or try again " "The account has been locked (please contact admin to unlock it or try again "
"after {} minutes)" "after {} minutes)"
msgstr "账号已被锁定(请联系管理员解锁{}分钟后重试)" msgstr "账号已被锁定(请联系管理员解锁{}分钟后重试)"
#: authentication/errors.py:63 #: authentication/errors.py:59
msgid ""
"The ip has been locked (please contact admin to unlock it or try again after "
"{} minutes)"
msgstr "IP 已被锁定(请联系管理员解锁或{}分钟后重试)"
#: authentication/errors.py:67
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{error}, You can also try {times_try} times (The account will be temporarily " "{error}, You can also try {times_try} times (The account will be temporarily "
@@ -1633,63 +1639,63 @@ msgid ""
msgstr "" msgstr ""
"{error},您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)" "{error},您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)"
#: authentication/errors.py:67 #: authentication/errors.py:71
msgid "MFA required" msgid "MFA required"
msgstr "需要 MFA 认证" msgstr "需要 MFA 认证"
#: authentication/errors.py:68 #: authentication/errors.py:72
msgid "MFA not set, please set it first" msgid "MFA not set, please set it first"
msgstr "MFA 没有设置,请先完成设置" msgstr "MFA 没有设置,请先完成设置"
#: authentication/errors.py:69 #: authentication/errors.py:73
msgid "Login confirm required" msgid "Login confirm required"
msgstr "需要登录复核" msgstr "需要登录复核"
#: authentication/errors.py:70 #: authentication/errors.py:74
msgid "Wait login confirm ticket for accept" msgid "Wait login confirm ticket for accept"
msgstr "等待登录复核处理" msgstr "等待登录复核处理"
#: authentication/errors.py:71 #: authentication/errors.py:75
msgid "Login confirm ticket was {}" msgid "Login confirm ticket was {}"
msgstr "登录复核 {}" msgstr "登录复核 {}"
#: authentication/errors.py:184 authentication/errors.py:248 #: authentication/errors.py:255
msgid "IP is not allowed" msgid "IP is not allowed"
msgstr "来源 IP 不被允许登录" msgstr "来源 IP 不被允许登录"
#: authentication/errors.py:255 #: authentication/errors.py:262
msgid "Time Period is not allowed" msgid "Time Period is not allowed"
msgstr "该 时间段 不被允许登录" msgstr "该 时间段 不被允许登录"
#: authentication/errors.py:288 #: authentication/errors.py:295
msgid "SSO auth closed" msgid "SSO auth closed"
msgstr "SSO 认证关闭了" msgstr "SSO 认证关闭了"
#: authentication/errors.py:293 authentication/mixins.py:360 #: authentication/errors.py:300 authentication/mixins.py:360
msgid "Your password is too simple, please change it for security" msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改" msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors.py:302 authentication/mixins.py:367 #: authentication/errors.py:309 authentication/mixins.py:367
msgid "You should to change your password before login" msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码" msgstr "登录完成前,请先修改密码"
#: authentication/errors.py:311 authentication/mixins.py:374 #: authentication/errors.py:318 authentication/mixins.py:374
msgid "Your password has expired, please reset before logging in" msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录" msgstr "您的密码已过期,先修改再登录"
#: authentication/errors.py:345 #: authentication/errors.py:352
msgid "Your password is invalid" msgid "Your password is invalid"
msgstr "您的密码无效" msgstr "您的密码无效"
#: authentication/errors.py:350 #: authentication/errors.py:357
msgid "Please enter MFA code" msgid "Please enter MFA code"
msgstr "请输入 MFA 验证码" msgstr "请输入 MFA 验证码"
#: authentication/errors.py:355 #: authentication/errors.py:362
msgid "Please enter SMS code" msgid "Please enter SMS code"
msgstr "请输入短信验证码" msgstr "请输入短信验证码"
#: authentication/errors.py:360 users/exceptions.py:15 #: authentication/errors.py:367 users/exceptions.py:15
msgid "Phone not set" msgid "Phone not set"
msgstr "手机号没有设置" msgstr "手机号没有设置"
@@ -1818,7 +1824,7 @@ msgid "Show"
msgstr "显示" msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66 #: authentication/templates/authentication/_access_key_modal.html:66
#: settings/serializers/security.py:42 users/models/user.py:458 #: settings/serializers/security.py:39 users/models/user.py:458
#: users/serializers/profile.py:99 users/templates/users/mfa_setting.html:60 #: users/serializers/profile.py:99 users/templates/users/mfa_setting.html:60
#: users/templates/users/user_verify_mfa.html:36 #: users/templates/users/user_verify_mfa.html:36
msgid "Disable" msgid "Disable"
@@ -3326,49 +3332,53 @@ msgstr "必须包含数字"
msgid "Must contain special" msgid "Must contain special"
msgstr "必须包含特殊字符" msgstr "必须包含特殊字符"
#: settings/serializers/security.py:43 #: settings/serializers/security.py:31
msgid "All users"
msgstr "所有用户"
#: settings/serializers/security.py:44
msgid "Only admin users"
msgstr "仅管理员"
#: settings/serializers/security.py:46
msgid "Global MFA auth"
msgstr "全局启用 MFA 认证"
#: settings/serializers/security.py:50
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
#: settings/serializers/security.py:54
msgid "Block logon interval"
msgstr "禁止登录时间间隔"
#: settings/serializers/security.py:56
msgid "" msgid ""
"Unit: minute, If the user has failed to log in for a limited number of " "Unit: minute, If the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval." "times, no login is allowed during this time interval."
msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
#: settings/serializers/security.py:61 #: settings/serializers/security.py:40
msgid "Login IP Black List" msgid "All users"
msgstr "登录 IP 黑名单" msgstr "所有用户"
#: settings/serializers/security.py:41
msgid "Only admin users"
msgstr "仅管理员"
#: settings/serializers/security.py:43
msgid "Global MFA auth"
msgstr "全局启用 MFA 认证"
#: settings/serializers/security.py:47
msgid "Limit the number of user login failures"
msgstr "限制用户登录失败次数"
#: settings/serializers/security.py:51
msgid "Block user login interval"
msgstr "禁止用户登录时间间隔"
#: settings/serializers/security.py:56
msgid "Limit the number of IP login failures"
msgstr "限制 IP 登录失败次数"
#: settings/serializers/security.py:60
msgid "Block IP login interval"
msgstr "禁止 IP 登录时间间隔"
#: settings/serializers/security.py:64 #: settings/serializers/security.py:64
msgid "" msgid "Login IP White List"
"Format for comma-delimited string. Such as: 192.168.10.1, 192.168.1.0/24, " msgstr "IP 登录白名单"
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
msgstr ""
"格式为逗号分隔的字符串。例如: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
#: settings/serializers/security.py:70 #: settings/serializers/security.py:69
msgid "Login IP Black List"
msgstr "IP 登录黑名单"
#: settings/serializers/security.py:75
msgid "User password expiration" msgid "User password expiration"
msgstr "用户密码过期时间" msgstr "用户密码过期时间"
#: settings/serializers/security.py:72 #: settings/serializers/security.py:77
msgid "" msgid ""
"Unit: day, If the user does not update the password during the time, the " "Unit: day, If the user does not update the password during the time, the "
"user password will expire failure;The password expiration reminder mail will " "user password will expire failure;The password expiration reminder mail will "
@@ -3378,55 +3388,55 @@ msgstr ""
"单位:天, 如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件" "单位:天, 如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件"
"将在密码过期前5天内由系统每天自动发送给用户" "将在密码过期前5天内由系统每天自动发送给用户"
#: settings/serializers/security.py:79 #: settings/serializers/security.py:84
msgid "Number of repeated historical passwords" msgid "Number of repeated historical passwords"
msgstr "不能设置近几次密码" msgstr "不能设置近几次密码"
#: settings/serializers/security.py:81 #: settings/serializers/security.py:86
msgid "" msgid ""
"Tip: When the user resets the password, it cannot be the previous n " "Tip: When the user resets the password, it cannot be the previous n "
"historical passwords of the user" "historical passwords of the user"
msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码" msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
#: settings/serializers/security.py:86 #: settings/serializers/security.py:91
msgid "Only single device login" msgid "Only single device login"
msgstr "仅一台设备登录" msgstr "仅一台设备登录"
#: settings/serializers/security.py:87 #: settings/serializers/security.py:92
msgid "Next device login, pre login will be logout" msgid "Next device login, pre login will be logout"
msgstr "下个设备登录,上次登录会被顶掉" msgstr "下个设备登录,上次登录会被顶掉"
#: settings/serializers/security.py:90 #: settings/serializers/security.py:95
msgid "Only exist user login" msgid "Only exist user login"
msgstr "仅已存在用户登录" msgstr "仅已存在用户登录"
#: settings/serializers/security.py:91 #: settings/serializers/security.py:96
msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet"
msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会失败" msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:94 #: settings/serializers/security.py:99
msgid "Only from source login" msgid "Only from source login"
msgstr "仅从用户来源登录" msgstr "仅从用户来源登录"
#: settings/serializers/security.py:95 #: settings/serializers/security.py:100
msgid "Only log in from the user source property" msgid "Only log in from the user source property"
msgstr "开启后如果用户来源为本地CAS、OIDC 登录将会失败" msgstr "开启后如果用户来源为本地CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:99 #: settings/serializers/security.py:104
msgid "MFA verify TTL" msgid "MFA verify TTL"
msgstr "MFA 校验有效期" msgstr "MFA 校验有效期"
#: settings/serializers/security.py:101 #: settings/serializers/security.py:106
msgid "" msgid ""
"Unit: second, The verification MFA takes effect only when you view the " "Unit: second, The verification MFA takes effect only when you view the "
"account password" "account password"
msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效" msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效"
#: settings/serializers/security.py:106 #: settings/serializers/security.py:111
msgid "Enable Login dynamic code" msgid "Enable Login dynamic code"
msgstr "启用登录附加码" msgstr "启用登录附加码"
#: settings/serializers/security.py:107 #: settings/serializers/security.py:112
msgid "" msgid ""
"The password and additional code are sent to a third party authentication " "The password and additional code are sent to a third party authentication "
"system for verification" "system for verification"
@@ -3434,96 +3444,96 @@ msgstr ""
"密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密" "密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密"
"码+6位数字 完成认证" "码+6位数字 完成认证"
#: settings/serializers/security.py:112 #: settings/serializers/security.py:117
msgid "MFA in login page" msgid "MFA in login page"
msgstr "MFA 在登录页面输入" msgstr "MFA 在登录页面输入"
#: settings/serializers/security.py:113 #: settings/serializers/security.py:118
msgid "Eu security regulations(GDPR) require MFA to be on the login page" msgid "Eu security regulations(GDPR) require MFA to be on the login page"
msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全" msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全"
#: settings/serializers/security.py:116 #: settings/serializers/security.py:121
msgid "Enable Login captcha" msgid "Enable Login captcha"
msgstr "启用登录验证码" msgstr "启用登录验证码"
#: settings/serializers/security.py:117 #: settings/serializers/security.py:122
msgid "Enable captcha to prevent robot authentication" msgid "Enable captcha to prevent robot authentication"
msgstr "开启验证码,防止机器人登录" msgstr "开启验证码,防止机器人登录"
#: settings/serializers/security.py:137 #: settings/serializers/security.py:142
msgid "Enable terminal register" msgid "Enable terminal register"
msgstr "终端注册" msgstr "终端注册"
#: settings/serializers/security.py:139 #: settings/serializers/security.py:144
msgid "" msgid ""
"Allow terminal register, after all terminal setup, you should disable this " "Allow terminal register, after all terminal setup, you should disable this "
"for security" "for security"
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
#: settings/serializers/security.py:143 #: settings/serializers/security.py:148
msgid "Enable watermark" msgid "Enable watermark"
msgstr "开启水印" msgstr "开启水印"
#: settings/serializers/security.py:144 #: settings/serializers/security.py:149
msgid "Enabled, the web session and replay contains watermark information" msgid "Enabled, the web session and replay contains watermark information"
msgstr "启用后Web 会话和录像将包含水印信息" msgstr "启用后Web 会话和录像将包含水印信息"
#: settings/serializers/security.py:148 #: settings/serializers/security.py:153
msgid "Connection max idle time" msgid "Connection max idle time"
msgstr "连接最大空闲时间" msgstr "连接最大空闲时间"
#: settings/serializers/security.py:149 #: settings/serializers/security.py:154
msgid "If idle time more than it, disconnect connection Unit: minute" msgid "If idle time more than it, disconnect connection Unit: minute"
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
#: settings/serializers/security.py:152 #: settings/serializers/security.py:157
msgid "Remember manual auth" msgid "Remember manual auth"
msgstr "保存手动输入密码" msgstr "保存手动输入密码"
#: settings/serializers/security.py:155 #: settings/serializers/security.py:160
msgid "Enable change auth secure mode" msgid "Enable change auth secure mode"
msgstr "启用改密安全模式" msgstr "启用改密安全模式"
#: settings/serializers/security.py:158 #: settings/serializers/security.py:163
msgid "Insecure command alert" msgid "Insecure command alert"
msgstr "危险命令告警" msgstr "危险命令告警"
#: settings/serializers/security.py:161 #: settings/serializers/security.py:166
msgid "Email recipient" msgid "Email recipient"
msgstr "邮件收件人" msgstr "邮件收件人"
#: settings/serializers/security.py:162 #: settings/serializers/security.py:167
msgid "Multiple user using , split" msgid "Multiple user using , split"
msgstr "多个用户,使用 , 分割" msgstr "多个用户,使用 , 分割"
#: settings/serializers/security.py:165 #: settings/serializers/security.py:170
msgid "Batch command execution" msgid "Batch command execution"
msgstr "批量命令执行" msgstr "批量命令执行"
#: settings/serializers/security.py:166 #: settings/serializers/security.py:171
msgid "Allow user run batch command or not using ansible" msgid "Allow user run batch command or not using ansible"
msgstr "是否允许用户使用 ansible 执行批量命令" msgstr "是否允许用户使用 ansible 执行批量命令"
#: settings/serializers/security.py:169 #: settings/serializers/security.py:174
msgid "Session share" msgid "Session share"
msgstr "会话分享" msgstr "会话分享"
#: settings/serializers/security.py:170 #: settings/serializers/security.py:175
msgid "Enabled, Allows user active session to be shared with other users" msgid "Enabled, Allows user active session to be shared with other users"
msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作" msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作"
#: settings/serializers/security.py:173 #: settings/serializers/security.py:178
msgid "Remote Login Protection" msgid "Remote Login Protection"
msgstr "异地登录保护" msgstr "异地登录保护"
#: settings/serializers/security.py:175 #: settings/serializers/security.py:180
msgid "" msgid ""
"The system determines whether the login IP address belongs to a common login " "The system determines whether the login IP address belongs to a common login "
"city. If the account is logged in from a common login city, the system sends " "city. If the account is logged in from a common login city, the system sends "
"a remote login reminder" "a remote login reminder"
msgstr "" msgstr ""
"根据登录IP是否所属常用登录城市进行判断若账号在非常用城市登录会发送异地" "根据登录 IP 是否所属常用登录城市进行判断,若账号在非常用城市登录,会发送异地"
"录提醒" "录提醒"
#: settings/serializers/sms.py:7 #: settings/serializers/sms.py:7
msgid "Label" msgid "Label"
@@ -3579,104 +3589,104 @@ msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389"
msgid "Enable XRDP" msgid "Enable XRDP"
msgstr "启用 XRDP 服务" msgstr "启用 XRDP 服务"
#: settings/utils/ldap.py:412 #: settings/utils/ldap.py:415
msgid "ldap:// or ldaps:// protocol is used." msgid "ldap:// or ldaps:// protocol is used."
msgstr "使用 ldap:// 或 ldaps:// 协议" msgstr "使用 ldap:// 或 ldaps:// 协议"
#: settings/utils/ldap.py:423 #: settings/utils/ldap.py:426
msgid "Host or port is disconnected: {}" msgid "Host or port is disconnected: {}"
msgstr "主机或端口不可连接: {}" msgstr "主机或端口不可连接: {}"
#: settings/utils/ldap.py:425 #: settings/utils/ldap.py:428
msgid "The port is not the port of the LDAP service: {}" msgid "The port is not the port of the LDAP service: {}"
msgstr "端口不是LDAP服务端口: {}" msgstr "端口不是LDAP服务端口: {}"
#: settings/utils/ldap.py:427 #: settings/utils/ldap.py:430
msgid "Please add certificate: {}" msgid "Please add certificate: {}"
msgstr "请添加证书" msgstr "请添加证书"
#: settings/utils/ldap.py:431 settings/utils/ldap.py:458 #: settings/utils/ldap.py:434 settings/utils/ldap.py:461
#: settings/utils/ldap.py:488 settings/utils/ldap.py:516 #: settings/utils/ldap.py:491 settings/utils/ldap.py:519
msgid "Unknown error: {}" msgid "Unknown error: {}"
msgstr "未知错误: {}" msgstr "未知错误: {}"
#: settings/utils/ldap.py:445 #: settings/utils/ldap.py:448
msgid "Bind DN or Password incorrect" msgid "Bind DN or Password incorrect"
msgstr "绑定DN或密码错误" msgstr "绑定DN或密码错误"
#: settings/utils/ldap.py:452 #: settings/utils/ldap.py:455
msgid "Please enter Bind DN: {}" msgid "Please enter Bind DN: {}"
msgstr "请输入绑定DN: {}" msgstr "请输入绑定DN: {}"
#: settings/utils/ldap.py:454 #: settings/utils/ldap.py:457
msgid "Please enter Password: {}" msgid "Please enter Password: {}"
msgstr "请输入密码: {}" msgstr "请输入密码: {}"
#: settings/utils/ldap.py:456 #: settings/utils/ldap.py:459
msgid "Please enter correct Bind DN and Password: {}" msgid "Please enter correct Bind DN and Password: {}"
msgstr "请输入正确的绑定DN和密码: {}" msgstr "请输入正确的绑定DN和密码: {}"
#: settings/utils/ldap.py:474 #: settings/utils/ldap.py:477
msgid "Invalid User OU or User search filter: {}" msgid "Invalid User OU or User search filter: {}"
msgstr "不合法的用户OU或用户过滤器: {}" msgstr "不合法的用户OU或用户过滤器: {}"
#: settings/utils/ldap.py:505 #: settings/utils/ldap.py:508
msgid "LDAP User attr map not include: {}" msgid "LDAP User attr map not include: {}"
msgstr "LDAP属性映射没有包含: {}" msgstr "LDAP属性映射没有包含: {}"
#: settings/utils/ldap.py:512 #: settings/utils/ldap.py:515
msgid "LDAP User attr map is not dict" msgid "LDAP User attr map is not dict"
msgstr "LDAP属性映射不合法" msgstr "LDAP属性映射不合法"
#: settings/utils/ldap.py:531 #: settings/utils/ldap.py:534
msgid "LDAP authentication is not enabled" msgid "LDAP authentication is not enabled"
msgstr "LDAP认证没有启用" msgstr "LDAP认证没有启用"
#: settings/utils/ldap.py:549 #: settings/utils/ldap.py:552
msgid "Error (Invalid LDAP server): {}" msgid "Error (Invalid LDAP server): {}"
msgstr "错误 不合法的LDAP服务器地址: {}" msgstr "错误 不合法的LDAP服务器地址: {}"
#: settings/utils/ldap.py:551 #: settings/utils/ldap.py:554
msgid "Error (Invalid Bind DN): {}" msgid "Error (Invalid Bind DN): {}"
msgstr "错误不合法的绑定DN: {}" msgstr "错误不合法的绑定DN: {}"
#: settings/utils/ldap.py:553 #: settings/utils/ldap.py:556
msgid "Error (Invalid LDAP User attr map): {}" msgid "Error (Invalid LDAP User attr map): {}"
msgstr "错误不合法的LDAP属性映射: {}" msgstr "错误不合法的LDAP属性映射: {}"
#: settings/utils/ldap.py:555 #: settings/utils/ldap.py:558
msgid "Error (Invalid User OU or User search filter): {}" msgid "Error (Invalid User OU or User search filter): {}"
msgstr "错误不合法的用户OU或用户过滤器: {}" msgstr "错误不合法的用户OU或用户过滤器: {}"
#: settings/utils/ldap.py:557 #: settings/utils/ldap.py:560
msgid "Error (Not enabled LDAP authentication): {}" msgid "Error (Not enabled LDAP authentication): {}"
msgstr "错误没有启用LDAP认证: {}" msgstr "错误没有启用LDAP认证: {}"
#: settings/utils/ldap.py:559 #: settings/utils/ldap.py:562
msgid "Error (Unknown): {}" msgid "Error (Unknown): {}"
msgstr "错误(未知): {}" msgstr "错误(未知): {}"
#: settings/utils/ldap.py:562 #: settings/utils/ldap.py:565
msgid "Succeed: Match {} s user" msgid "Succeed: Match {} s user"
msgstr "成功匹配 {} 个用户" msgstr "成功匹配 {} 个用户"
#: settings/utils/ldap.py:595 #: settings/utils/ldap.py:598
msgid "Authentication failed (configuration incorrect): {}" msgid "Authentication failed (configuration incorrect): {}"
msgstr "认证失败(配置错误): {}" msgstr "认证失败(配置错误): {}"
#: settings/utils/ldap.py:597 #: settings/utils/ldap.py:600
msgid "Authentication failed (before login check failed): {}" msgid "Authentication failed (before login check failed): {}"
msgstr "认证失败(登录前检查失败): {}" msgstr "认证失败(登录前检查失败): {}"
#: settings/utils/ldap.py:599 #: settings/utils/ldap.py:602
msgid "Authentication failed (username or password incorrect): {}" msgid "Authentication failed (username or password incorrect): {}"
msgstr "认证失败 (用户名或密码不正确): {}" msgstr "认证失败 (用户名或密码不正确): {}"
#: settings/utils/ldap.py:601 #: settings/utils/ldap.py:604
msgid "Authentication failed (Unknown): {}" msgid "Authentication failed (Unknown): {}"
msgstr "认证失败: (未知): {}" msgstr "认证失败: (未知): {}"
#: settings/utils/ldap.py:604 #: settings/utils/ldap.py:607
msgid "Authentication success: {}" msgid "Authentication success: {}"
msgstr "认证成功: {}" msgstr "认证成功: {}"
@@ -3827,11 +3837,11 @@ msgstr ""
msgid "Send verification code" msgid "Send verification code"
msgstr "发送验证码" msgstr "发送验证码"
#: templates/_mfa_login_field.html:105 #: templates/_mfa_login_field.html:106
msgid "Wait: " msgid "Wait: "
msgstr "等待:" msgstr "等待:"
#: templates/_mfa_login_field.html:115 #: templates/_mfa_login_field.html:116
msgid "The verification code has been sent" msgid "The verification code has been sent"
msgstr "验证码已发送" msgstr "验证码已发送"
@@ -6127,200 +6137,3 @@ msgstr "旗舰版"
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid "No upload or download permission"
#~ msgstr "没有上传下载权限"
#~ msgid "OTP not set, please set it first"
#~ msgstr "OTP认证没有设置请先完成设置"
#~ msgid "Radius MFA"
#~ msgstr "Radius MFA"
#~ msgid "Help Website URL"
#~ msgstr "官网链接"
#~ msgid "default: http://www.jumpserver.org"
#~ msgstr "如: http://dev.jumpserver.org:8080"
#~ msgid "One-time password invalid, or ntp sync server time"
#~ msgstr "MFA 验证码不正确,或者服务器端时间不对"
#~ msgid "Download MFA APP, Using dynamic code"
#~ msgstr "下载 MFA APP, 使用一次性动态码"
#~ msgid "MFA Radius"
#~ msgstr "Radius MFA"
#~ msgid "Please enter verification code"
#~ msgstr "请输入验证码"
#, python-brace-format
#~ msgid ""
#~ "One-time password invalid, or ntp sync server time, You can also try "
#~ "{times_try} times (The account will be temporarily locked for "
#~ "{block_time} minutes)"
#~ msgstr ""
#~ "虚拟MFA 不正确,或者服务器端时间不对。 您还可以尝试 {times_try} 次(账号将"
#~ "被临时 锁定 {block_time} 分钟)"
#, python-brace-format
#~ msgid ""
#~ "The MFA type({mfa_type}) is not supported, You can also try {times_try} "
#~ "times (The account will be temporarily locked for {block_time} minutes)"
#~ msgstr ""
#~ "该({mfa_type}) MFA 类型不支持, 您还可以尝试 {times_try} 次(账号将被临时 "
#~ "锁定 {block_time} 分钟)"
#~ msgid "One-time password"
#~ msgstr "一次性密码"
#~ msgid "Go"
#~ msgstr "立即"
#, python-brace-format
#~ msgid "Hello {name}"
#~ msgstr "你好 {name}"
#~ msgid "Login direct"
#~ msgstr "直接登录"
#~ msgid "to apply for a password reset email."
#~ msgstr "申请重置"
#~ msgid "Please login and reset your MFA."
#~ msgstr "请登录并重新设置你的 MFA"
#~ msgid "Please login and reset your ssh public key."
#~ msgstr "请登录并重新设置你的密钥"
# msgid "Update user"
# msgstr "更新用户"
#, python-format
#~ msgid ""
#~ "\n"
#~ " <div>\n"
#~ " <p>Your account has been created successfully</p>\n"
#~ " <div>\n"
#~ " Username: %(username)s\n"
#~ " <br/>\n"
#~ " Password: <a href=\"%(rest_password_url)s?token="
#~ "%(rest_password_token)s\">\n"
#~ " click here to set your password</a> \n"
#~ " (This link is valid for 1 hour. After it expires, <a href="
#~ "\"%(forget_password_url)s?email=%(email)s\">request new one</a>)\n"
#~ " </div>\n"
#~ " <div>\n"
#~ " <p>---</p>\n"
#~ " <a href=\"%(login_url)s\">Login direct</a>\n"
#~ " </div>\n"
#~ " </div>\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " <div>\n"
#~ " <p>您的账户已创建成功</p>\n"
#~ " <div>\n"
#~ " 用户名: %(username)s\n"
#~ " <br/>\n"
#~ " 密码: <a href=\"%(rest_password_url)s?token="
#~ "%(rest_password_token)s\">请点击这里设置密码</a> (这个链接有效期1小时, 超"
#~ "过时间您可以 <a href=\"%(forget_password_url)s?email=%(email)s\">重新申请"
#~ "</a>)\n"
#~ " </div>\n"
#~ " <div>\n"
#~ " <p>---</p>\n"
#~ " <a href=\"%(login_url)s\">直接登录</a>\n"
#~ " </div>\n"
#~ " </div>\n"
#~ " "
#, python-format
#~ msgid "Hello %(name)s"
#~ msgstr "你好 %(name)s"
#~ msgid "This link is valid for 1 hour. After it expires,"
#~ msgstr "这个链接有效期1小时, 超过时间您可以"
#~ msgid "Welcome to the JumpServer open source Bastion Host"
#~ msgstr "欢迎使用JumpServer开源堡垒机"
#~ msgid "Login IP"
#~ msgstr "登录IP"
#~ msgid "The user `{}` is not in the current organization: `{}`"
#~ msgstr "用户 `{}` 不在当前组织: `{}`"
#~ msgid "Login Confirm"
#~ msgstr "登录复核"
#~ msgid "{} need confirm by {}"
#~ msgstr "{} 需要 {} 复核"
#~ msgid "Enabled, please go to the user detail add approver"
#~ msgstr "启用后, 请在用户详情中添加审批人"
#~ msgid ""
#~ "\n"
#~ "Time: {}"
#~ msgstr ""
#~ "\n"
#~ "时间:{}"
#~ msgid "asset permission"
#~ msgstr "资产授权"
#~ msgid "Asset permissions will expired"
#~ msgstr "资产授权即将过期"
#, python-brace-format
#~ msgid ""
#~ "\n"
#~ "Organization: {org}\n"
#~ "Permissions: {perms}\n"
#~ msgstr ""
#~ "\n"
#~ "组织: {org}\n"
#~ "权限: {perms}\n"
#~ msgid "asset permissions of organization"
#~ msgstr "组织资产授权"
#~ msgid "application permissions of organization"
#~ msgstr "组织的应用授权 {}"
#, python-brace-format
#~ msgid ""
#~ "\n"
#~ " Organization: {org} \n"
#~ " Permissions: {perms} \n"
#~ msgstr ""
#~ "\n"
#~ "组织: {org} \n"
#~ "授权: {perms} \n"
#~ msgid "You've been hacked"
#~ msgstr "你被攻击了"
#~ msgid "Binding DingTalk failed"
#~ msgstr "绑定钉钉失败"
#~ msgid "Binding FeiShu failed"
#~ msgstr "绑定飞书失败"
#~ msgid "Binding WeCom failed"
#~ msgstr "绑定企业微信失败"
#~ msgid "Enable Login MFA"
#~ msgstr "启用登录MFA"
#~ msgid "Enable login password add-on"
#~ msgstr "启用登录密码附加码"
#~ msgid "OpenID"
#~ msgstr "OpenID"
#~ msgid "CAS"
#~ msgstr "CAS"
#~ msgid "Only "
#~ msgstr "仅能从用户配置来源登录"

View File

@@ -52,11 +52,10 @@ class SiteMsgWebsocket(JsonWebsocketConsumer):
try: try:
msgs = self.chan.listen() msgs = self.chan.listen()
# 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中 # 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中
close_old_connections()
for message in msgs: for message in msgs:
if message['type'] != 'message': if message['type'] != 'message':
continue continue
close_old_connections()
try: try:
msg = json.loads(message['data'].decode()) msg = json.loads(message['data'].decode())
except json.JSONDecoder as e: except json.JSONDecoder as e:
@@ -70,6 +69,7 @@ class SiteMsgWebsocket(JsonWebsocketConsumer):
logger.debug('Message users: {}'.format(users)) logger.debug('Message users: {}'.format(users))
if user_id in users: if user_id in users:
self.send_unread_msg_count() self.send_unread_msg_count()
close_old_connections()
except ConnectionError: except ConnectionError:
logger.error('Redis chan closed') logger.error('Redis chan closed')
finally: finally:

View File

@@ -143,7 +143,7 @@ class AssetPermissionFilter(PermissionBaseFilter):
if not _nodes: if not _nodes:
return queryset.none() return queryset.none()
node = _nodes.get() node = _nodes.first()
if not is_query_all: if not is_query_all:
queryset = queryset.filter(nodes=node) queryset = queryset.filter(nodes=node)
@@ -170,13 +170,13 @@ class AssetPermissionFilter(PermissionBaseFilter):
return queryset return queryset
if not assets: if not assets:
return queryset.none() return queryset.none()
asset = assets.get() assetids = list(assets.values_list('id', flat=True))
if not is_query_all: if not is_query_all:
queryset = queryset.filter(assets=asset) queryset = queryset.filter(assets__in=assetids)
return queryset return queryset
inherit_all_nodekeys = set() inherit_all_nodekeys = set()
inherit_nodekeys = asset.nodes.values_list('key', flat=True) inherit_nodekeys = set(assets.values_list('nodes__key', flat=True))
for key in inherit_nodekeys: for key in inherit_nodekeys:
ancestor_keys = Node.get_node_ancestor_keys(key, with_self=True) ancestor_keys = Node.get_node_ancestor_keys(key, with_self=True)
@@ -185,8 +185,8 @@ class AssetPermissionFilter(PermissionBaseFilter):
inherit_all_nodeids = Node.objects.filter(key__in=inherit_all_nodekeys).values_list('id', flat=True) inherit_all_nodeids = Node.objects.filter(key__in=inherit_all_nodekeys).values_list('id', flat=True)
inherit_all_nodeids = list(inherit_all_nodeids) inherit_all_nodeids = list(inherit_all_nodeids)
qs1 = queryset.filter(assets=asset).distinct() qs1 = queryset.filter(assets__in=assetids).distinct()
qs2 = queryset.filter(nodes__id__in=inherit_all_nodeids).distinct() qs2 = queryset.filter(nodes__in=inherit_all_nodeids).distinct()
qs = UnionQuerySet(qs1, qs2) qs = UnionQuerySet(qs1, qs2)
return qs return qs

View File

@@ -1,7 +1,7 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment from acls.serializers.rules import ip_group_help_text, ip_group_child_validator
class SecurityPasswordRuleSerializer(serializers.Serializer): class SecurityPasswordRuleSerializer(serializers.Serializer):
@@ -27,13 +27,10 @@ class SecurityPasswordRuleSerializer(serializers.Serializer):
) )
def ip_child_validator(ip_child): login_ip_limit_time_help_text = _(
is_valid = is_ip_address(ip_child) \ 'Unit: minute, If the user has failed to log in for a limited number of times, '
or is_ip_network(ip_child) \ 'no login is allowed during this time interval.'
or is_ip_segment(ip_child) )
if not is_valid:
error = _('IP address invalid: `{}`').format(ip_child)
raise serializers.ValidationError(error)
class SecurityAuthSerializer(serializers.Serializer): class SecurityAuthSerializer(serializers.Serializer):
@@ -47,23 +44,31 @@ class SecurityAuthSerializer(serializers.Serializer):
) )
SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField( SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(
min_value=3, max_value=99999, min_value=3, max_value=99999,
label=_('Limit the number of login failures') label=_('Limit the number of user login failures')
) )
SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField( SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField(
min_value=5, max_value=99999, required=True, min_value=5, max_value=99999, required=True,
label=_('Block logon interval'), label=_('Block user login interval'),
help_text=_( help_text=login_ip_limit_time_help_text
'Unit: minute, If the user has failed to log in for a limited number of times, ' )
'no login is allowed during this time interval.' SECURITY_LOGIN_IP_LIMIT_COUNT = serializers.IntegerField(
) min_value=3, max_value=99999,
label=_('Limit the number of IP login failures')
)
SECURITY_LOGIN_IP_LIMIT_TIME = serializers.IntegerField(
min_value=5, max_value=99999, required=True,
label=_('Block IP login interval'),
help_text=login_ip_limit_time_help_text
)
SECURITY_LOGIN_IP_WHITE_LIST = serializers.ListField(
default=[], label=_('Login IP White List'), allow_empty=True,
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]),
help_text=ip_group_help_text
) )
SECURITY_LOGIN_IP_BLACK_LIST = serializers.ListField( SECURITY_LOGIN_IP_BLACK_LIST = serializers.ListField(
default=[], label=_('Login IP Black List'), allow_empty=True, default=[], label=_('Login IP Black List'), allow_empty=True,
child=serializers.CharField(max_length=1024, validators=[ip_child_validator]), child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]),
help_text=_( help_text=ip_group_help_text
'Format for comma-delimited string. Such as: '
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64'
)
) )
SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField( SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(
min_value=1, max_value=99999, required=True, min_value=1, max_value=99999, required=True,

View File

@@ -86,17 +86,17 @@ def subscribe_settings_change(sender, **kwargs):
sub = setting_pub_sub.subscribe() sub = setting_pub_sub.subscribe()
msgs = sub.listen() msgs = sub.listen()
# 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中 # 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中
close_old_connections()
for msg in msgs: for msg in msgs:
if msg["type"] != "message": if msg["type"] != "message":
continue continue
close_old_connections()
item = msg['data'].decode() item = msg['data'].decode()
logger.debug("Found setting change: {}".format(str(item))) logger.debug("Found setting change: {}".format(str(item)))
Setting.refresh_item(item) Setting.refresh_item(item)
close_old_connections()
except Exception as e: except Exception as e:
logger.exception(f'subscribe_settings_change: {e}') logger.exception(f'subscribe_settings_change: {e}')
Setting.refresh_all_settings() Setting.refresh_all_settings()
finally:
close_old_connections() close_old_connections()
t = threading.Thread(target=keep_subscribe_settings_change) t = threading.Thread(target=keep_subscribe_settings_change)

View File

@@ -136,6 +136,10 @@ article ul li:last-child{
border-radius: 6px; border-radius: 6px;
color: white; color: white;
} }
.next:hover {
color: white;
}
/*绑定TOTP*/ /*绑定TOTP*/
/*版权信息*/ /*版权信息*/

View File

@@ -207,10 +207,10 @@ class UserResetMFAApi(UserQuerysetMixin, generics.RetrieveAPIView):
user = self.get_object() if kwargs.get('pk') else request.user user = self.get_object() if kwargs.get('pk') else request.user
if user == request.user: if user == request.user:
msg = _("Could not reset self otp, use profile reset instead") msg = _("Could not reset self otp, use profile reset instead")
return Response({"error": msg}, status=401) return Response({"error": msg}, status=400)
backends = user.active_mfa_backends_mapper backends = user.active_mfa_backends_mapper
for backend in backends: for backend in backends.values():
if backend.can_disable(): if backend.can_disable():
backend.disable() backend.disable()

View File

@@ -7,36 +7,8 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="verify">{% trans 'Please enter the password of' %}&nbsp;{% trans 'account' %}&nbsp;<span>{{ user.username }}</span>&nbsp;{% trans 'to complete the binding operation' %}</div> <hr style="width: 500px; margin: 10px auto auto;">
<hr style="width: 500px; margin: auto; margin-top: 10px;"> <a type="submit" class="next" href="{% url 'authentication:user-otp-enable-install-app' %}" >
<form id="verify-form" class="" role="form" method="post" action=""> {% trans 'Next' %}
{% csrf_token %} </a>
<div class="form-input">
<input id="password" type="password" class="" placeholder="{% trans 'Password' %}" required="" autofocus="autofocus">
<input id="password-hidden" type="text" style="display:none" name="{{ form.password.html_name }}">
</div>
<button type="submit" class="next" onclick="doVerify();return false;">{% trans 'Next' %}</button>
{% if 'password' in form.errors %}
<p class="red-fonts">{{ form.password.errors.as_text }}</p>
{% endif %}
</form>
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script>
<script>
function encryptLoginPassword(password, rsaPublicKey) {
var jsencrypt = new JSEncrypt(); //加密对象
jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥
return jsencrypt.encrypt(password); //加密
}
function doVerify() {
//公钥加密
var rsaPublicKey = "{{ rsa_public_key }}"
var password = $('#password').val(); //明文密码
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
$('#password-hidden').val(passwordEncrypted); //返回给密码输入input
$('#verify-form').submit();//post提交
}
$(document).ready(function () {
})
</script>
{% endblock %} {% endblock %}

View File

@@ -16,12 +16,12 @@
<div id="qr_code"></div> <div id="qr_code"></div>
<div style="display: block; margin: 0">Secret: {{ otp_secret_key }}</div> <div style="display: block; margin: 0">Secret: {{ otp_secret_key }}</div>
<form class="" role="form" method="post" action=""> <form id="bind-form" class="" role="form" method="post" action="">
{% csrf_token %} {% csrf_token %}
<div class="form-input"> <div class="form-input">
<input type="text" class="" name="otp_code" placeholder="{% trans 'Six figures' %}" required=""> <input type="text" class="" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
</div> </div>
<button type="submit" class="next">{% trans 'Next' %}</button> <a type="submit" class="next button" onclick="submitForm()">{% trans 'Next' %}</a>
{% if 'otp_code' in form.errors %} {% if 'otp_code' in form.errors %}
<p style="color: #ed5565">{{ form.otp_code.errors.as_text }}</p> <p style="color: #ed5565">{{ form.otp_code.errors.as_text }}</p>
{% endif %} {% endif %}
@@ -33,6 +33,10 @@
$('.change-color li:eq(1) i').css('color', '#1ab394'); $('.change-color li:eq(1) i').css('color', '#1ab394');
$('.change-color li:eq(2) i').css('color', '#1ab394'); $('.change-color li:eq(2) i').css('color', '#1ab394');
function submitForm() {
$('#bind-form').submit()
}
$(document).ready(function() { $(document).ready(function() {
// 生成用户绑定otp的二维码 // 生成用户绑定otp的二维码
var qrcode = new QRCode(document.getElementById('qr_code'), { var qrcode = new QRCode(document.getElementById('qr_code'), {

View File

@@ -11,7 +11,7 @@ from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from common.tasks import send_mail_async from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none from common.utils import reverse, get_object_or_none, ip
from .models import User from .models import User
logger = logging.getLogger('jumpserver') logger = logging.getLogger('jumpserver')
@@ -180,33 +180,37 @@ class BlockGlobalIpUtilBase:
self.ip = ip self.ip = ip
self.limit_key = self.LIMIT_KEY_TMPL.format(ip) self.limit_key = self.LIMIT_KEY_TMPL.format(ip)
self.block_key = self.BLOCK_KEY_TMPL.format(ip) self.block_key = self.BLOCK_KEY_TMPL.format(ip)
self.key_ttl = int(settings.SECURITY_LOGIN_LIMIT_TIME) * 60 self.key_ttl = int(settings.SECURITY_LOGIN_IP_LIMIT_TIME) * 60
@property @property
def ip_in_black_list(self): def ip_in_black_list(self):
return self.ip in settings.SECURITY_LOGIN_IP_BLACK_LIST return ip.contains_ip(self.ip, settings.SECURITY_LOGIN_IP_BLACK_LIST)
@property
def ip_in_white_list(self):
return ip.contains_ip(self.ip, settings.SECURITY_LOGIN_IP_WHITE_LIST)
def set_block_if_need(self): def set_block_if_need(self):
if not self.ip_in_black_list: if self.ip_in_white_list or self.ip_in_black_list:
return return
count = cache.get(self.limit_key, 0) count = cache.get(self.limit_key, 0)
count += 1 count += 1
cache.set(self.limit_key, count, self.key_ttl) cache.set(self.limit_key, count, self.key_ttl)
limit_count = settings.SECURITY_LOGIN_LIMIT_COUNT limit_count = settings.SECURITY_LOGIN_IP_LIMIT_COUNT
if count < limit_count: if count < limit_count:
return return
cache.set(self.block_key, True, self.key_ttl) cache.set(self.block_key, True, self.key_ttl)
def clean_block_if_need(self): def clean_block_if_need(self):
if not self.ip_in_black_list:
return
cache.delete(self.limit_key) cache.delete(self.limit_key)
cache.delete(self.block_key) cache.delete(self.block_key)
def is_block(self): def is_block(self):
if not self.ip_in_black_list: if self.ip_in_white_list:
return False return False
if self.ip_in_black_list:
return True
return bool(cache.get(self.block_key)) return bool(cache.get(self.block_key))

View File

@@ -6,10 +6,12 @@ from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.contrib.auth import logout as auth_logout from django.contrib.auth import logout as auth_logout
from django.shortcuts import redirect
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from authentication.mixins import AuthMixin from authentication.mixins import AuthMixin
from authentication.mfa import MFAOtp, otp_failed_msg from authentication.mfa import MFAOtp, otp_failed_msg
from authentication.errors import SessionEmptyError
from common.utils import get_logger, FlashMessageUtil from common.utils import get_logger, FlashMessageUtil
from common.mixins.views import PermissionsMixin from common.mixins.views import PermissionsMixin
from common.permissions import IsValidUser from common.permissions import IsValidUser
@@ -30,11 +32,15 @@ __all__ = [
logger = get_logger(__name__) logger = get_logger(__name__)
class UserOtpEnableStartView(UserVerifyPasswordView): class UserOtpEnableStartView(AuthMixin, TemplateView):
template_name = 'users/user_otp_check_password.html' template_name = 'users/user_otp_check_password.html'
def get_success_url(self): def get(self, request, *args, **kwargs):
return reverse('authentication:user-otp-enable-install-app') try:
self.get_user_from_session()
except SessionEmptyError:
return redirect('authentication:login') + '?_=otp_enable_start'
return super().get(request, *args, **kwargs)
class UserOtpEnableInstallAppView(TemplateView): class UserOtpEnableInstallAppView(TemplateView):