mirror of https://github.com/jumpserver/jumpserver
feat: 首页的 chanlege 和 MFA 统一 (#6989)
* feat: 首页的 chanlege 和 MFA 统一 * 登陆样式调整 * mfa bug * q * m * mfa封装组件 前端可修改配置 * perf: 添加翻译 * login css bug * perf: 修改一些风格 * perf: 修改命名 * perf: 修改 mfa code 不是必填 * mfa 前端统一组件 * stash * perf: 统一验证码 Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: ibuler <ibuler@qq.com>pull/7027/head
parent
fa68389028
commit
63638ed1ce
|
@ -2,17 +2,17 @@
|
||||||
#
|
#
|
||||||
import builtins
|
import builtins
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from authentication.sms_verify_code import VerifyCodeUtil
|
from common.permissions import IsValidUser, NeedMFAVerify
|
||||||
from common.exceptions import JMSException
|
from users.models.user import MFAType, User
|
||||||
from common.permissions import IsValidUser, NeedMFAVerify, IsAppUser
|
|
||||||
from users.models.user import MFAType
|
|
||||||
from ..serializers import OtpVerifySerializer
|
from ..serializers import OtpVerifySerializer
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from .. import errors
|
from .. import errors
|
||||||
|
@ -90,6 +90,13 @@ class SendSMSVerifyCodeApi(AuthMixin, CreateAPIView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
user = self.get_user_from_session()
|
username = request.data.get('username', '')
|
||||||
|
username = username.strip()
|
||||||
|
if username:
|
||||||
|
user = get_object_or_404(User, username=username)
|
||||||
|
else:
|
||||||
|
user = self.get_user_from_session()
|
||||||
|
if not user.mfa_enabled:
|
||||||
|
raise errors.NotEnableMFAError
|
||||||
timeout = user.send_sms_code()
|
timeout = user.send_sms_code()
|
||||||
return Response({'code': 'ok','timeout': timeout})
|
return Response({'code': 'ok', 'timeout': timeout})
|
||||||
|
|
|
@ -78,6 +78,7 @@ mfa_type_failed_msg = _(
|
||||||
|
|
||||||
mfa_required_msg = _("MFA required")
|
mfa_required_msg = _("MFA required")
|
||||||
mfa_unset_msg = _("MFA not set, please set it first")
|
mfa_unset_msg = _("MFA not set, please set it first")
|
||||||
|
otp_unset_msg = _("OTP not set, please set it first")
|
||||||
login_confirm_required_msg = _("Login confirm required")
|
login_confirm_required_msg = _("Login confirm required")
|
||||||
login_confirm_wait_msg = _("Wait login confirm ticket for accept")
|
login_confirm_wait_msg = _("Wait login confirm ticket for accept")
|
||||||
login_confirm_error_msg = _("Login confirm ticket was {}")
|
login_confirm_error_msg = _("Login confirm ticket was {}")
|
||||||
|
@ -354,3 +355,15 @@ class NotHaveUpDownLoadPerm(JMSException):
|
||||||
status_code = status.HTTP_403_FORBIDDEN
|
status_code = status.HTTP_403_FORBIDDEN
|
||||||
code = 'not_have_up_down_load_perm'
|
code = 'not_have_up_down_load_perm'
|
||||||
default_detail = _('No upload or download permission')
|
default_detail = _('No upload or download permission')
|
||||||
|
|
||||||
|
|
||||||
|
class NotEnableMFAError(JMSException):
|
||||||
|
default_detail = mfa_unset_msg
|
||||||
|
|
||||||
|
|
||||||
|
class OTPRequiredError(JMSException):
|
||||||
|
default_detail = otp_unset_msg
|
||||||
|
|
||||||
|
def __init__(self, url, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.url = url
|
||||||
|
|
|
@ -43,7 +43,7 @@ class UserLoginForm(forms.Form):
|
||||||
|
|
||||||
|
|
||||||
class UserCheckOtpCodeForm(forms.Form):
|
class UserCheckOtpCodeForm(forms.Form):
|
||||||
code = forms.CharField(label=_('MFA Code'), max_length=6)
|
code = forms.CharField(label=_('MFA Code'), max_length=6, required=False)
|
||||||
mfa_type = forms.CharField(label=_('MFA type'), max_length=6)
|
mfa_type = forms.CharField(label=_('MFA type'), max_length=6)
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class ChallengeMixin(forms.Form):
|
||||||
challenge = forms.CharField(
|
challenge = forms.CharField(
|
||||||
label=_('MFA code'), max_length=6, required=False,
|
label=_('MFA code'), max_length=6, required=False,
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(attrs={
|
||||||
'placeholder': _("MFA code"),
|
'placeholder': _("Dynamic code"),
|
||||||
'style': 'width: 50%'
|
'style': 'width: 50%'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -69,6 +69,8 @@ def get_user_login_form_cls(*, captcha=False):
|
||||||
bases = []
|
bases = []
|
||||||
if settings.SECURITY_LOGIN_CHALLENGE_ENABLED:
|
if settings.SECURITY_LOGIN_CHALLENGE_ENABLED:
|
||||||
bases.append(ChallengeMixin)
|
bases.append(ChallengeMixin)
|
||||||
|
elif settings.SECURITY_MFA_IN_LOGIN_PAGE:
|
||||||
|
bases.append(UserCheckOtpCodeForm)
|
||||||
elif settings.SECURITY_LOGIN_CAPTCHA_ENABLED and captcha:
|
elif settings.SECURITY_LOGIN_CAPTCHA_ENABLED and captcha:
|
||||||
bases.append(CaptchaMixin)
|
bases.append(CaptchaMixin)
|
||||||
bases.append(UserLoginForm)
|
bases.append(UserLoginForm)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import time
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.contrib.auth import (
|
from django.contrib.auth import (
|
||||||
|
@ -14,11 +15,11 @@ from django.contrib.auth import (
|
||||||
PermissionDenied, user_login_failed, _clean_credentials
|
PermissionDenied, user_login_failed, _clean_credentials
|
||||||
)
|
)
|
||||||
from django.shortcuts import reverse, redirect
|
from django.shortcuts import reverse, redirect
|
||||||
from django.views.generic.edit import FormView
|
|
||||||
|
|
||||||
from common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get, FlashMessageUtil
|
from common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get, FlashMessageUtil
|
||||||
from users.models import User, MFAType
|
from users.models import User, MFAType
|
||||||
from users.utils import LoginBlockUtil, MFABlockUtils
|
from users.utils import LoginBlockUtil, MFABlockUtils
|
||||||
|
from users.exceptions import MFANotEnabled
|
||||||
from . import errors
|
from . import errors
|
||||||
from .utils import rsa_decrypt, gen_key_pair
|
from .utils import rsa_decrypt, gen_key_pair
|
||||||
from .signals import post_auth_success, post_auth_failed
|
from .signals import post_auth_success, post_auth_failed
|
||||||
|
@ -208,18 +209,20 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
||||||
ip = self.get_request_ip()
|
ip = self.get_request_ip()
|
||||||
self._set_partial_credential_error(username=username, ip=ip, request=request)
|
self._set_partial_credential_error(username=username, ip=ip, request=request)
|
||||||
|
|
||||||
password = password + challenge.strip()
|
|
||||||
if decrypt_passwd:
|
if decrypt_passwd:
|
||||||
password = self.get_decrypted_password()
|
password = self.get_decrypted_password()
|
||||||
|
password = password + challenge.strip()
|
||||||
return username, password, public_key, ip, auto_login
|
return username, password, public_key, ip, auto_login
|
||||||
|
|
||||||
def _check_only_allow_exists_user_auth(self, username):
|
def _check_only_allow_exists_user_auth(self, username):
|
||||||
# 仅允许预先存在的用户认证
|
# 仅允许预先存在的用户认证
|
||||||
if settings.ONLY_ALLOW_EXIST_USER_AUTH:
|
if not settings.ONLY_ALLOW_EXIST_USER_AUTH:
|
||||||
exist = User.objects.filter(username=username).exists()
|
return
|
||||||
if not exist:
|
|
||||||
logger.error(f"Only allow exist user auth, login failed: {username}")
|
exist = User.objects.filter(username=username).exists()
|
||||||
self.raise_credential_error(errors.reason_user_not_exist)
|
if not exist:
|
||||||
|
logger.error(f"Only allow exist user auth, login failed: {username}")
|
||||||
|
self.raise_credential_error(errors.reason_user_not_exist)
|
||||||
|
|
||||||
def _check_auth_user_is_valid(self, username, password, public_key):
|
def _check_auth_user_is_valid(self, username, password, public_key):
|
||||||
user = authenticate(self.request, username=username, password=password, public_key=public_key)
|
user = authenticate(self.request, username=username, password=password, public_key=public_key)
|
||||||
|
@ -231,6 +234,17 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
||||||
self.raise_credential_error(errors.reason_user_inactive)
|
self.raise_credential_error(errors.reason_user_inactive)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def _check_login_mfa_login_if_need(self, user):
|
||||||
|
request = self.request
|
||||||
|
if hasattr(request, 'data'):
|
||||||
|
data = request.data
|
||||||
|
else:
|
||||||
|
data = request.POST
|
||||||
|
code = data.get('code')
|
||||||
|
mfa_type = data.get('mfa_type')
|
||||||
|
if settings.SECURITY_MFA_IN_LOGIN_PAGE and code and mfa_type:
|
||||||
|
self.check_user_mfa(code, mfa_type, user=user)
|
||||||
|
|
||||||
def _check_login_acl(self, user, ip):
|
def _check_login_acl(self, user, ip):
|
||||||
# ACL 限制用户登录
|
# ACL 限制用户登录
|
||||||
from acls.models import LoginACL
|
from acls.models import LoginACL
|
||||||
|
@ -255,8 +269,7 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
||||||
|
|
||||||
def check_user_auth(self, decrypt_passwd=False):
|
def check_user_auth(self, decrypt_passwd=False):
|
||||||
self.check_is_block()
|
self.check_is_block()
|
||||||
request = self.request
|
username, password, public_key, ip, auto_login = self.get_auth_data(decrypt_passwd)
|
||||||
username, password, public_key, ip, auto_login = self.get_auth_data(decrypt_passwd=decrypt_passwd)
|
|
||||||
|
|
||||||
self._check_only_allow_exists_user_auth(username)
|
self._check_only_allow_exists_user_auth(username)
|
||||||
user = self._check_auth_user_is_valid(username, password, public_key)
|
user = self._check_auth_user_is_valid(username, password, public_key)
|
||||||
|
@ -266,7 +279,11 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
||||||
self._check_passwd_is_too_simple(user, password)
|
self._check_passwd_is_too_simple(user, password)
|
||||||
self._check_passwd_need_update(user)
|
self._check_passwd_need_update(user)
|
||||||
|
|
||||||
|
# 校验login-mfa, 如果登录页面上显示 mfa 的话
|
||||||
|
self._check_login_mfa_login_if_need(user)
|
||||||
|
|
||||||
LoginBlockUtil(username, ip).clean_failed_count()
|
LoginBlockUtil(username, ip).clean_failed_count()
|
||||||
|
request = self.request
|
||||||
request.session['auth_password'] = 1
|
request.session['auth_password'] = 1
|
||||||
request.session['user_id'] = str(user.id)
|
request.session['user_id'] = str(user.id)
|
||||||
request.session['auto_login'] = auto_login
|
request.session['auto_login'] = auto_login
|
||||||
|
@ -348,12 +365,11 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
||||||
def check_user_mfa_if_need(self, user):
|
def check_user_mfa_if_need(self, user):
|
||||||
if self.request.session.get('auth_mfa'):
|
if self.request.session.get('auth_mfa'):
|
||||||
return
|
return
|
||||||
|
|
||||||
if settings.OTP_IN_RADIUS:
|
if settings.OTP_IN_RADIUS:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not user.mfa_enabled:
|
if not user.mfa_enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
unset, url = user.mfa_enabled_but_not_set()
|
unset, url = user.mfa_enabled_but_not_set()
|
||||||
if unset:
|
if unset:
|
||||||
raise errors.MFAUnsetError(user, self.request, url)
|
raise errors.MFAUnsetError(user, self.request, url)
|
||||||
|
@ -372,19 +388,29 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
||||||
self.request.session['auth_mfa_type'] = ''
|
self.request.session['auth_mfa_type'] = ''
|
||||||
|
|
||||||
def check_mfa_is_block(self, username, ip, raise_exception=True):
|
def check_mfa_is_block(self, username, ip, raise_exception=True):
|
||||||
if MFABlockUtils(username, ip).is_block():
|
blocked = MFABlockUtils(username, ip).is_block()
|
||||||
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
|
if not blocked:
|
||||||
exception = errors.BlockMFAError(username=username, request=self.request, ip=ip)
|
return
|
||||||
if raise_exception:
|
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
|
||||||
raise exception
|
exception = errors.BlockMFAError(username=username, request=self.request, ip=ip)
|
||||||
else:
|
if raise_exception:
|
||||||
return exception
|
raise exception
|
||||||
|
else:
|
||||||
|
return exception
|
||||||
|
|
||||||
|
def check_user_mfa(self, code, mfa_type=MFAType.OTP, user=None):
|
||||||
|
user = user if user else self.get_user_from_session()
|
||||||
|
if not user.mfa_enabled:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not (bool(user.otp_secret_key) and mfa_type == MFAType.OTP):
|
||||||
|
self.set_passwd_verify_on_session(user)
|
||||||
|
raise errors.OTPRequiredError(reverse_lazy('authentication:user-otp-enable-bind'))
|
||||||
|
|
||||||
def check_user_mfa(self, code, mfa_type=MFAType.OTP):
|
|
||||||
user = self.get_user_from_session()
|
|
||||||
ip = self.get_request_ip()
|
ip = self.get_request_ip()
|
||||||
self.check_mfa_is_block(user.username, ip)
|
self.check_mfa_is_block(user.username, ip)
|
||||||
ok = user.check_mfa(code, mfa_type=mfa_type)
|
ok = user.check_mfa(code, mfa_type=mfa_type)
|
||||||
|
|
||||||
if ok:
|
if ok:
|
||||||
self.mark_mfa_ok()
|
self.mark_mfa_ok()
|
||||||
return
|
return
|
||||||
|
@ -468,3 +494,31 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
||||||
if args:
|
if args:
|
||||||
guard_url = "%s?%s" % (guard_url, args)
|
guard_url = "%s?%s" % (guard_url, args)
|
||||||
return redirect(guard_url)
|
return redirect(guard_url)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_user_mfa_methods(user=None):
|
||||||
|
otp_enabled = user.otp_secret_key if user else True
|
||||||
|
# 没有用户时,或者有用户并且有电话配置
|
||||||
|
sms_enabled = any([user and user.phone, not user]) \
|
||||||
|
and settings.SMS_ENABLED and settings.XPACK_ENABLED
|
||||||
|
|
||||||
|
methods = [
|
||||||
|
{
|
||||||
|
'name': 'otp',
|
||||||
|
'label': 'MFA',
|
||||||
|
'enable': otp_enabled,
|
||||||
|
'selected': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'sms',
|
||||||
|
'label': _('SMS'),
|
||||||
|
'enable': sms_enabled,
|
||||||
|
'selected': False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for item in methods:
|
||||||
|
if item['enable']:
|
||||||
|
item['selected'] = True
|
||||||
|
break
|
||||||
|
return methods
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _, ugettext as __
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.message.backends.sms.alibaba import AlibabaSMS
|
|
||||||
from common.message.backends.sms import SMS
|
from common.message.backends.sms import SMS
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.exceptions import JMSException
|
from common.exceptions import JMSException
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
{% if audio %}
|
{% if audio %}
|
||||||
<a title="{% trans "Play CAPTCHA as audio file" %}" href="{{ audio }}">
|
<a title="{% trans "Play CAPTCHA as audio file" %}" href="{{ audio }}"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include "django/forms/widgets/multiwidget.html" %}
|
{% include "django/forms/widgets/multiwidget.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,23 +10,15 @@
|
||||||
{{ JMS_TITLE }}
|
{{ JMS_TITLE }}
|
||||||
</title>
|
</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
{% include '_head_css_js.html' %}
|
||||||
<!-- Stylesheets -->
|
<!-- Stylesheets -->
|
||||||
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
|
|
||||||
<link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet">
|
|
||||||
<link href="{% static 'css/bootstrap-style.css' %}" rel="stylesheet">
|
|
||||||
<link href="{% static 'css/login-style.css' %}" rel="stylesheet">
|
<link href="{% static 'css/login-style.css' %}" rel="stylesheet">
|
||||||
<link href="{% static 'css/style.css' %}" rel="stylesheet">
|
|
||||||
<link href="{% static 'css/jumpserver.css' %}" rel="stylesheet">
|
<link href="{% static 'css/jumpserver.css' %}" rel="stylesheet">
|
||||||
|
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||||
<!-- scripts -->
|
|
||||||
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
|
|
||||||
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
|
||||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
|
||||||
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.login-content {
|
.login-content {
|
||||||
box-shadow: 0 5px 5px -3px rgb(0 0 0 / 20%), 0 8px 10px 1px rgb(0 0 0 / 14%), 0 3px 14px 2px rgb(0 0 0 / 12%);
|
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.15), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-block {
|
.help-block {
|
||||||
|
@ -49,17 +41,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-content {
|
.login-content {
|
||||||
height: 472px;
|
height: 490px;
|
||||||
width: 984px;
|
width: 1066px;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-top: calc((100vh - 470px) / 3);
|
margin-top: calc((100vh - 470px) / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #f2f2f2;
|
background-color: #f2f2f2;
|
||||||
height: calc(100vh - (100vh - 470px) / 3);
|
height: calc(100vh - (100vh - 470px) / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.captcha {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.right-image-box {
|
.right-image-box {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
@ -73,18 +70,10 @@
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.captcha {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red-fonts {
|
.red-fonts {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-error {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.has-error {
|
.form-group.has-error {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
@ -109,14 +98,6 @@
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio, .checkbox {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#github_star {
|
|
||||||
float: right;
|
|
||||||
margin: 10px 10px 0 0;
|
|
||||||
}
|
|
||||||
.more-login-item {
|
.more-login-item {
|
||||||
border-right: 1px dashed #dedede;
|
border-right: 1px dashed #dedede;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
|
@ -127,11 +108,18 @@
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-con {
|
||||||
|
width: 22%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfa-div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="login-content ">
|
<div class="login-content">
|
||||||
<div class="right-image-box">
|
<div class="right-image-box">
|
||||||
<a href="{% if not XPACK_ENABLED %}https://github.com/jumpserver/jumpserver{% endif %}">
|
<a href="{% if not XPACK_ENABLED %}https://github.com/jumpserver/jumpserver{% endif %}">
|
||||||
<img src="{{ LOGIN_IMAGE_URL }}" style="height: 100%; width: 100%"/>
|
<img src="{{ LOGIN_IMAGE_URL }}" style="height: 100%; width: 100%"/>
|
||||||
|
@ -170,6 +158,10 @@
|
||||||
</div>
|
</div>
|
||||||
{% if form.challenge %}
|
{% if form.challenge %}
|
||||||
{% bootstrap_field form.challenge show_label=False %}
|
{% bootstrap_field form.challenge show_label=False %}
|
||||||
|
{% elif form.mfa_type %}
|
||||||
|
<div class="form-group" style="display: flex">
|
||||||
|
{% include '_mfa_otp_login.html' %}
|
||||||
|
</div>
|
||||||
{% elif form.captcha %}
|
{% elif form.captcha %}
|
||||||
<div class="captch-field">
|
<div class="captch-field">
|
||||||
{% bootstrap_field form.captcha show_label=False %}
|
{% bootstrap_field form.captcha show_label=False %}
|
||||||
|
@ -233,7 +225,7 @@
|
||||||
var password = $('#password').val(); //明文密码
|
var password = $('#password').val(); //明文密码
|
||||||
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
|
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
|
||||||
$('#password-hidden').val(passwordEncrypted); //返回给密码输入input
|
$('#password-hidden').val(passwordEncrypted); //返回给密码输入input
|
||||||
$('#login-form').submit();//post提交
|
$('#login-form').submit(); //post提交
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
|
@ -13,78 +13,19 @@
|
||||||
<p class="red-fonts">{{ form.code.errors.as_text }}</p>
|
<p class="red-fonts">{{ form.code.errors.as_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<select id="verify-method-select" name="mfa_type" class="form-control" onchange="select_change(this.value)">
|
{% include '_mfa_otp_login.html' %}
|
||||||
{% for method in methods %}
|
|
||||||
<option value="{{ method.name }}" {% if method.selected %} selected {% endif %} {% if not method.enable %} disabled {% endif %}>{{ method.label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="display: flex">
|
<button id='submit_button' type="submit"
|
||||||
|
class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||||
<input id="mfa-code" required type="text" class="form-control" name="code" placeholder="{% trans 'Please enter the verification code' %}" autofocus="autofocus">
|
|
||||||
<button id='send-sms-verify-code' type="button" class="btn btn-info full-width m-b" onclick="sendSMSVerifyCode()" style="width: 150px!important;">{% trans 'Send verification code' %}</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id='submit_button' type="submit" class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
|
||||||
<div>
|
<div>
|
||||||
<small>{% trans "Can't provide security? Please contact the administrator!" %}</small>
|
<small>{% trans "Can't provide security? Please contact the administrator!" %}</small>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.disabledBtn {
|
.mfa-div {
|
||||||
background: #e6e4e4!important;
|
margin-top: 15px;
|
||||||
border-color: #d8d5d5!important;
|
}
|
||||||
color: #949191!important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var methodSelect = document.getElementById('verify-method-select');
|
|
||||||
if (methodSelect.value !== null) {
|
|
||||||
select_change(methodSelect.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function select_change(type){
|
|
||||||
var currentBtn = document.getElementById('send-sms-verify-code');
|
|
||||||
|
|
||||||
if (type == "sms") {
|
|
||||||
currentBtn.style.display = "block";
|
|
||||||
currentBtn.disabled = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
currentBtn.style.display = "none";
|
|
||||||
currentBtn.disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function sendSMSVerifyCode(){
|
|
||||||
var currentBtn = document.getElementById('send-sms-verify-code');
|
|
||||||
var time = 60
|
|
||||||
var url = "{% url 'api-auth:sms-verify-code-send' %}";
|
|
||||||
requestApi({
|
|
||||||
url: url,
|
|
||||||
method: "POST",
|
|
||||||
success: function (data) {
|
|
||||||
currentBtn.innerHTML = `{% trans 'Wait: ' %} ${time}`;
|
|
||||||
currentBtn.disabled = true
|
|
||||||
currentBtn.classList.add("disabledBtn" )
|
|
||||||
var TimeInterval = setInterval(()=>{
|
|
||||||
--time
|
|
||||||
currentBtn.innerHTML = `{% trans 'Wait: ' %} ${time}`;
|
|
||||||
if(time === 0) {
|
|
||||||
currentBtn.innerHTML = "{% trans 'Send verification code' %}"
|
|
||||||
currentBtn.disabled = false
|
|
||||||
currentBtn.classList.remove("disabledBtn")
|
|
||||||
clearInterval(TimeInterval)
|
|
||||||
}
|
|
||||||
},1000)
|
|
||||||
alert("{% trans 'The verification code has been sent' %}");
|
|
||||||
},
|
|
||||||
error: function (text, data) {
|
|
||||||
alert(data.detail)
|
|
||||||
},
|
|
||||||
flash_message: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -13,7 +13,6 @@ router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('token/', api.UserToken.as_view(), name='user-token'),
|
|
||||||
path('wecom/qr/unbind/', api.WeComQRUnBindForUserApi.as_view(), name='wecom-qr-unbind'),
|
path('wecom/qr/unbind/', api.WeComQRUnBindForUserApi.as_view(), name='wecom-qr-unbind'),
|
||||||
path('wecom/qr/unbind/<uuid:user_id>/', api.WeComQRUnBindForAdminApi.as_view(), name='wecom-qr-unbind-for-admin'),
|
path('wecom/qr/unbind/<uuid:user_id>/', api.WeComQRUnBindForAdminApi.as_view(), name='wecom-qr-unbind-for-admin'),
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ from ..const import RSA_PRIVATE_KEY, RSA_PUBLIC_KEY
|
||||||
from .. import mixins, errors
|
from .. import mixins, errors
|
||||||
from ..forms import get_user_login_form_cls
|
from ..forms import get_user_login_form_cls
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserLoginView', 'UserLogoutView',
|
'UserLoginView', 'UserLogoutView',
|
||||||
'UserLoginGuardView', 'UserLoginWaitConfirmView',
|
'UserLoginGuardView', 'UserLoginWaitConfirmView',
|
||||||
|
@ -117,7 +116,6 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
||||||
except errors.AuthFailedError as e:
|
except errors.AuthFailedError as e:
|
||||||
form.add_error(None, e.msg)
|
form.add_error(None, e.msg)
|
||||||
self.set_login_failed_mark()
|
self.set_login_failed_mark()
|
||||||
|
|
||||||
form_cls = get_user_login_form_cls(captcha=True)
|
form_cls = get_user_login_form_cls(captcha=True)
|
||||||
new_form = form_cls(data=form.data)
|
new_form = form_cls(data=form.data)
|
||||||
new_form._errors = form.errors
|
new_form._errors = form.errors
|
||||||
|
@ -125,11 +123,20 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
||||||
self.request.session.set_test_cookie()
|
self.request.session.set_test_cookie()
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
except (
|
except (
|
||||||
errors.PasswdTooSimple,
|
errors.PasswdTooSimple,
|
||||||
errors.PasswordRequireResetError,
|
errors.PasswordRequireResetError,
|
||||||
errors.PasswdNeedUpdate
|
errors.PasswdNeedUpdate
|
||||||
) as e:
|
) as e:
|
||||||
return redirect(e.url)
|
return redirect(e.url)
|
||||||
|
except (
|
||||||
|
errors.MFAUnsetError,
|
||||||
|
errors.MFAFailedError,
|
||||||
|
errors.BlockMFAError
|
||||||
|
) as e:
|
||||||
|
form.add_error('code', e.msg)
|
||||||
|
return super().form_invalid(form)
|
||||||
|
except errors.OTPRequiredError as e:
|
||||||
|
return redirect(e.url)
|
||||||
self.clear_rsa_key()
|
self.clear_rsa_key()
|
||||||
return self.redirect_to_guard_view()
|
return self.redirect_to_guard_view()
|
||||||
|
|
||||||
|
@ -150,31 +157,31 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
||||||
'name': 'OpenID',
|
'name': 'OpenID',
|
||||||
'enabled': settings.AUTH_OPENID,
|
'enabled': settings.AUTH_OPENID,
|
||||||
'url': reverse('authentication:openid:login'),
|
'url': reverse('authentication:openid:login'),
|
||||||
'logo': static('img/login_oidc_logo.png')
|
'logo': static('img/login_oidc_logo.png')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CAS',
|
'name': 'CAS',
|
||||||
'enabled': settings.AUTH_CAS,
|
'enabled': settings.AUTH_CAS,
|
||||||
'url': reverse('authentication:cas:cas-login'),
|
'url': reverse('authentication:cas:cas-login'),
|
||||||
'logo': static('img/login_cas_logo.png')
|
'logo': static('img/login_cas_logo.png')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('WeCom'),
|
'name': _('WeCom'),
|
||||||
'enabled': settings.AUTH_WECOM,
|
'enabled': settings.AUTH_WECOM,
|
||||||
'url': reverse('authentication:wecom-qr-login'),
|
'url': reverse('authentication:wecom-qr-login'),
|
||||||
'logo': static('img/login_wecom_logo.png')
|
'logo': static('img/login_wecom_logo.png')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('DingTalk'),
|
'name': _('DingTalk'),
|
||||||
'enabled': settings.AUTH_DINGTALK,
|
'enabled': settings.AUTH_DINGTALK,
|
||||||
'url': reverse('authentication:dingtalk-qr-login'),
|
'url': reverse('authentication:dingtalk-qr-login'),
|
||||||
'logo': static('img/login_dingtalk_logo.png')
|
'logo': static('img/login_dingtalk_logo.png')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('FeiShu'),
|
'name': _('FeiShu'),
|
||||||
'enabled': settings.AUTH_FEISHU,
|
'enabled': settings.AUTH_FEISHU,
|
||||||
'url': reverse('authentication:feishu-qr-login'),
|
'url': reverse('authentication:feishu-qr-login'),
|
||||||
'logo': static('img/login_feishu_logo.png')
|
'logo': static('img/login_feishu_logo.png')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
return [method for method in auth_methods if method['enabled']]
|
return [method for method in auth_methods if method['enabled']]
|
||||||
|
@ -191,7 +198,8 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
||||||
context = {
|
context = {
|
||||||
'demo_mode': os.environ.get("DEMO_MODE"),
|
'demo_mode': os.environ.get("DEMO_MODE"),
|
||||||
'auth_methods': self.get_support_auth_methods(),
|
'auth_methods': self.get_support_auth_methods(),
|
||||||
'forgot_password_url': self.get_forgot_password_url()
|
'forgot_password_url': self.get_forgot_password_url(),
|
||||||
|
'methods': self.get_user_mfa_methods(),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -20,11 +20,11 @@ class UserLoginOtpView(mixins.AuthMixin, FormView):
|
||||||
redirect_field_name = 'next'
|
redirect_field_name = 'next'
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
otp_code = form.cleaned_data.get('code')
|
code = form.cleaned_data.get('code')
|
||||||
mfa_type = form.cleaned_data.get('mfa_type')
|
mfa_type = form.cleaned_data.get('mfa_type')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.check_user_mfa(otp_code, mfa_type)
|
self.check_user_mfa(code, mfa_type)
|
||||||
return redirect_to_guard_view()
|
return redirect_to_guard_view()
|
||||||
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)
|
||||||
|
@ -37,26 +37,6 @@ class UserLoginOtpView(mixins.AuthMixin, FormView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
user = self.get_user_from_session()
|
user = self.get_user_from_session()
|
||||||
context = {
|
methods = self.get_user_mfa_methods(user)
|
||||||
'methods': [
|
kwargs.update({'methods': methods})
|
||||||
{
|
return kwargs
|
||||||
'name': 'otp',
|
|
||||||
'label': _('One-time password'),
|
|
||||||
'enable': bool(user.otp_secret_key),
|
|
||||||
'selected': False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'sms',
|
|
||||||
'label': _('SMS'),
|
|
||||||
'enable': bool(user.phone) and settings.SMS_ENABLED and settings.XPACK_ENABLED,
|
|
||||||
'selected': False,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
for item in context['methods']:
|
|
||||||
if item['enable']:
|
|
||||||
item['selected'] = True
|
|
||||||
break
|
|
||||||
context.update(kwargs)
|
|
||||||
return context
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
@ -295,6 +295,7 @@ class Config(dict):
|
||||||
'SECURITY_PASSWORD_LOWER_CASE': False,
|
'SECURITY_PASSWORD_LOWER_CASE': False,
|
||||||
'SECURITY_PASSWORD_NUMBER': False,
|
'SECURITY_PASSWORD_NUMBER': False,
|
||||||
'SECURITY_PASSWORD_SPECIAL_CHAR': False,
|
'SECURITY_PASSWORD_SPECIAL_CHAR': False,
|
||||||
|
'SECURITY_MFA_IN_LOGIN_PAGE': False,
|
||||||
'SECURITY_LOGIN_CHALLENGE_ENABLED': False,
|
'SECURITY_LOGIN_CHALLENGE_ENABLED': False,
|
||||||
'SECURITY_LOGIN_CAPTCHA_ENABLED': True,
|
'SECURITY_LOGIN_CAPTCHA_ENABLED': True,
|
||||||
'SECURITY_INSECURE_COMMAND': False,
|
'SECURITY_INSECURE_COMMAND': False,
|
||||||
|
|
|
@ -55,6 +55,7 @@ SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL
|
||||||
SECURITY_VIEW_AUTH_NEED_MFA = CONFIG.SECURITY_VIEW_AUTH_NEED_MFA
|
SECURITY_VIEW_AUTH_NEED_MFA = CONFIG.SECURITY_VIEW_AUTH_NEED_MFA
|
||||||
SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION
|
SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION
|
||||||
SECURITY_LOGIN_CAPTCHA_ENABLED = CONFIG.SECURITY_LOGIN_CAPTCHA_ENABLED
|
SECURITY_LOGIN_CAPTCHA_ENABLED = CONFIG.SECURITY_LOGIN_CAPTCHA_ENABLED
|
||||||
|
SECURITY_MFA_IN_LOGIN_PAGE = CONFIG.SECURITY_MFA_IN_LOGIN_PAGE
|
||||||
SECURITY_LOGIN_CHALLENGE_ENABLED = CONFIG.SECURITY_LOGIN_CHALLENGE_ENABLED
|
SECURITY_LOGIN_CHALLENGE_ENABLED = CONFIG.SECURITY_LOGIN_CHALLENGE_ENABLED
|
||||||
SECURITY_DATA_CRYPTO_ALGO = CONFIG.SECURITY_DATA_CRYPTO_ALGO
|
SECURITY_DATA_CRYPTO_ALGO = CONFIG.SECURITY_DATA_CRYPTO_ALGO
|
||||||
SECURITY_INSECURE_COMMAND = CONFIG.SECURITY_INSECURE_COMMAND
|
SECURITY_INSECURE_COMMAND = CONFIG.SECURITY_INSECURE_COMMAND
|
||||||
|
|
|
@ -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-09-29 14:51+0800\n"
|
"POT-Creation-Date: 2021-10-18 17:36+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"
|
||||||
|
@ -25,7 +25,7 @@ msgstr ""
|
||||||
#: orgs/models.py:24 perms/models/base.py:44 settings/models.py:29
|
#: orgs/models.py:24 perms/models/base.py:44 settings/models.py:29
|
||||||
#: settings/serializers/sms.py:6 terminal/models/storage.py:23
|
#: settings/serializers/sms.py:6 terminal/models/storage.py:23
|
||||||
#: terminal/models/task.py:16 terminal/models/terminal.py:100
|
#: terminal/models/task.py:16 terminal/models/terminal.py:100
|
||||||
#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:605
|
#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:604
|
||||||
#: users/templates/users/_select_user_modal.html:13
|
#: users/templates/users/_select_user_modal.html:13
|
||||||
#: users/templates/users/user_asset_permission.html:37
|
#: users/templates/users/user_asset_permission.html:37
|
||||||
#: users/templates/users/user_asset_permission.html:154
|
#: users/templates/users/user_asset_permission.html:154
|
||||||
|
@ -60,7 +60,7 @@ msgstr "激活中"
|
||||||
#: orgs/models.py:27 perms/models/base.py:53 settings/models.py:34
|
#: orgs/models.py:27 perms/models/base.py:53 settings/models.py:34
|
||||||
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
|
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
|
||||||
#: tickets/models/ticket.py:71 users/models/group.py:16
|
#: tickets/models/ticket.py:71 users/models/group.py:16
|
||||||
#: users/models/user.py:638 xpack/plugins/change_auth_plan/models/base.py:41
|
#: users/models/user.py:637 xpack/plugins/change_auth_plan/models/base.py:41
|
||||||
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113
|
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113
|
||||||
#: xpack/plugins/gathered_user/models.py:26
|
#: xpack/plugins/gathered_user/models.py:26
|
||||||
msgid "Comment"
|
msgid "Comment"
|
||||||
|
@ -97,8 +97,8 @@ msgstr "动作"
|
||||||
#: perms/models/base.py:45 templates/index.html:78
|
#: perms/models/base.py:45 templates/index.html:78
|
||||||
#: terminal/backends/command/models.py:18
|
#: terminal/backends/command/models.py:18
|
||||||
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38
|
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38
|
||||||
#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:181
|
#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:180
|
||||||
#: users/models/user.py:814 users/models/user.py:840
|
#: users/models/user.py:813 users/models/user.py:839
|
||||||
#: users/serializers/group.py:19
|
#: users/serializers/group.py:19
|
||||||
#: users/templates/users/user_asset_permission.html:38
|
#: users/templates/users/user_asset_permission.html:38
|
||||||
#: users/templates/users/user_asset_permission.html:64
|
#: users/templates/users/user_asset_permission.html:64
|
||||||
|
@ -177,7 +177,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
|
||||||
#: applications/serializers/attrs/application_type/vmware_client.py:26
|
#: applications/serializers/attrs/application_type/vmware_client.py:26
|
||||||
#: assets/models/base.py:176 assets/models/gathered_user.py:15
|
#: assets/models/base.py:176 assets/models/gathered_user.py:15
|
||||||
#: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17
|
#: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17
|
||||||
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:603
|
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:602
|
||||||
#: users/templates/users/_select_user_modal.html:14
|
#: users/templates/users/_select_user_modal.html:14
|
||||||
#: xpack/plugins/change_auth_plan/models/asset.py:35
|
#: xpack/plugins/change_auth_plan/models/asset.py:35
|
||||||
#: xpack/plugins/change_auth_plan/models/asset.py:191
|
#: xpack/plugins/change_auth_plan/models/asset.py:191
|
||||||
|
@ -377,7 +377,7 @@ msgstr "目标URL"
|
||||||
#: applications/serializers/attrs/application_type/vmware_client.py:30
|
#: applications/serializers/attrs/application_type/vmware_client.py:30
|
||||||
#: assets/models/base.py:177 audits/signals_handler.py:65
|
#: assets/models/base.py:177 audits/signals_handler.py:65
|
||||||
#: authentication/forms.py:22
|
#: authentication/forms.py:22
|
||||||
#: authentication/templates/authentication/login.html:163
|
#: authentication/templates/authentication/login.html:151
|
||||||
#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21
|
#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21
|
||||||
#: users/templates/users/user_otp_check_password.html:13
|
#: users/templates/users/user_otp_check_password.html:13
|
||||||
#: users/templates/users/user_password_update.html:43
|
#: users/templates/users/user_password_update.html:43
|
||||||
|
@ -531,7 +531,7 @@ msgstr "标签管理"
|
||||||
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
|
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
|
||||||
#: assets/models/cmd_filter.py:67 assets/models/group.py:21
|
#: assets/models/cmd_filter.py:67 assets/models/group.py:21
|
||||||
#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25
|
#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25
|
||||||
#: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:646
|
#: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:645
|
||||||
#: users/serializers/group.py:33
|
#: users/serializers/group.py:33
|
||||||
#: xpack/plugins/change_auth_plan/models/base.py:45
|
#: xpack/plugins/change_auth_plan/models/base.py:45
|
||||||
#: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30
|
#: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30
|
||||||
|
@ -544,7 +544,7 @@ msgstr "创建者"
|
||||||
#: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50
|
#: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50
|
||||||
#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26
|
#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26
|
||||||
#: orgs/models.py:435 perms/models/base.py:52 users/models/group.py:18
|
#: orgs/models.py:435 perms/models/base.py:52 users/models/group.py:18
|
||||||
#: users/models/user.py:841 xpack/plugins/cloud/models.py:122
|
#: users/models/user.py:840 xpack/plugins/cloud/models.py:122
|
||||||
msgid "Date created"
|
msgid "Date created"
|
||||||
msgstr "创建日期"
|
msgstr "创建日期"
|
||||||
|
|
||||||
|
@ -599,7 +599,7 @@ msgstr "带宽"
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr "联系人"
|
msgstr "联系人"
|
||||||
|
|
||||||
#: assets/models/cluster.py:22 users/models/user.py:624
|
#: assets/models/cluster.py:22 users/models/user.py:623
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "手机"
|
msgstr "手机"
|
||||||
|
|
||||||
|
@ -625,7 +625,7 @@ msgid "Default"
|
||||||
msgstr "默认"
|
msgstr "默认"
|
||||||
|
|
||||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||||
#: users/models/user.py:826
|
#: users/models/user.py:825
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "系统"
|
msgstr "系统"
|
||||||
|
|
||||||
|
@ -1161,7 +1161,7 @@ msgstr "用户代理"
|
||||||
#: audits/models.py:110
|
#: audits/models.py:110
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
||||||
#: authentication/templates/authentication/login_otp.html:6
|
#: authentication/templates/authentication/login_otp.html:6
|
||||||
#: users/forms/profile.py:64 users/models/user.py:627
|
#: users/forms/profile.py:64 users/models/user.py:626
|
||||||
#: users/serializers/profile.py:102
|
#: users/serializers/profile.py:102
|
||||||
msgid "MFA"
|
msgid "MFA"
|
||||||
msgstr "多因子认证"
|
msgstr "多因子认证"
|
||||||
|
@ -1239,13 +1239,13 @@ msgstr ""
|
||||||
msgid "Auth Token"
|
msgid "Auth Token"
|
||||||
msgstr "认证令牌"
|
msgstr "认证令牌"
|
||||||
|
|
||||||
#: audits/signals_handler.py:68 authentication/views/login.py:160
|
#: audits/signals_handler.py:68 authentication/views/login.py:169
|
||||||
#: notifications/backends/__init__.py:11 users/models/user.py:660
|
#: notifications/backends/__init__.py:11 users/models/user.py:659
|
||||||
msgid "WeCom"
|
msgid "WeCom"
|
||||||
msgstr "企业微信"
|
msgstr "企业微信"
|
||||||
|
|
||||||
#: audits/signals_handler.py:69 authentication/views/login.py:166
|
#: audits/signals_handler.py:69 authentication/views/login.py:175
|
||||||
#: notifications/backends/__init__.py:12 users/models/user.py:661
|
#: notifications/backends/__init__.py:12 users/models/user.py:660
|
||||||
msgid "DingTalk"
|
msgid "DingTalk"
|
||||||
msgstr "钉钉"
|
msgstr "钉钉"
|
||||||
|
|
||||||
|
@ -1432,7 +1432,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}"
|
||||||
msgid "{ApplicationPermission} REMOVE {SystemUser}"
|
msgid "{ApplicationPermission} REMOVE {SystemUser}"
|
||||||
msgstr "{ApplicationPermission} 移除 {SystemUser}"
|
msgstr "{ApplicationPermission} 移除 {SystemUser}"
|
||||||
|
|
||||||
#: authentication/api/connection_token.py:227
|
#: authentication/api/connection_token.py:235
|
||||||
msgid "Invalid token"
|
msgid "Invalid token"
|
||||||
msgstr "无效的令牌"
|
msgstr "无效的令牌"
|
||||||
|
|
||||||
|
@ -1596,41 +1596,51 @@ msgid "MFA not set, please set it first"
|
||||||
msgstr "多因子认证没有设置,请先完成设置"
|
msgstr "多因子认证没有设置,请先完成设置"
|
||||||
|
|
||||||
#: authentication/errors.py:81
|
#: authentication/errors.py:81
|
||||||
|
msgid "OTP not set, please set it first"
|
||||||
|
msgstr "OTP认证没有设置,请先完成设置"
|
||||||
|
|
||||||
|
#: authentication/errors.py:82
|
||||||
msgid "Login confirm required"
|
msgid "Login confirm required"
|
||||||
msgstr "需要登录复核"
|
msgstr "需要登录复核"
|
||||||
|
|
||||||
#: authentication/errors.py:82
|
#: authentication/errors.py:83
|
||||||
msgid "Wait login confirm ticket for accept"
|
msgid "Wait login confirm ticket for accept"
|
||||||
msgstr "等待登录复核处理"
|
msgstr "等待登录复核处理"
|
||||||
|
|
||||||
#: authentication/errors.py:83
|
#: authentication/errors.py:84
|
||||||
msgid "Login confirm ticket was {}"
|
msgid "Login confirm ticket was {}"
|
||||||
msgstr "登录复核 {}"
|
msgstr "登录复核 {}"
|
||||||
|
|
||||||
#: authentication/errors.py:260
|
#: authentication/errors.py:261
|
||||||
msgid "IP is not allowed"
|
msgid "IP is not allowed"
|
||||||
msgstr "来源 IP 不被允许登录"
|
msgstr "来源 IP 不被允许登录"
|
||||||
|
|
||||||
#: authentication/errors.py:293
|
#: authentication/errors.py:294
|
||||||
msgid "SSO auth closed"
|
msgid "SSO auth closed"
|
||||||
msgstr "SSO 认证关闭了"
|
msgstr "SSO 认证关闭了"
|
||||||
|
|
||||||
#: authentication/errors.py:298 authentication/mixins.py:321
|
#: authentication/errors.py:299 authentication/mixins.py:338
|
||||||
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:307 authentication/mixins.py:328
|
#: authentication/errors.py:308 authentication/mixins.py:345
|
||||||
msgid "You should to change your password before login"
|
msgid "You should to change your password before login"
|
||||||
msgstr "登录完成前,请先修改密码"
|
msgstr "登录完成前,请先修改密码"
|
||||||
|
|
||||||
#: authentication/errors.py:316 authentication/mixins.py:335
|
#: authentication/errors.py:317 authentication/mixins.py:352
|
||||||
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:350
|
#: authentication/errors.py:351
|
||||||
msgid "Your password is invalid"
|
msgid "Your password is invalid"
|
||||||
msgstr "您的密码无效"
|
msgstr "您的密码无效"
|
||||||
|
|
||||||
|
#: authentication/errors.py:357
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Not has host {} permission"
|
||||||
|
msgid "No upload or download permission"
|
||||||
|
msgstr "没有该主机 {} 权限"
|
||||||
|
|
||||||
#: authentication/forms.py:35
|
#: authentication/forms.py:35
|
||||||
msgid "{} days auto login"
|
msgid "{} days auto login"
|
||||||
msgstr "{} 天内自动登录"
|
msgstr "{} 天内自动登录"
|
||||||
|
@ -1643,12 +1653,15 @@ msgstr "MFA 验证码"
|
||||||
msgid "MFA type"
|
msgid "MFA type"
|
||||||
msgstr "MFA 类型"
|
msgstr "MFA 类型"
|
||||||
|
|
||||||
#: authentication/forms.py:60 authentication/forms.py:62
|
#: authentication/forms.py:60 users/forms/profile.py:27
|
||||||
#: users/forms/profile.py:27
|
|
||||||
msgid "MFA code"
|
msgid "MFA code"
|
||||||
msgstr "多因子认证验证码"
|
msgstr "多因子认证验证码"
|
||||||
|
|
||||||
#: authentication/mixins.py:311
|
#: authentication/forms.py:62
|
||||||
|
msgid "Dynamic code"
|
||||||
|
msgstr "动态码"
|
||||||
|
|
||||||
|
#: authentication/mixins.py:328
|
||||||
msgid "Please change your password"
|
msgid "Please change your password"
|
||||||
msgstr "请修改密码"
|
msgstr "请修改密码"
|
||||||
|
|
||||||
|
@ -1656,7 +1669,7 @@ msgstr "请修改密码"
|
||||||
msgid "Private Token"
|
msgid "Private Token"
|
||||||
msgstr "SSH密钥"
|
msgstr "SSH密钥"
|
||||||
|
|
||||||
#: authentication/models.py:49 settings/serializers/security.py:118
|
#: authentication/models.py:49 settings/serializers/security.py:144
|
||||||
msgid "Login Confirm"
|
msgid "Login Confirm"
|
||||||
msgstr "登录复核"
|
msgstr "登录复核"
|
||||||
|
|
||||||
|
@ -1709,15 +1722,15 @@ msgstr ""
|
||||||
"若怀疑此次登录行为异常,请及时修改账号密码。\n"
|
"若怀疑此次登录行为异常,请及时修改账号密码。\n"
|
||||||
"感谢您对{server_name}的关注!\n"
|
"感谢您对{server_name}的关注!\n"
|
||||||
|
|
||||||
#: authentication/sms_verify_code.py:17
|
#: authentication/sms_verify_code.py:15
|
||||||
msgid "The verification code has expired. Please resend it"
|
msgid "The verification code has expired. Please resend it"
|
||||||
msgstr "验证码已过期,请重新发送"
|
msgstr "验证码已过期,请重新发送"
|
||||||
|
|
||||||
#: authentication/sms_verify_code.py:22
|
#: authentication/sms_verify_code.py:20
|
||||||
msgid "The verification code is incorrect"
|
msgid "The verification code is incorrect"
|
||||||
msgstr "验证码错误"
|
msgstr "验证码错误"
|
||||||
|
|
||||||
#: authentication/sms_verify_code.py:27
|
#: authentication/sms_verify_code.py:25
|
||||||
msgid "Please wait {} seconds before sending"
|
msgid "Please wait {} seconds before sending"
|
||||||
msgstr "请在 {} 秒后发送"
|
msgstr "请在 {} 秒后发送"
|
||||||
|
|
||||||
|
@ -1753,14 +1766,14 @@ msgid "Show"
|
||||||
msgstr "显示"
|
msgstr "显示"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:66
|
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||||
#: settings/serializers/security.py:25 users/models/user.py:470
|
#: settings/serializers/security.py:25 users/models/user.py:469
|
||||||
#: users/serializers/profile.py:99
|
#: users/serializers/profile.py:99
|
||||||
#: users/templates/users/user_verify_mfa.html:32
|
#: users/templates/users/user_verify_mfa.html:32
|
||||||
msgid "Disable"
|
msgid "Disable"
|
||||||
msgstr "禁用"
|
msgstr "禁用"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:67
|
#: authentication/templates/authentication/_access_key_modal.html:67
|
||||||
#: users/models/user.py:471 users/serializers/profile.py:100
|
#: users/models/user.py:470 users/serializers/profile.py:100
|
||||||
msgid "Enable"
|
msgid "Enable"
|
||||||
msgstr "启用"
|
msgstr "启用"
|
||||||
|
|
||||||
|
@ -1800,35 +1813,26 @@ msgstr "确认"
|
||||||
msgid "Code error"
|
msgid "Code error"
|
||||||
msgstr "代码错误"
|
msgstr "代码错误"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:155
|
#: authentication/templates/authentication/login.html:143
|
||||||
msgid "Welcome back, please enter username and password to login"
|
msgid "Welcome back, please enter username and password to login"
|
||||||
msgstr "欢迎回来,请输入用户名和密码登录"
|
msgstr "欢迎回来,请输入用户名和密码登录"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:187
|
#: authentication/templates/authentication/login.html:179
|
||||||
#: users/templates/users/forgot_password.html:15
|
#: users/templates/users/forgot_password.html:15
|
||||||
#: users/templates/users/forgot_password.html:16
|
#: users/templates/users/forgot_password.html:16
|
||||||
msgid "Forgot password"
|
msgid "Forgot password"
|
||||||
msgstr "忘记密码"
|
msgstr "忘记密码"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:194
|
#: authentication/templates/authentication/login.html:186
|
||||||
#: templates/_header_bar.html:83
|
#: templates/_header_bar.html:83
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "登录"
|
msgstr "登录"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:201
|
#: authentication/templates/authentication/login.html:193
|
||||||
msgid "More login options"
|
msgid "More login options"
|
||||||
msgstr "更多登录方式"
|
msgstr "更多登录方式"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_otp.html:24
|
#: authentication/templates/authentication/login_otp.html:19
|
||||||
msgid "Please enter the verification code"
|
|
||||||
msgstr "请输入验证码"
|
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_otp.html:25
|
|
||||||
#: authentication/templates/authentication/login_otp.html:75
|
|
||||||
msgid "Send verification code"
|
|
||||||
msgstr "发送验证码"
|
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_otp.html:29
|
|
||||||
#: users/templates/users/user_otp_check_password.html:16
|
#: users/templates/users/user_otp_check_password.html:16
|
||||||
#: users/templates/users/user_otp_enable_bind.html:24
|
#: users/templates/users/user_otp_enable_bind.html:24
|
||||||
#: users/templates/users/user_otp_enable_install_app.html:29
|
#: users/templates/users/user_otp_enable_install_app.html:29
|
||||||
|
@ -1836,19 +1840,10 @@ msgstr "发送验证码"
|
||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "下一步"
|
msgstr "下一步"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_otp.html:31
|
#: authentication/templates/authentication/login_otp.html:21
|
||||||
msgid "Can't provide security? Please contact the administrator!"
|
msgid "Can't provide security? Please contact the administrator!"
|
||||||
msgstr "如果不能提供多因子认证验证码,请联系管理员!"
|
msgstr "如果不能提供多因子认证验证码,请联系管理员!"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_otp.html:68
|
|
||||||
#: authentication/templates/authentication/login_otp.html:73
|
|
||||||
msgid "Wait: "
|
|
||||||
msgstr "等待:"
|
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_otp.html:81
|
|
||||||
msgid "The verification code has been sent"
|
|
||||||
msgstr "验证码已发送"
|
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_wait_confirm.html:41
|
#: authentication/templates/authentication/login_wait_confirm.html:41
|
||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "刷新"
|
msgstr "刷新"
|
||||||
|
@ -1957,24 +1952,28 @@ msgstr "请使用密码登录,然后绑定飞书"
|
||||||
msgid "Binding FeiShu failed"
|
msgid "Binding FeiShu failed"
|
||||||
msgstr "绑定飞书失败"
|
msgstr "绑定飞书失败"
|
||||||
|
|
||||||
#: authentication/views/login.py:81
|
#: authentication/views/login.py:82
|
||||||
msgid "Redirecting"
|
msgid "Redirecting"
|
||||||
msgstr "跳转中"
|
msgstr "跳转中"
|
||||||
|
|
||||||
#: authentication/views/login.py:82
|
#: authentication/views/login.py:83
|
||||||
msgid "Redirecting to {} authentication"
|
msgid "Redirecting to {} authentication"
|
||||||
msgstr "正在跳转到 {} 认证"
|
msgstr "正在跳转到 {} 认证"
|
||||||
|
|
||||||
#: authentication/views/login.py:108
|
#: authentication/views/login.py:109
|
||||||
msgid "Please enable cookies and try again."
|
msgid "Please enable cookies and try again."
|
||||||
msgstr "设置你的浏览器支持cookie"
|
msgstr "设置你的浏览器支持cookie"
|
||||||
|
|
||||||
#: authentication/views/login.py:172 notifications/backends/__init__.py:14
|
#: authentication/views/login.py:181 notifications/backends/__init__.py:14
|
||||||
#: users/models/user.py:662
|
#: users/models/user.py:661
|
||||||
msgid "FeiShu"
|
msgid "FeiShu"
|
||||||
msgstr "飞书"
|
msgstr "飞书"
|
||||||
|
|
||||||
#: authentication/views/login.py:259
|
#: authentication/views/login.py:202 authentication/views/mfa.py:50
|
||||||
|
msgid "SMS"
|
||||||
|
msgstr "短信"
|
||||||
|
|
||||||
|
#: authentication/views/login.py:291
|
||||||
msgid ""
|
msgid ""
|
||||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||||
" Don't close this page"
|
" Don't close this page"
|
||||||
|
@ -1982,26 +1981,18 @@ msgstr ""
|
||||||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||||
" 不要关闭本页面"
|
" 不要关闭本页面"
|
||||||
|
|
||||||
#: authentication/views/login.py:264
|
#: authentication/views/login.py:296
|
||||||
msgid "No ticket found"
|
msgid "No ticket found"
|
||||||
msgstr "没有发现工单"
|
msgstr "没有发现工单"
|
||||||
|
|
||||||
#: authentication/views/login.py:296
|
#: authentication/views/login.py:328
|
||||||
msgid "Logout success"
|
msgid "Logout success"
|
||||||
msgstr "退出登录成功"
|
msgstr "退出登录成功"
|
||||||
|
|
||||||
#: authentication/views/login.py:297
|
#: authentication/views/login.py:329
|
||||||
msgid "Logout success, return login page"
|
msgid "Logout success, return login page"
|
||||||
msgstr "退出登录成功,返回到登录页面"
|
msgstr "退出登录成功,返回到登录页面"
|
||||||
|
|
||||||
#: authentication/views/mfa.py:44 users/models/user.py:37
|
|
||||||
msgid "One-time password"
|
|
||||||
msgstr "一次性密码"
|
|
||||||
|
|
||||||
#: authentication/views/mfa.py:50
|
|
||||||
msgid "SMS"
|
|
||||||
msgstr "短信"
|
|
||||||
|
|
||||||
#: authentication/views/wecom.py:41
|
#: authentication/views/wecom.py:41
|
||||||
msgid "WeCom Error, Please contact your system administrator"
|
msgid "WeCom Error, Please contact your system administrator"
|
||||||
msgstr "企业微信错误,请联系系统管理员"
|
msgstr "企业微信错误,请联系系统管理员"
|
||||||
|
@ -2218,7 +2209,7 @@ msgstr ""
|
||||||
"div>"
|
"div>"
|
||||||
|
|
||||||
#: notifications/backends/__init__.py:10 users/forms/profile.py:101
|
#: notifications/backends/__init__.py:10 users/forms/profile.py:101
|
||||||
#: users/models/user.py:607
|
#: users/models/user.py:606
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "邮件"
|
msgstr "邮件"
|
||||||
|
|
||||||
|
@ -2436,7 +2427,7 @@ msgstr "组织审计员"
|
||||||
msgid "GLOBAL"
|
msgid "GLOBAL"
|
||||||
msgstr "全局组织"
|
msgstr "全局组织"
|
||||||
|
|
||||||
#: orgs/models.py:434 users/models/user.py:615 users/serializers/user.py:37
|
#: orgs/models.py:434 users/models/user.py:614 users/serializers/user.py:37
|
||||||
#: users/templates/users/_select_user_modal.html:15
|
#: users/templates/users/_select_user_modal.html:15
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr "角色"
|
msgstr "角色"
|
||||||
|
@ -2449,7 +2440,7 @@ msgstr "管理员正在修改授权,请稍等"
|
||||||
msgid "The authorization cannot be revoked for the time being"
|
msgid "The authorization cannot be revoked for the time being"
|
||||||
msgstr "该授权暂时不能撤销"
|
msgstr "该授权暂时不能撤销"
|
||||||
|
|
||||||
#: perms/models/application_permission.py:27 users/models/user.py:182
|
#: perms/models/application_permission.py:27 users/models/user.py:181
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr "应用程序"
|
msgstr "应用程序"
|
||||||
|
|
||||||
|
@ -2501,7 +2492,7 @@ msgid "Favorite"
|
||||||
msgstr "收藏夹"
|
msgstr "收藏夹"
|
||||||
|
|
||||||
#: perms/models/base.py:47 templates/_nav.html:21 users/models/group.py:31
|
#: perms/models/base.py:47 templates/_nav.html:21 users/models/group.py:31
|
||||||
#: users/models/user.py:611 users/templates/users/_select_user_modal.html:16
|
#: users/models/user.py:610 users/templates/users/_select_user_modal.html:16
|
||||||
#: users/templates/users/user_asset_permission.html:39
|
#: users/templates/users/user_asset_permission.html:39
|
||||||
#: users/templates/users/user_asset_permission.html:67
|
#: users/templates/users/user_asset_permission.html:67
|
||||||
#: users/templates/users/user_database_app_permission.html:38
|
#: users/templates/users/user_database_app_permission.html:38
|
||||||
|
@ -2512,7 +2503,7 @@ msgstr "用户组"
|
||||||
#: perms/models/base.py:50
|
#: perms/models/base.py:50
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:60
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:60
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50
|
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50
|
||||||
#: users/models/user.py:643
|
#: users/models/user.py:642
|
||||||
msgid "Date expired"
|
msgid "Date expired"
|
||||||
msgstr "失效日期"
|
msgstr "失效日期"
|
||||||
|
|
||||||
|
@ -2520,6 +2511,272 @@ msgstr "失效日期"
|
||||||
msgid "From ticket"
|
msgid "From ticket"
|
||||||
msgstr "来自工单"
|
msgstr "来自工单"
|
||||||
|
|
||||||
|
#: perms/notifications.py:18 perms/notifications.py:40
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Asset number"
|
||||||
|
msgid "Assets may expire"
|
||||||
|
msgstr "资产编号"
|
||||||
|
|
||||||
|
#: perms/notifications.py:21
|
||||||
|
#, fuzzy, python-format
|
||||||
|
#| msgid ""
|
||||||
|
#| "\n"
|
||||||
|
#| " Hello %(name)s:\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " Your account will expire in %(date_expired)s,\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " In order not to affect your normal work, please contact the "
|
||||||
|
#| "administrator for confirmation.\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " "
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Hello %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" Your permissions for the following assets may expire in three "
|
||||||
|
"days:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" %(assets)s\n"
|
||||||
|
" <br>\n"
|
||||||
|
" Please contact the administrator\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" 您好 %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 您的账户会在 %(date_expired)s 过期,\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 为了不影响您正常工作,请联系管理员确认。\n"
|
||||||
|
" <br>\n"
|
||||||
|
" "
|
||||||
|
|
||||||
|
#: perms/notifications.py:43
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"Hello %(name)s:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"Your permissions for the following assets may expire in three days:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"%(assets)s\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"Please contact the administrator\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: perms/notifications.py:69 perms/notifications.py:90
|
||||||
|
#: perms/notifications.py:117 perms/notifications.py:140
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Asset permission"
|
||||||
|
msgid "Asset permission will expired"
|
||||||
|
msgstr "资产授权"
|
||||||
|
|
||||||
|
#: perms/notifications.py:72
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Hello %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" The following asset permissions of organization %(org) will "
|
||||||
|
"expire in three days\n"
|
||||||
|
" <br>\n"
|
||||||
|
" %(perms)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: perms/notifications.py:93
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"Hello %(name)s:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"The following asset permissions of organization %(org) will expire in three "
|
||||||
|
"days\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"%(perms)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: perms/notifications.py:123
|
||||||
|
#, fuzzy, python-format
|
||||||
|
#| msgid ""
|
||||||
|
#| "\n"
|
||||||
|
#| " Hello %(name)s:\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " Your account will expire in %(date_expired)s,\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " In order not to affect your normal work, please contact the "
|
||||||
|
#| "administrator for confirmation.\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " "
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Hello %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" The following asset permissions will expire in three days\n"
|
||||||
|
" <br>\n"
|
||||||
|
" %(content)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" 您好 %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 您的账户会在 %(date_expired)s 过期,\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 为了不影响您正常工作,请联系管理员确认。\n"
|
||||||
|
" <br>\n"
|
||||||
|
" "
|
||||||
|
|
||||||
|
#: perms/notifications.py:146
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"Hello %(name)s:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"The following asset permissions of organization %(org) will expire in three "
|
||||||
|
"days\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"%(content)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: perms/notifications.py:169 perms/notifications.py:191
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Applications"
|
||||||
|
msgid "Applications may expire"
|
||||||
|
msgstr "应用管理"
|
||||||
|
|
||||||
|
#: perms/notifications.py:172
|
||||||
|
#, fuzzy, python-format
|
||||||
|
#| msgid ""
|
||||||
|
#| "\n"
|
||||||
|
#| " Hello %(name)s:\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " Your account will expire in %(date_expired)s,\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " In order not to affect your normal work, please contact the "
|
||||||
|
#| "administrator for confirmation.\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " "
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Hello %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" Your permissions for the following applications may expire in "
|
||||||
|
"three days:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" %(apps)s\n"
|
||||||
|
" <br>\n"
|
||||||
|
" Please contact the administrator\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" 您好 %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 您的账户会在 %(date_expired)s 过期,\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 为了不影响您正常工作,请联系管理员确认。\n"
|
||||||
|
" <br>\n"
|
||||||
|
" "
|
||||||
|
|
||||||
|
#: perms/notifications.py:194
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"Hello %(name)s:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"Your permissions for the following applications may expire in three days:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"%(apps)s\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"Please contact the administrator\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: perms/notifications.py:220 perms/notifications.py:241
|
||||||
|
#: perms/notifications.py:268 perms/notifications.py:291
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Application permission"
|
||||||
|
msgid "Application permission will expired"
|
||||||
|
msgstr "应用管理"
|
||||||
|
|
||||||
|
#: perms/notifications.py:223
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Hello %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" The following application permissions of organization %(org) "
|
||||||
|
"will expire in three days\n"
|
||||||
|
" <br>\n"
|
||||||
|
" %(perms)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: perms/notifications.py:244
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"Hello %(name)s:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"The following application permissions of organization %(org) will expire in "
|
||||||
|
"three days\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"%(perms)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: perms/notifications.py:274
|
||||||
|
#, fuzzy, python-format
|
||||||
|
#| msgid ""
|
||||||
|
#| "\n"
|
||||||
|
#| " Hello %(name)s:\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " Your account will expire in %(date_expired)s,\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " In order not to affect your normal work, please contact the "
|
||||||
|
#| "administrator for confirmation.\n"
|
||||||
|
#| " <br>\n"
|
||||||
|
#| " "
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Hello %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" The following application permissions will expire in three days\n"
|
||||||
|
" <br>\n"
|
||||||
|
" %(content)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" 您好 %(name)s:\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 您的账户会在 %(date_expired)s 过期,\n"
|
||||||
|
" <br>\n"
|
||||||
|
" 为了不影响您正常工作,请联系管理员确认。\n"
|
||||||
|
" <br>\n"
|
||||||
|
" "
|
||||||
|
|
||||||
|
#: perms/notifications.py:297
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"Hello %(name)s:\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"The following application permissions of organization %(org) will expire in "
|
||||||
|
"three days\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"%(content)s\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: perms/serializers/application/permission.py:18
|
#: perms/serializers/application/permission.py:18
|
||||||
#: perms/serializers/application/permission.py:38
|
#: perms/serializers/application/permission.py:38
|
||||||
#: perms/serializers/asset/permission.py:42
|
#: perms/serializers/asset/permission.py:42
|
||||||
|
@ -2885,7 +3142,7 @@ msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过
|
||||||
msgid "SSO auth key TTL"
|
msgid "SSO auth key TTL"
|
||||||
msgstr "Token 有效期"
|
msgstr "Token 有效期"
|
||||||
|
|
||||||
#: settings/serializers/auth/sso.py:16 settings/serializers/security.py:74
|
#: settings/serializers/auth/sso.py:16
|
||||||
msgid "Unit: second"
|
msgid "Unit: second"
|
||||||
msgstr "单位: 秒"
|
msgstr "单位: 秒"
|
||||||
|
|
||||||
|
@ -3196,73 +3453,103 @@ msgstr "开启后,如果用户来源为本地,CAS、OIDC 登录将会失败"
|
||||||
msgid "MFA verify TTL"
|
msgid "MFA verify TTL"
|
||||||
msgstr "MFA 校验有效期"
|
msgstr "MFA 校验有效期"
|
||||||
|
|
||||||
#: settings/serializers/security.py:78
|
#: settings/serializers/security.py:75
|
||||||
|
msgid ""
|
||||||
|
"Unit: second, The verification MFA takes effect only when you view the "
|
||||||
|
"account password"
|
||||||
|
msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效"
|
||||||
|
|
||||||
|
#: settings/serializers/security.py:79
|
||||||
|
msgid "Enable Login dynamic code"
|
||||||
|
msgstr "启用登录附加码"
|
||||||
|
|
||||||
|
#: settings/serializers/security.py:80
|
||||||
|
msgid ""
|
||||||
|
"The password and additional code are sent to a third party authentication "
|
||||||
|
"system for verification"
|
||||||
|
msgstr ""
|
||||||
|
"密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密"
|
||||||
|
"码+6位数字 完成认证"
|
||||||
|
|
||||||
|
#: settings/serializers/security.py:85
|
||||||
|
msgid "MFA in login page"
|
||||||
|
msgstr "MFA 在登录页面"
|
||||||
|
|
||||||
|
#: settings/serializers/security.py:86
|
||||||
|
msgid "Eu security regulations(GDPR) require MFA to be on the login page"
|
||||||
|
msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全"
|
||||||
|
|
||||||
|
#: settings/serializers/security.py:89
|
||||||
msgid "Enable Login captcha"
|
msgid "Enable Login captcha"
|
||||||
msgstr "启用登录验证码"
|
msgstr "启用登录验证码"
|
||||||
|
|
||||||
#: settings/serializers/security.py:84
|
#: settings/serializers/security.py:90
|
||||||
|
msgid "Enable captcha to prevent robot authentication"
|
||||||
|
msgstr "开启验证码,防止机器人登录"
|
||||||
|
|
||||||
|
#: settings/serializers/security.py:110
|
||||||
msgid "Enable terminal register"
|
msgid "Enable terminal register"
|
||||||
msgstr "终端注册"
|
msgstr "终端注册"
|
||||||
|
|
||||||
#: settings/serializers/security.py:85
|
#: settings/serializers/security.py:111
|
||||||
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:88
|
#: settings/serializers/security.py:114
|
||||||
msgid "Replay watermark"
|
msgid "Replay watermark"
|
||||||
msgstr "录像水印"
|
msgstr "录像水印"
|
||||||
|
|
||||||
#: settings/serializers/security.py:89
|
#: settings/serializers/security.py:115
|
||||||
msgid "Enabled, the session replay contains watermark information"
|
msgid "Enabled, the session replay contains watermark information"
|
||||||
msgstr "启用后,会话录像将包含水印信息"
|
msgstr "启用后,会话录像将包含水印信息"
|
||||||
|
|
||||||
#: settings/serializers/security.py:93
|
#: settings/serializers/security.py:119
|
||||||
msgid "Connection max idle time"
|
msgid "Connection max idle time"
|
||||||
msgstr "连接最大空闲时间"
|
msgstr "连接最大空闲时间"
|
||||||
|
|
||||||
#: settings/serializers/security.py:94
|
#: settings/serializers/security.py:120
|
||||||
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:97
|
#: settings/serializers/security.py:123
|
||||||
msgid "Remember manual auth"
|
msgid "Remember manual auth"
|
||||||
msgstr "保存手动输入密码"
|
msgstr "保存手动输入密码"
|
||||||
|
|
||||||
#: settings/serializers/security.py:100
|
#: settings/serializers/security.py:126
|
||||||
msgid "Enable change auth secure mode"
|
msgid "Enable change auth secure mode"
|
||||||
msgstr "启用改密安全模式"
|
msgstr "启用改密安全模式"
|
||||||
|
|
||||||
#: settings/serializers/security.py:103
|
#: settings/serializers/security.py:129
|
||||||
msgid "Insecure command alert"
|
msgid "Insecure command alert"
|
||||||
msgstr "危险命令告警"
|
msgstr "危险命令告警"
|
||||||
|
|
||||||
#: settings/serializers/security.py:106
|
#: settings/serializers/security.py:132
|
||||||
msgid "Email recipient"
|
msgid "Email recipient"
|
||||||
msgstr "邮件收件人"
|
msgstr "邮件收件人"
|
||||||
|
|
||||||
#: settings/serializers/security.py:107
|
#: settings/serializers/security.py:133
|
||||||
msgid "Multiple user using , split"
|
msgid "Multiple user using , split"
|
||||||
msgstr "多个用户,使用 , 分割"
|
msgstr "多个用户,使用 , 分割"
|
||||||
|
|
||||||
#: settings/serializers/security.py:110
|
#: settings/serializers/security.py:136
|
||||||
msgid "Batch command execution"
|
msgid "Batch command execution"
|
||||||
msgstr "批量命令执行"
|
msgstr "批量命令执行"
|
||||||
|
|
||||||
#: settings/serializers/security.py:111
|
#: settings/serializers/security.py:137
|
||||||
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:114
|
#: settings/serializers/security.py:140
|
||||||
msgid "Session share"
|
msgid "Session share"
|
||||||
msgstr "会话分享"
|
msgstr "会话分享"
|
||||||
|
|
||||||
#: settings/serializers/security.py:115
|
#: settings/serializers/security.py:141
|
||||||
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:119
|
#: settings/serializers/security.py:145
|
||||||
msgid "Enabled, please go to the user detail add approver"
|
msgid "Enabled, please go to the user detail add approver"
|
||||||
msgstr "启用后, 请在用户详情中添加审批人"
|
msgstr "启用后, 请在用户详情中添加审批人"
|
||||||
|
|
||||||
|
@ -3576,6 +3863,30 @@ msgstr ""
|
||||||
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
|
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
|
#: templates/_mfa_otp_login.html:14
|
||||||
|
msgid "Please enter verification code"
|
||||||
|
msgstr "请输入验证码"
|
||||||
|
|
||||||
|
#: templates/_mfa_otp_login.html:16 templates/_mfa_otp_login.html:67
|
||||||
|
msgid "Send verification code"
|
||||||
|
msgstr "发送验证码"
|
||||||
|
|
||||||
|
#: templates/_mfa_otp_login.html:37
|
||||||
|
msgid "Please enter MFA code"
|
||||||
|
msgstr "请输入6位动态安全码"
|
||||||
|
|
||||||
|
#: templates/_mfa_otp_login.html:38
|
||||||
|
msgid "Please enter SMS code"
|
||||||
|
msgstr "请输入短信验证码"
|
||||||
|
|
||||||
|
#: templates/_mfa_otp_login.html:60 templates/_mfa_otp_login.html:65
|
||||||
|
msgid "Wait: "
|
||||||
|
msgstr "等待:"
|
||||||
|
|
||||||
|
#: templates/_mfa_otp_login.html:73
|
||||||
|
msgid "The verification code has been sent"
|
||||||
|
msgstr "验证码已发送"
|
||||||
|
|
||||||
#: templates/_nav.html:7
|
#: templates/_nav.html:7
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "仪表盘"
|
msgstr "仪表盘"
|
||||||
|
@ -4711,11 +5022,11 @@ msgstr "当前组织已存在该类型"
|
||||||
msgid "Could not reset self otp, use profile reset instead"
|
msgid "Could not reset self otp, use profile reset instead"
|
||||||
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
|
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
|
||||||
|
|
||||||
#: users/const.py:10 users/models/user.py:179
|
#: users/const.py:10 users/models/user.py:178
|
||||||
msgid "System administrator"
|
msgid "System administrator"
|
||||||
msgstr "系统管理员"
|
msgstr "系统管理员"
|
||||||
|
|
||||||
#: users/const.py:11 users/models/user.py:180
|
#: users/const.py:11 users/models/user.py:179
|
||||||
msgid "System auditor"
|
msgid "System auditor"
|
||||||
msgstr "系统审计员"
|
msgstr "系统审计员"
|
||||||
|
|
||||||
|
@ -4810,52 +5121,56 @@ msgstr "不能和原来的密钥相同"
|
||||||
msgid "Not a valid ssh public key"
|
msgid "Not a valid ssh public key"
|
||||||
msgstr "SSH密钥不合法"
|
msgstr "SSH密钥不合法"
|
||||||
|
|
||||||
#: users/forms/profile.py:160 users/models/user.py:635
|
#: users/forms/profile.py:160 users/models/user.py:634
|
||||||
#: users/templates/users/user_password_update.html:48
|
#: users/templates/users/user_password_update.html:48
|
||||||
msgid "Public key"
|
msgid "Public key"
|
||||||
msgstr "SSH公钥"
|
msgstr "SSH公钥"
|
||||||
|
|
||||||
#: users/models/user.py:38
|
#: users/models/user.py:36
|
||||||
|
msgid "One-time password"
|
||||||
|
msgstr "一次性密码"
|
||||||
|
|
||||||
|
#: users/models/user.py:37
|
||||||
msgid "SMS verify code"
|
msgid "SMS verify code"
|
||||||
msgstr "短信验证码"
|
msgstr "短信验证码"
|
||||||
|
|
||||||
#: users/models/user.py:472
|
#: users/models/user.py:471
|
||||||
msgid "Force enable"
|
msgid "Force enable"
|
||||||
msgstr "强制启用"
|
msgstr "强制启用"
|
||||||
|
|
||||||
#: users/models/user.py:584
|
#: users/models/user.py:583
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "数据库"
|
msgstr "数据库"
|
||||||
|
|
||||||
#: users/models/user.py:618
|
#: users/models/user.py:617
|
||||||
msgid "Avatar"
|
msgid "Avatar"
|
||||||
msgstr "头像"
|
msgstr "头像"
|
||||||
|
|
||||||
#: users/models/user.py:621
|
#: users/models/user.py:620
|
||||||
msgid "Wechat"
|
msgid "Wechat"
|
||||||
msgstr "微信"
|
msgstr "微信"
|
||||||
|
|
||||||
#: users/models/user.py:632
|
#: users/models/user.py:631
|
||||||
msgid "Private key"
|
msgid "Private key"
|
||||||
msgstr "ssh私钥"
|
msgstr "ssh私钥"
|
||||||
|
|
||||||
#: users/models/user.py:651
|
#: users/models/user.py:650
|
||||||
msgid "Source"
|
msgid "Source"
|
||||||
msgstr "来源"
|
msgstr "来源"
|
||||||
|
|
||||||
#: users/models/user.py:655
|
#: users/models/user.py:654
|
||||||
msgid "Date password last updated"
|
msgid "Date password last updated"
|
||||||
msgstr "最后更新密码日期"
|
msgstr "最后更新密码日期"
|
||||||
|
|
||||||
#: users/models/user.py:658
|
#: users/models/user.py:657
|
||||||
msgid "Need update password"
|
msgid "Need update password"
|
||||||
msgstr "需要更新密码"
|
msgstr "需要更新密码"
|
||||||
|
|
||||||
#: users/models/user.py:822
|
#: users/models/user.py:821
|
||||||
msgid "Administrator"
|
msgid "Administrator"
|
||||||
msgstr "管理员"
|
msgstr "管理员"
|
||||||
|
|
||||||
#: users/models/user.py:825
|
#: users/models/user.py:824
|
||||||
msgid "Administrator is the super user of system"
|
msgid "Administrator is the super user of system"
|
||||||
msgstr "Administrator是初始的超级管理员"
|
msgstr "Administrator是初始的超级管理员"
|
||||||
|
|
||||||
|
@ -6320,6 +6635,12 @@ msgstr "旗舰版"
|
||||||
msgid "Community edition"
|
msgid "Community edition"
|
||||||
msgstr "社区版"
|
msgstr "社区版"
|
||||||
|
|
||||||
|
#~ msgid "Enable Login MFA"
|
||||||
|
#~ msgstr "启用登录MFA"
|
||||||
|
|
||||||
|
#~ msgid "Enable login password add-on"
|
||||||
|
#~ msgstr "启用登录密码附加码"
|
||||||
|
|
||||||
#~ msgid "OpenID"
|
#~ msgid "OpenID"
|
||||||
#~ msgstr "OpenID"
|
#~ msgstr "OpenID"
|
||||||
|
|
||||||
|
|
|
@ -70,14 +70,40 @@ class SecurityAuthSerializer(serializers.Serializer):
|
||||||
help_text=_("Only log in from the user source property")
|
help_text=_("Only log in from the user source property")
|
||||||
)
|
)
|
||||||
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(
|
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(
|
||||||
min_value=5, max_value=60*60*10,
|
min_value=5, max_value=60 * 60 * 10,
|
||||||
label=_("MFA verify TTL"), help_text=_("Unit: second"),
|
label=_("MFA verify TTL"),
|
||||||
|
help_text=_("Unit: second, The verification MFA takes effect only when you view the account password"),
|
||||||
|
)
|
||||||
|
SECURITY_LOGIN_CHALLENGE_ENABLED = serializers.BooleanField(
|
||||||
|
required=False, default=False,
|
||||||
|
label=_("Enable Login dynamic code"),
|
||||||
|
help_text=_("The password and additional code are sent to a third party "
|
||||||
|
"authentication system for verification")
|
||||||
|
)
|
||||||
|
SECURITY_MFA_IN_LOGIN_PAGE = serializers.BooleanField(
|
||||||
|
required=False, default=False,
|
||||||
|
label=_("MFA in login page"),
|
||||||
|
help_text=_("Eu security regulations(GDPR) require MFA to be on the login page")
|
||||||
)
|
)
|
||||||
SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField(
|
SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField(
|
||||||
required=False, default=True,
|
required=False, default=False, label=_("Enable Login captcha"),
|
||||||
label=_("Enable Login captcha")
|
help_text=_("Enable captcha to prevent robot authentication")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
if attrs.get('SECURITY_MFA_AUTH') != 1:
|
||||||
|
attrs['SECURITY_MFA_IN_LOGIN_PAGE'] = False
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
data = super().to_representation(instance)
|
||||||
|
if data['SECURITY_LOGIN_CHALLENGE_ENABLED']:
|
||||||
|
data['SECURITY_MFA_IN_LOGIN_PAGE'] = False
|
||||||
|
data['SECURITY_LOGIN_CAPTCHA_ENABLED'] = False
|
||||||
|
elif data['SECURITY_MFA_IN_LOGIN_PAGE']:
|
||||||
|
data['SECURITY_LOGIN_CAPTCHA_ENABLED'] = False
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSerializer):
|
class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSerializer):
|
||||||
SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(
|
SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(
|
||||||
|
@ -118,4 +144,3 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri
|
||||||
required=False, label=_('Login Confirm'),
|
required=False, label=_('Login Confirm'),
|
||||||
help_text=_("Enabled, please go to the user detail add approver")
|
help_text=_("Enabled, please go to the user detail add approver")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
{% load i18n %}
|
||||||
|
<select id="verify-method-select" name="mfa_type" class="form-control select-con" onchange="selectChange(this.value)">
|
||||||
|
{% for method in methods %}
|
||||||
|
<option value="{{ method.name }}"
|
||||||
|
{% if method.selected %} selected {% endif %}
|
||||||
|
{% if not method.enable %} disabled {% endif %}
|
||||||
|
>
|
||||||
|
{{ method.label }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<div class="mfa-div">
|
||||||
|
<input id="mfa-code" type="text" class="form-control input-style" required name="code"
|
||||||
|
placeholder="{% trans 'Please enter verification code' %}">
|
||||||
|
<button id='send-sms-verify-code' type="button" class="btn btn-primary full-width" onclick="sendSMSVerifyCode()"
|
||||||
|
style="margin-left: 10px!important;height: 100%">{% trans 'Send verification code' %}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.input-style {
|
||||||
|
width: calc(100% - 114px);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#send-sms-verify-code {
|
||||||
|
width: 110px !important;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
var methodSelect = document.getElementById('verify-method-select');
|
||||||
|
if (methodSelect.value !== null) {
|
||||||
|
selectChange(methodSelect.value);
|
||||||
|
}
|
||||||
|
function selectChange(type) {
|
||||||
|
var otpPlaceholder = '{% trans 'Please enter MFA code' %}';
|
||||||
|
var smsPlaceholder = '{% trans 'Please enter SMS code' %}';
|
||||||
|
if (type === "sms") {
|
||||||
|
$("#mfa-code").css("cssText", "width: calc(100% - 114px)").attr('placeholder', smsPlaceholder);
|
||||||
|
$("#send-sms-verify-code").css("cssText", "display: inline-block !important");
|
||||||
|
} else {
|
||||||
|
$("#mfa-code").css("cssText", "width: 100% !important").attr('placeholder', otpPlaceholder);
|
||||||
|
$("#send-sms-verify-code").css("cssText", "display: none !important");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendSMSVerifyCode() {
|
||||||
|
var currentBtn = document.getElementById('send-sms-verify-code');
|
||||||
|
var time = 60
|
||||||
|
var url = "{% url 'api-auth:sms-verify-code-send' %}";
|
||||||
|
var data = {
|
||||||
|
username: $("#id_username").val()
|
||||||
|
};
|
||||||
|
requestApi({
|
||||||
|
url: url,
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success: function (data) {
|
||||||
|
currentBtn.innerHTML = `{% trans 'Wait: ' %} ${time}`;
|
||||||
|
currentBtn.disabled = true
|
||||||
|
currentBtn.classList.add("disabledBtn")
|
||||||
|
var TimeInterval = setInterval(() => {
|
||||||
|
--time
|
||||||
|
currentBtn.innerHTML = `{% trans 'Wait: ' %} ${time}`;
|
||||||
|
if (time === 0) {
|
||||||
|
currentBtn.innerHTML = "{% trans 'Send verification code' %}"
|
||||||
|
currentBtn.disabled = false
|
||||||
|
currentBtn.classList.remove("disabledBtn")
|
||||||
|
clearInterval(TimeInterval)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
alert("{% trans 'The verification code has been sent' %}");
|
||||||
|
},
|
||||||
|
error: function (text, data) {
|
||||||
|
alert(data.detail)
|
||||||
|
},
|
||||||
|
flash_message: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -9,10 +9,9 @@ import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.auth.hashers import check_password, make_password
|
from django.contrib.auth.hashers import check_password
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import TextChoices
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
Loading…
Reference in New Issue