Compare commits

...

17 Commits
v4.4 ... v2.16

Author SHA1 Message Date
ibuler
62920fef8b fix: 修复 otp 返回时报错
(cherry picked from commit 19ecc7fef6)
2022-04-02 16:03:47 +08:00
fit2bot
be7c8ca558 fix: redis lock bug (#7340)
Co-authored-by: feng626 <1304903146@qq.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
2021-12-08 17:55:08 +08:00
xinwen
06caf56f11 fix: redis 锁在redis集群环境报错 2021-12-03 19:23:25 +08:00
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
27 changed files with 309 additions and 446 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__)
__all__ = ['RuleSerializer']
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text']
def ip_group_child_validator(ip_group_child):
@@ -21,13 +21,14 @@ def ip_group_child_validator(ip_group_child):
raise serializers.ValidationError(error)
class RuleSerializer(serializers.Serializer):
ip_group_help_text = _(
'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:db8:1a:1110::/64 '
)
ip_group_help_text = _(
'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:db8:1a:1110::/64 '
)
class RuleSerializer(serializers.Serializer):
ip_group = serializers.ListField(
default=['*'], label=_('IP'), help_text=ip_group_help_text,
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]))

View File

@@ -48,6 +48,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
extra_kwargs = {
'password': {
"write_only": True,
'trim_whitespace': False,
"validators": [validate_password_contains_left_double_curly_bracket]
},
'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()
msgs = subscribe.listen()
# 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中
close_old_connections()
for message in msgs:
if message["type"] != "message":
continue
close_old_connections()
org_id = message['data'].decode()
root_org_id = Organization.ROOT_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={}"
"".format(str(org_id), os.getpid())
)
close_old_connections()
except Exception as e:
logger.exception(f'subscribe_node_assets_mapping_expire: {e}')
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 "
"(The account will be temporarily locked for {block_time} minutes)"
)
block_login_msg = _(
block_user_login_msg = _(
"The account has been locked "
"(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 = _(
"The account has been locked "
"(please contact admin to unlock it or try again after {} minutes)"
@@ -118,7 +122,7 @@ class BlockGlobalIpLoginError(AuthFailedError):
error = 'block_global_ip_login'
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()
super().__init__(username=username, ip=ip, **kwargs)
@@ -133,7 +137,7 @@ class CredentialError(
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
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
default_msg = invalid_login_msg.format(
@@ -184,7 +188,7 @@ class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
error = 'block_login'
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)

View File

@@ -43,8 +43,8 @@ class UserLoginForm(forms.Form):
class UserCheckOtpCodeForm(forms.Form):
code = forms.CharField(label=_('MFA Code'), max_length=6, required=False)
mfa_type = forms.CharField(label=_('MFA type'), max_length=6)
code = forms.CharField(label=_('MFA Code'), max_length=128, required=False)
mfa_type = forms.CharField(label=_('MFA type'), max_length=128)
class CustomCaptchaTextInput(CaptchaTextInput):
@@ -57,7 +57,7 @@ class CaptchaMixin(forms.Form):
class ChallengeMixin(forms.Form):
challenge = forms.CharField(
label=_('MFA code'), max_length=6, required=False,
label=_('MFA code'), max_length=128, required=False,
widget=forms.TextInput(attrs={
'placeholder': _("Dynamic code"),
'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:
"""
这个 中间件 是用来全局拦截开启了 MFA 却没有认证的,如 OIDC, CAS使用第三方库做的登录直接 login 了,
所以只能在 Middleware 中控制
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if request.path.find('/auth/login/otp/') > -1:
# 没有校验
if not request.session.get('auth_mfa_required'):
return response
if request.session.get('auth_mfa_required'):
return redirect('authentication:login-mfa')
return response
# 没有认证过,证明不是从 第三方 来的
if request.user.is_anonymous:
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):
if not settings.SECURITY_MFA_IN_LOGIN_PAGE:
return
self._check_if_no_active_mfa(user)
if not user.active_mfa_backends:
return
request = self.request
data = request.data if hasattr(request, 'data') else request.POST
@@ -274,10 +275,8 @@ class MFAMixin:
if not user.mfa_enabled:
return
self._check_if_no_active_mfa(user)
active_mfa_mapper = user.active_mfa_backends_mapper
raise errors.MFARequiredError(mfa_types=tuple(active_mfa_mapper.keys()))
active_mfa_names = user.active_mfa_backends_mapper.keys()
raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names))
def mark_mfa_ok(self, mfa_type):
self.request.session['auth_mfa'] = 1
@@ -417,12 +416,10 @@ class AuthACLMixin:
self.request.session["auth_confirm"] = "1"
return
elif ticket.state_reject:
self.clean_mfa_mark()
raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_state_display()
)
elif ticket.state_close:
self.clean_mfa_mark()
raise errors.LoginConfirmOtherError(
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 jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success
from .signals import post_auth_success, post_auth_failed

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,10 @@
from functools import wraps
import threading
from redis_lock import Lock as RedisLock, NotAcquired
from redis_lock import (
Lock as RedisLock, NotAcquired, UNLOCK_SCRIPT,
EXTEND_SCRIPT, RESET_SCRIPT, RESET_ALL_SCRIPT
)
from redis import Redis
from django.db import transaction
@@ -49,7 +52,8 @@ class DistributedLock(RedisLock):
else:
auto_renewal = False
super().__init__(redis_client=redis, name=name, expire=expire, auto_renewal=auto_renewal)
super().__init__(redis_client=redis, name='{'+name+'}', expire=expire, auto_renewal=auto_renewal)
self.register_scripts(redis)
self._release_on_transaction_commit = release_on_transaction_commit
self._release_raise_exc = release_raise_exc
self._reentrant = reentrant
@@ -73,6 +77,13 @@ class DistributedLock(RedisLock):
return func(*args, **kwds)
return inner
@classmethod
def register_scripts(cls, redis_client):
cls.unlock_script = redis_client.register_script(UNLOCK_SCRIPT)
cls.extend_script = redis_client.register_script(EXTEND_SCRIPT)
cls.reset_script = redis_client.register_script(RESET_SCRIPT)
cls.reset_all_script = redis_client.register_script(RESET_ALL_SCRIPT)
def locked_by_me(self):
if self.locked():
if self.get_owner_id() == self.id:

View File

@@ -291,9 +291,6 @@ class Config(dict):
'SECURITY_COMMAND_EXECUTION': True,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': 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_PASSWORD_EXPIRATION_TIME': 9999,
'SECURITY_PASSWORD_MIN_LENGTH': 6,
@@ -318,6 +315,14 @@ class Config(dict):
'USER_LOGIN_SINGLE_MACHINE_ENABLED': False,
'ONLY_ALLOW_EXIST_USER_AUTH': 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',

View File

@@ -32,11 +32,8 @@ TERMINAL_REPLAY_STORAGE = CONFIG.TERMINAL_REPLAY_STORAGE
# Security settings
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_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION
SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day
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
@@ -63,6 +60,14 @@ SECURITY_INSECURE_COMMAND = CONFIG.SECURITY_INSECURE_COMMAND
SECURITY_INSECURE_COMMAND_LEVEL = CONFIG.SECURITY_INSECURE_COMMAND_LEVEL
SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = CONFIG.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER
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_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH

View File

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

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\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"
"Last-Translator: ibuler <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, "
"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
#: assets/models/asset.py:211 assets/models/domain.py:61
#: assets/serializers/account.py:12
@@ -225,12 +225,12 @@ msgstr "组织 `{}` 不存在"
msgid "None of the reviewers belong to Organization `{}`"
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
msgid "IP address invalid: `{}`"
msgstr "IP 地址无效: `{}`"
#: acls/serializers/rules/rules.py:26
#: acls/serializers/rules/rules.py:25
msgid ""
"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:"
@@ -239,7 +239,7 @@ 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"
#: acls/serializers/rules/rules.py:34
#: acls/serializers/rules/rules.py:35
msgid "Time Period"
msgstr "时段"
@@ -1619,13 +1619,19 @@ msgstr ""
"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将"
"被临时 锁定 {block_time} 分钟)"
#: authentication/errors.py:55 authentication/errors.py:59
#: authentication/errors.py:55 authentication/errors.py:63
msgid ""
"The account has been locked (please contact admin to unlock it or try again "
"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
msgid ""
"{error}, You can also try {times_try} times (The account will be temporarily "
@@ -1633,63 +1639,63 @@ msgid ""
msgstr ""
"{error},您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)"
#: authentication/errors.py:67
#: authentication/errors.py:71
msgid "MFA required"
msgstr "需要 MFA 认证"
#: authentication/errors.py:68
#: authentication/errors.py:72
msgid "MFA not set, please set it first"
msgstr "MFA 没有设置,请先完成设置"
#: authentication/errors.py:69
#: authentication/errors.py:73
msgid "Login confirm required"
msgstr "需要登录复核"
#: authentication/errors.py:70
#: authentication/errors.py:74
msgid "Wait login confirm ticket for accept"
msgstr "等待登录复核处理"
#: authentication/errors.py:71
#: authentication/errors.py:75
msgid "Login confirm ticket was {}"
msgstr "登录复核 {}"
#: authentication/errors.py:184 authentication/errors.py:248
#: authentication/errors.py:255
msgid "IP is not allowed"
msgstr "来源 IP 不被允许登录"
#: authentication/errors.py:255
#: authentication/errors.py:262
msgid "Time Period is not allowed"
msgstr "该 时间段 不被允许登录"
#: authentication/errors.py:288
#: authentication/errors.py:295
msgid "SSO auth closed"
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"
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"
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"
msgstr "您的密码已过期,先修改再登录"
#: authentication/errors.py:345
#: authentication/errors.py:352
msgid "Your password is invalid"
msgstr "您的密码无效"
#: authentication/errors.py:350
#: authentication/errors.py:357
msgid "Please enter MFA code"
msgstr "请输入 MFA 验证码"
#: authentication/errors.py:355
#: authentication/errors.py:362
msgid "Please enter SMS code"
msgstr "请输入短信验证码"
#: authentication/errors.py:360 users/exceptions.py:15
#: authentication/errors.py:367 users/exceptions.py:15
msgid "Phone not set"
msgstr "手机号没有设置"
@@ -1818,7 +1824,7 @@ msgid "Show"
msgstr "显示"
#: 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/templates/users/user_verify_mfa.html:36
msgid "Disable"
@@ -3326,49 +3332,53 @@ msgstr "必须包含数字"
msgid "Must contain special"
msgstr "必须包含特殊字符"
#: settings/serializers/security.py:43
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
#: settings/serializers/security.py:31
msgid ""
"Unit: minute, If the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval."
msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
#: settings/serializers/security.py:61
msgid "Login IP Black List"
msgstr "登录 IP 黑名单"
#: settings/serializers/security.py:40
msgid "All users"
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
msgid ""
"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"
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 (支持网域)"
msgid "Login IP White List"
msgstr "IP 登录白名单"
#: 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"
msgstr "用户密码过期时间"
#: settings/serializers/security.py:72
#: settings/serializers/security.py:77
msgid ""
"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 "
@@ -3378,55 +3388,55 @@ msgstr ""
"单位:天, 如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件"
"将在密码过期前5天内由系统每天自动发送给用户"
#: settings/serializers/security.py:79
#: settings/serializers/security.py:84
msgid "Number of repeated historical passwords"
msgstr "不能设置近几次密码"
#: settings/serializers/security.py:81
#: settings/serializers/security.py:86
msgid ""
"Tip: When the user resets the password, it cannot be the previous n "
"historical passwords of the user"
msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
#: settings/serializers/security.py:86
#: settings/serializers/security.py:91
msgid "Only single device login"
msgstr "仅一台设备登录"
#: settings/serializers/security.py:87
#: settings/serializers/security.py:92
msgid "Next device login, pre login will be logout"
msgstr "下个设备登录,上次登录会被顶掉"
#: settings/serializers/security.py:90
#: settings/serializers/security.py:95
msgid "Only exist user login"
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"
msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:94
#: settings/serializers/security.py:99
msgid "Only from source login"
msgstr "仅从用户来源登录"
#: settings/serializers/security.py:95
#: settings/serializers/security.py:100
msgid "Only log in from the user source property"
msgstr "开启后如果用户来源为本地CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:99
#: settings/serializers/security.py:104
msgid "MFA verify TTL"
msgstr "MFA 校验有效期"
#: settings/serializers/security.py:101
#: settings/serializers/security.py:106
msgid ""
"Unit: second, The verification MFA takes effect only when you view the "
"account password"
msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效"
#: settings/serializers/security.py:106
#: settings/serializers/security.py:111
msgid "Enable Login dynamic code"
msgstr "启用登录附加码"
#: settings/serializers/security.py:107
#: settings/serializers/security.py:112
msgid ""
"The password and additional code are sent to a third party authentication "
"system for verification"
@@ -3434,96 +3444,96 @@ msgstr ""
"密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密"
"码+6位数字 完成认证"
#: settings/serializers/security.py:112
#: settings/serializers/security.py:117
msgid "MFA in login page"
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"
msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全"
#: settings/serializers/security.py:116
#: settings/serializers/security.py:121
msgid "Enable Login captcha"
msgstr "启用登录验证码"
#: settings/serializers/security.py:117
#: settings/serializers/security.py:122
msgid "Enable captcha to prevent robot authentication"
msgstr "开启验证码,防止机器人登录"
#: settings/serializers/security.py:137
#: settings/serializers/security.py:142
msgid "Enable terminal register"
msgstr "终端注册"
#: settings/serializers/security.py:139
#: settings/serializers/security.py:144
msgid ""
"Allow terminal register, after all terminal setup, you should disable this "
"for security"
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
#: settings/serializers/security.py:143
#: settings/serializers/security.py:148
msgid "Enable watermark"
msgstr "开启水印"
#: settings/serializers/security.py:144
#: settings/serializers/security.py:149
msgid "Enabled, the web session and replay contains watermark information"
msgstr "启用后Web 会话和录像将包含水印信息"
#: settings/serializers/security.py:148
#: settings/serializers/security.py:153
msgid "Connection max idle time"
msgstr "连接最大空闲时间"
#: settings/serializers/security.py:149
#: settings/serializers/security.py:154
msgid "If idle time more than it, disconnect connection Unit: minute"
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
#: settings/serializers/security.py:152
#: settings/serializers/security.py:157
msgid "Remember manual auth"
msgstr "保存手动输入密码"
#: settings/serializers/security.py:155
#: settings/serializers/security.py:160
msgid "Enable change auth secure mode"
msgstr "启用改密安全模式"
#: settings/serializers/security.py:158
#: settings/serializers/security.py:163
msgid "Insecure command alert"
msgstr "危险命令告警"
#: settings/serializers/security.py:161
#: settings/serializers/security.py:166
msgid "Email recipient"
msgstr "邮件收件人"
#: settings/serializers/security.py:162
#: settings/serializers/security.py:167
msgid "Multiple user using , split"
msgstr "多个用户,使用 , 分割"
#: settings/serializers/security.py:165
#: settings/serializers/security.py:170
msgid "Batch command execution"
msgstr "批量命令执行"
#: settings/serializers/security.py:166
#: settings/serializers/security.py:171
msgid "Allow user run batch command or not using ansible"
msgstr "是否允许用户使用 ansible 执行批量命令"
#: settings/serializers/security.py:169
#: settings/serializers/security.py:174
msgid "Session share"
msgstr "会话分享"
#: settings/serializers/security.py:170
#: settings/serializers/security.py:175
msgid "Enabled, Allows user active session to be shared with other users"
msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作"
#: settings/serializers/security.py:173
#: settings/serializers/security.py:178
msgid "Remote Login Protection"
msgstr "异地登录保护"
#: settings/serializers/security.py:175
#: settings/serializers/security.py:180
msgid ""
"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 "
"a remote login reminder"
msgstr ""
"根据登录IP是否所属常用登录城市进行判断若账号在非常用城市登录会发送异地"
"录提醒"
"根据登录 IP 是否所属常用登录城市进行判断,若账号在非常用城市登录,会发送异地"
"录提醒"
#: settings/serializers/sms.py:7
msgid "Label"
@@ -3579,104 +3589,104 @@ msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389"
msgid "Enable XRDP"
msgstr "启用 XRDP 服务"
#: settings/utils/ldap.py:412
#: settings/utils/ldap.py:415
msgid "ldap:// or ldaps:// protocol is used."
msgstr "使用 ldap:// 或 ldaps:// 协议"
#: settings/utils/ldap.py:423
#: settings/utils/ldap.py:426
msgid "Host or port is disconnected: {}"
msgstr "主机或端口不可连接: {}"
#: settings/utils/ldap.py:425
#: settings/utils/ldap.py:428
msgid "The port is not the port of the LDAP service: {}"
msgstr "端口不是LDAP服务端口: {}"
#: settings/utils/ldap.py:427
#: settings/utils/ldap.py:430
msgid "Please add certificate: {}"
msgstr "请添加证书"
#: settings/utils/ldap.py:431 settings/utils/ldap.py:458
#: settings/utils/ldap.py:488 settings/utils/ldap.py:516
#: settings/utils/ldap.py:434 settings/utils/ldap.py:461
#: settings/utils/ldap.py:491 settings/utils/ldap.py:519
msgid "Unknown error: {}"
msgstr "未知错误: {}"
#: settings/utils/ldap.py:445
#: settings/utils/ldap.py:448
msgid "Bind DN or Password incorrect"
msgstr "绑定DN或密码错误"
#: settings/utils/ldap.py:452
#: settings/utils/ldap.py:455
msgid "Please enter Bind DN: {}"
msgstr "请输入绑定DN: {}"
#: settings/utils/ldap.py:454
#: settings/utils/ldap.py:457
msgid "Please enter Password: {}"
msgstr "请输入密码: {}"
#: settings/utils/ldap.py:456
#: settings/utils/ldap.py:459
msgid "Please enter correct Bind DN and Password: {}"
msgstr "请输入正确的绑定DN和密码: {}"
#: settings/utils/ldap.py:474
#: settings/utils/ldap.py:477
msgid "Invalid User OU or User search filter: {}"
msgstr "不合法的用户OU或用户过滤器: {}"
#: settings/utils/ldap.py:505
#: settings/utils/ldap.py:508
msgid "LDAP User attr map not include: {}"
msgstr "LDAP属性映射没有包含: {}"
#: settings/utils/ldap.py:512
#: settings/utils/ldap.py:515
msgid "LDAP User attr map is not dict"
msgstr "LDAP属性映射不合法"
#: settings/utils/ldap.py:531
#: settings/utils/ldap.py:534
msgid "LDAP authentication is not enabled"
msgstr "LDAP认证没有启用"
#: settings/utils/ldap.py:549
#: settings/utils/ldap.py:552
msgid "Error (Invalid LDAP server): {}"
msgstr "错误 不合法的LDAP服务器地址: {}"
#: settings/utils/ldap.py:551
#: settings/utils/ldap.py:554
msgid "Error (Invalid Bind DN): {}"
msgstr "错误不合法的绑定DN: {}"
#: settings/utils/ldap.py:553
#: settings/utils/ldap.py:556
msgid "Error (Invalid LDAP User attr map): {}"
msgstr "错误不合法的LDAP属性映射: {}"
#: settings/utils/ldap.py:555
#: settings/utils/ldap.py:558
msgid "Error (Invalid User OU or User search filter): {}"
msgstr "错误不合法的用户OU或用户过滤器: {}"
#: settings/utils/ldap.py:557
#: settings/utils/ldap.py:560
msgid "Error (Not enabled LDAP authentication): {}"
msgstr "错误没有启用LDAP认证: {}"
#: settings/utils/ldap.py:559
#: settings/utils/ldap.py:562
msgid "Error (Unknown): {}"
msgstr "错误(未知): {}"
#: settings/utils/ldap.py:562
#: settings/utils/ldap.py:565
msgid "Succeed: Match {} s user"
msgstr "成功匹配 {} 个用户"
#: settings/utils/ldap.py:595
#: settings/utils/ldap.py:598
msgid "Authentication failed (configuration incorrect): {}"
msgstr "认证失败(配置错误): {}"
#: settings/utils/ldap.py:597
#: settings/utils/ldap.py:600
msgid "Authentication failed (before login check failed): {}"
msgstr "认证失败(登录前检查失败): {}"
#: settings/utils/ldap.py:599
#: settings/utils/ldap.py:602
msgid "Authentication failed (username or password incorrect): {}"
msgstr "认证失败 (用户名或密码不正确): {}"
#: settings/utils/ldap.py:601
#: settings/utils/ldap.py:604
msgid "Authentication failed (Unknown): {}"
msgstr "认证失败: (未知): {}"
#: settings/utils/ldap.py:604
#: settings/utils/ldap.py:607
msgid "Authentication success: {}"
msgstr "认证成功: {}"
@@ -3827,11 +3837,11 @@ msgstr ""
msgid "Send verification code"
msgstr "发送验证码"
#: templates/_mfa_login_field.html:105
#: templates/_mfa_login_field.html:106
msgid "Wait: "
msgstr "等待:"
#: templates/_mfa_login_field.html:115
#: templates/_mfa_login_field.html:116
msgid "The verification code has been sent"
msgstr "验证码已发送"
@@ -6127,200 +6137,3 @@ msgstr "旗舰版"
msgid "Community edition"
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:
msgs = self.chan.listen()
# 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中
close_old_connections()
for message in msgs:
if message['type'] != 'message':
continue
close_old_connections()
try:
msg = json.loads(message['data'].decode())
except json.JSONDecoder as e:
@@ -70,6 +69,7 @@ class SiteMsgWebsocket(JsonWebsocketConsumer):
logger.debug('Message users: {}'.format(users))
if user_id in users:
self.send_unread_msg_count()
close_old_connections()
except ConnectionError:
logger.error('Redis chan closed')
finally:

View File

@@ -143,7 +143,7 @@ class AssetPermissionFilter(PermissionBaseFilter):
if not _nodes:
return queryset.none()
node = _nodes.get()
node = _nodes.first()
if not is_query_all:
queryset = queryset.filter(nodes=node)
@@ -170,13 +170,13 @@ class AssetPermissionFilter(PermissionBaseFilter):
return queryset
if not assets:
return queryset.none()
asset = assets.get()
assetids = list(assets.values_list('id', flat=True))
if not is_query_all:
queryset = queryset.filter(assets=asset)
queryset = queryset.filter(assets__in=assetids)
return queryset
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:
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 = list(inherit_all_nodeids)
qs1 = queryset.filter(assets=asset).distinct()
qs2 = queryset.filter(nodes__id__in=inherit_all_nodeids).distinct()
qs1 = queryset.filter(assets__in=assetids).distinct()
qs2 = queryset.filter(nodes__in=inherit_all_nodeids).distinct()
qs = UnionQuerySet(qs1, qs2)
return qs

View File

@@ -1,7 +1,7 @@
from django.utils.translation import ugettext_lazy as _
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):
@@ -27,13 +27,10 @@ class SecurityPasswordRuleSerializer(serializers.Serializer):
)
def ip_child_validator(ip_child):
is_valid = is_ip_address(ip_child) \
or is_ip_network(ip_child) \
or is_ip_segment(ip_child)
if not is_valid:
error = _('IP address invalid: `{}`').format(ip_child)
raise serializers.ValidationError(error)
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.'
)
class SecurityAuthSerializer(serializers.Serializer):
@@ -47,23 +44,31 @@ class SecurityAuthSerializer(serializers.Serializer):
)
SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(
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(
min_value=5, max_value=99999, required=True,
label=_('Block logon interval'),
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.'
)
label=_('Block user login interval'),
help_text=login_ip_limit_time_help_text
)
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(
default=[], label=_('Login IP Black List'), allow_empty=True,
child=serializers.CharField(max_length=1024, validators=[ip_child_validator]),
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'
)
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]),
help_text=ip_group_help_text
)
SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(
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()
msgs = sub.listen()
# 开始之前关闭连接因为server端可能关闭了连接而 client 还在 CONN_MAX_AGE 中
close_old_connections()
for msg in msgs:
if msg["type"] != "message":
continue
close_old_connections()
item = msg['data'].decode()
logger.debug("Found setting change: {}".format(str(item)))
Setting.refresh_item(item)
close_old_connections()
except Exception as e:
logger.exception(f'subscribe_settings_change: {e}')
Setting.refresh_all_settings()
finally:
close_old_connections()
t = threading.Thread(target=keep_subscribe_settings_change)

View File

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

View File

@@ -207,10 +207,10 @@ class UserResetMFAApi(UserQuerysetMixin, generics.RetrieveAPIView):
user = self.get_object() if kwargs.get('pk') else request.user
if user == request.user:
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
for backend in backends:
for backend in backends.values():
if backend.can_disable():
backend.disable()

View File

@@ -7,36 +7,8 @@
{% endblock %}
{% 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: auto; margin-top: 10px;">
<form id="verify-form" class="" role="form" method="post" action="">
{% csrf_token %}
<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>
<hr style="width: 500px; margin: 10px auto auto;">
<a type="submit" class="next" href="{% url 'authentication:user-otp-enable-install-app' %}" >
{% trans 'Next' %}
</a>
{% endblock %}

View File

@@ -16,12 +16,12 @@
<div id="qr_code"></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 %}
<div class="form-input">
<input type="text" class="" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
</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 %}
<p style="color: #ed5565">{{ form.otp_code.errors.as_text }}</p>
{% endif %}
@@ -33,6 +33,10 @@
$('.change-color li:eq(1) i').css('color', '#1ab394');
$('.change-color li:eq(2) i').css('color', '#1ab394');
function submitForm() {
$('#bind-form').submit()
}
$(document).ready(function() {
// 生成用户绑定otp的二维码
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 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
logger = logging.getLogger('jumpserver')
@@ -180,33 +180,37 @@ class BlockGlobalIpUtilBase:
self.ip = ip
self.limit_key = self.LIMIT_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
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):
if not self.ip_in_black_list:
if self.ip_in_white_list or self.ip_in_black_list:
return
count = cache.get(self.limit_key, 0)
count += 1
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:
return
cache.set(self.block_key, True, self.key_ttl)
def clean_block_if_need(self):
if not self.ip_in_black_list:
return
cache.delete(self.limit_key)
cache.delete(self.block_key)
def is_block(self):
if not self.ip_in_black_list:
if self.ip_in_white_list:
return False
if self.ip_in_black_list:
return True
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.edit import FormView
from django.contrib.auth import logout as auth_logout
from django.shortcuts import redirect
from django.http.response import HttpResponseRedirect
from authentication.mixins import AuthMixin
from authentication.mfa import MFAOtp, otp_failed_msg
from authentication.errors import SessionEmptyError
from common.utils import get_logger, FlashMessageUtil
from common.mixins.views import PermissionsMixin
from common.permissions import IsValidUser
@@ -30,11 +32,16 @@ __all__ = [
logger = get_logger(__name__)
class UserOtpEnableStartView(UserVerifyPasswordView):
class UserOtpEnableStartView(AuthMixin, TemplateView):
template_name = 'users/user_otp_check_password.html'
def get_success_url(self):
return reverse('authentication:user-otp-enable-install-app')
def get(self, request, *args, **kwargs):
try:
self.get_user_from_session()
except SessionEmptyError:
url = reverse('authentication:login') + '?_=otp_enable_start'
return redirect(url)
return super().get(request, *args, **kwargs)
class UserOtpEnableInstallAppView(TemplateView):
@@ -66,8 +73,8 @@ class UserOtpEnableBindView(AuthMixin, TemplateView, FormView):
def _pre_check_can_bind(self):
try:
user = self.get_user_from_session()
except:
verify_url = reverse('authentication:user-otp-enable-start')
except Exception as e:
verify_url = reverse('authentication:user-otp-enable-start') + f'?e={e}'
return HttpResponseRedirect(verify_url)
if user.otp_secret_key:

View File

@@ -95,7 +95,7 @@ python-cas==1.5.0
ipython
huaweicloud-sdk-python==1.0.21
django-redis==4.11.0
python-redis-lock==3.5.0
python-redis-lock==3.7.0
jumpserver-django-oidc-rp==0.3.7.8
django-mysql==3.9.0
gmssl==3.2.1