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 time
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.generics import CreateAPIView
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.response import Response
|
||||
|
||||
from authentication.sms_verify_code import VerifyCodeUtil
|
||||
from common.exceptions import JMSException
|
||||
from common.permissions import IsValidUser, NeedMFAVerify, IsAppUser
|
||||
from users.models.user import MFAType
|
||||
from common.permissions import IsValidUser, NeedMFAVerify
|
||||
from users.models.user import MFAType, User
|
||||
from ..serializers import OtpVerifySerializer
|
||||
from .. import serializers
|
||||
from .. import errors
|
||||
|
@ -90,6 +90,13 @@ class SendSMSVerifyCodeApi(AuthMixin, CreateAPIView):
|
|||
permission_classes = (AllowAny,)
|
||||
|
||||
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()
|
||||
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_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_wait_msg = _("Wait login confirm ticket for accept")
|
||||
login_confirm_error_msg = _("Login confirm ticket was {}")
|
||||
|
@ -354,3 +355,15 @@ class NotHaveUpDownLoadPerm(JMSException):
|
|||
status_code = status.HTTP_403_FORBIDDEN
|
||||
code = 'not_have_up_down_load_perm'
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ class ChallengeMixin(forms.Form):
|
|||
challenge = forms.CharField(
|
||||
label=_('MFA code'), max_length=6, required=False,
|
||||
widget=forms.TextInput(attrs={
|
||||
'placeholder': _("MFA code"),
|
||||
'placeholder': _("Dynamic code"),
|
||||
'style': 'width: 50%'
|
||||
})
|
||||
)
|
||||
|
@ -69,6 +69,8 @@ def get_user_login_form_cls(*, captcha=False):
|
|||
bases = []
|
||||
if settings.SECURITY_LOGIN_CHALLENGE_ENABLED:
|
||||
bases.append(ChallengeMixin)
|
||||
elif settings.SECURITY_MFA_IN_LOGIN_PAGE:
|
||||
bases.append(UserCheckOtpCodeForm)
|
||||
elif settings.SECURITY_LOGIN_CAPTCHA_ENABLED and captcha:
|
||||
bases.append(CaptchaMixin)
|
||||
bases.append(UserLoginForm)
|
||||
|
|
|
@ -7,6 +7,7 @@ import time
|
|||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib import auth
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth import (
|
||||
|
@ -14,11 +15,11 @@ from django.contrib.auth import (
|
|||
PermissionDenied, user_login_failed, _clean_credentials
|
||||
)
|
||||
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 users.models import User, MFAType
|
||||
from users.utils import LoginBlockUtil, MFABlockUtils
|
||||
from users.exceptions import MFANotEnabled
|
||||
from . import errors
|
||||
from .utils import rsa_decrypt, gen_key_pair
|
||||
from .signals import post_auth_success, post_auth_failed
|
||||
|
@ -208,18 +209,20 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
ip = self.get_request_ip()
|
||||
self._set_partial_credential_error(username=username, ip=ip, request=request)
|
||||
|
||||
password = password + challenge.strip()
|
||||
if decrypt_passwd:
|
||||
password = self.get_decrypted_password()
|
||||
password = password + challenge.strip()
|
||||
return username, password, public_key, ip, auto_login
|
||||
|
||||
def _check_only_allow_exists_user_auth(self, username):
|
||||
# 仅允许预先存在的用户认证
|
||||
if settings.ONLY_ALLOW_EXIST_USER_AUTH:
|
||||
exist = User.objects.filter(username=username).exists()
|
||||
if not exist:
|
||||
logger.error(f"Only allow exist user auth, login failed: {username}")
|
||||
self.raise_credential_error(errors.reason_user_not_exist)
|
||||
if not settings.ONLY_ALLOW_EXIST_USER_AUTH:
|
||||
return
|
||||
|
||||
exist = User.objects.filter(username=username).exists()
|
||||
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):
|
||||
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)
|
||||
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):
|
||||
# ACL 限制用户登录
|
||||
from acls.models import LoginACL
|
||||
|
@ -255,8 +269,7 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
|
||||
def check_user_auth(self, decrypt_passwd=False):
|
||||
self.check_is_block()
|
||||
request = self.request
|
||||
username, password, public_key, ip, auto_login = self.get_auth_data(decrypt_passwd=decrypt_passwd)
|
||||
username, password, public_key, ip, auto_login = self.get_auth_data(decrypt_passwd)
|
||||
|
||||
self._check_only_allow_exists_user_auth(username)
|
||||
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_need_update(user)
|
||||
|
||||
# 校验login-mfa, 如果登录页面上显示 mfa 的话
|
||||
self._check_login_mfa_login_if_need(user)
|
||||
|
||||
LoginBlockUtil(username, ip).clean_failed_count()
|
||||
request = self.request
|
||||
request.session['auth_password'] = 1
|
||||
request.session['user_id'] = str(user.id)
|
||||
request.session['auto_login'] = auto_login
|
||||
|
@ -348,12 +365,11 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
def check_user_mfa_if_need(self, user):
|
||||
if self.request.session.get('auth_mfa'):
|
||||
return
|
||||
|
||||
if settings.OTP_IN_RADIUS:
|
||||
return
|
||||
|
||||
if not user.mfa_enabled:
|
||||
return
|
||||
|
||||
unset, url = user.mfa_enabled_but_not_set()
|
||||
if unset:
|
||||
raise errors.MFAUnsetError(user, self.request, url)
|
||||
|
@ -372,19 +388,29 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
self.request.session['auth_mfa_type'] = ''
|
||||
|
||||
def check_mfa_is_block(self, username, ip, raise_exception=True):
|
||||
if MFABlockUtils(username, ip).is_block():
|
||||
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
|
||||
exception = errors.BlockMFAError(username=username, request=self.request, ip=ip)
|
||||
if raise_exception:
|
||||
raise exception
|
||||
else:
|
||||
return exception
|
||||
blocked = MFABlockUtils(username, ip).is_block()
|
||||
if not blocked:
|
||||
return
|
||||
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
|
||||
exception = errors.BlockMFAError(username=username, request=self.request, ip=ip)
|
||||
if raise_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()
|
||||
self.check_mfa_is_block(user.username, ip)
|
||||
ok = user.check_mfa(code, mfa_type=mfa_type)
|
||||
|
||||
if ok:
|
||||
self.mark_mfa_ok()
|
||||
return
|
||||
|
@ -468,3 +494,31 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
if args:
|
||||
guard_url = "%s?%s" % (guard_url, args)
|
||||
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
|
||||
|
||||
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 django.conf import settings
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import random
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
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.utils import get_logger
|
||||
from common.exceptions import JMSException
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
<div class="col-sm-6">
|
||||
<div class="input-group-prepend">
|
||||
{% if audio %}
|
||||
<a title="{% trans "Play CAPTCHA as audio file" %}" href="{{ audio }}">
|
||||
<a title="{% trans "Play CAPTCHA as audio file" %}" href="{{ audio }}"></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% include "django/forms/widgets/multiwidget.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,23 +10,15 @@
|
|||
{{ JMS_TITLE }}
|
||||
</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% include '_head_css_js.html' %}
|
||||
<!-- 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/style.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/jumpserver.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- 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>
|
||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||
|
||||
<style>
|
||||
.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 {
|
||||
|
@ -49,17 +41,22 @@
|
|||
}
|
||||
|
||||
.login-content {
|
||||
height: 472px;
|
||||
width: 984px;
|
||||
height: 490px;
|
||||
width: 1066px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
margin-top: calc((100vh - 470px) / 3);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f2f2f2;
|
||||
height: calc(100vh - (100vh - 470px) / 3);
|
||||
}
|
||||
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.right-image-box {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
|
@ -73,18 +70,10 @@
|
|||
width: 50%;
|
||||
}
|
||||
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.red-fonts {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.field-error {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-group.has-error {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -109,14 +98,6 @@
|
|||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.radio, .checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#github_star {
|
||||
float: right;
|
||||
margin: 10px 10px 0 0;
|
||||
}
|
||||
.more-login-item {
|
||||
border-right: 1px dashed #dedede;
|
||||
padding-left: 5px;
|
||||
|
@ -127,11 +108,18 @@
|
|||
border: none;
|
||||
}
|
||||
|
||||
.select-con {
|
||||
width: 22%;
|
||||
}
|
||||
|
||||
.mfa-div {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-content ">
|
||||
<div class="login-content">
|
||||
<div class="right-image-box">
|
||||
<a href="{% if not XPACK_ENABLED %}https://github.com/jumpserver/jumpserver{% endif %}">
|
||||
<img src="{{ LOGIN_IMAGE_URL }}" style="height: 100%; width: 100%"/>
|
||||
|
@ -170,6 +158,10 @@
|
|||
</div>
|
||||
{% if form.challenge %}
|
||||
{% 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 %}
|
||||
<div class="captch-field">
|
||||
{% bootstrap_field form.captcha show_label=False %}
|
||||
|
@ -233,7 +225,7 @@
|
|||
var password = $('#password').val(); //明文密码
|
||||
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
|
||||
$('#password-hidden').val(passwordEncrypted); //返回给密码输入input
|
||||
$('#login-form').submit();//post提交
|
||||
$('#login-form').submit(); //post提交
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
|
|
@ -13,78 +13,19 @@
|
|||
<p class="red-fonts">{{ form.code.errors.as_text }}</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<select id="verify-method-select" name="mfa_type" class="form-control" onchange="select_change(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>
|
||||
{% include '_mfa_otp_login.html' %}
|
||||
</div>
|
||||
<div class="form-group" style="display: flex">
|
||||
|
||||
<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>
|
||||
<button id='submit_button' type="submit"
|
||||
class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||
<div>
|
||||
<small>{% trans "Can't provide security? Please contact the administrator!" %}</small>
|
||||
</div>
|
||||
</form>
|
||||
<style type="text/css">
|
||||
.disabledBtn {
|
||||
background: #e6e4e4!important;
|
||||
border-color: #d8d5d5!important;
|
||||
color: #949191!important;
|
||||
}
|
||||
.mfa-div {
|
||||
margin-top: 15px;
|
||||
}
|
||||
</style>
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
|
|
@ -13,7 +13,6 @@ router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-
|
|||
|
||||
|
||||
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/<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 ..forms import get_user_login_form_cls
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserLoginView', 'UserLogoutView',
|
||||
'UserLoginGuardView', 'UserLoginWaitConfirmView',
|
||||
|
@ -117,7 +116,6 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||
except errors.AuthFailedError as e:
|
||||
form.add_error(None, e.msg)
|
||||
self.set_login_failed_mark()
|
||||
|
||||
form_cls = get_user_login_form_cls(captcha=True)
|
||||
new_form = form_cls(data=form.data)
|
||||
new_form._errors = form.errors
|
||||
|
@ -125,11 +123,20 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||
self.request.session.set_test_cookie()
|
||||
return self.render_to_response(context)
|
||||
except (
|
||||
errors.PasswdTooSimple,
|
||||
errors.PasswordRequireResetError,
|
||||
errors.PasswdNeedUpdate
|
||||
errors.PasswdTooSimple,
|
||||
errors.PasswordRequireResetError,
|
||||
errors.PasswdNeedUpdate
|
||||
) as e:
|
||||
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()
|
||||
return self.redirect_to_guard_view()
|
||||
|
||||
|
@ -150,31 +157,31 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||
'name': 'OpenID',
|
||||
'enabled': settings.AUTH_OPENID,
|
||||
'url': reverse('authentication:openid:login'),
|
||||
'logo': static('img/login_oidc_logo.png')
|
||||
'logo': static('img/login_oidc_logo.png')
|
||||
},
|
||||
{
|
||||
'name': 'CAS',
|
||||
'enabled': settings.AUTH_CAS,
|
||||
'url': reverse('authentication:cas:cas-login'),
|
||||
'logo': static('img/login_cas_logo.png')
|
||||
'logo': static('img/login_cas_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _('WeCom'),
|
||||
'enabled': settings.AUTH_WECOM,
|
||||
'url': reverse('authentication:wecom-qr-login'),
|
||||
'logo': static('img/login_wecom_logo.png')
|
||||
'logo': static('img/login_wecom_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _('DingTalk'),
|
||||
'enabled': settings.AUTH_DINGTALK,
|
||||
'url': reverse('authentication:dingtalk-qr-login'),
|
||||
'logo': static('img/login_dingtalk_logo.png')
|
||||
'logo': static('img/login_dingtalk_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _('FeiShu'),
|
||||
'enabled': settings.AUTH_FEISHU,
|
||||
'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']]
|
||||
|
@ -191,7 +198,8 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||
context = {
|
||||
'demo_mode': os.environ.get("DEMO_MODE"),
|
||||
'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)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -20,11 +20,11 @@ class UserLoginOtpView(mixins.AuthMixin, FormView):
|
|||
redirect_field_name = 'next'
|
||||
|
||||
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')
|
||||
|
||||
try:
|
||||
self.check_user_mfa(otp_code, mfa_type)
|
||||
self.check_user_mfa(code, mfa_type)
|
||||
return redirect_to_guard_view()
|
||||
except (errors.MFAFailedError, errors.BlockMFAError) as e:
|
||||
form.add_error('code', e.msg)
|
||||
|
@ -37,26 +37,6 @@ class UserLoginOtpView(mixins.AuthMixin, FormView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
user = self.get_user_from_session()
|
||||
context = {
|
||||
'methods': [
|
||||
{
|
||||
'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
|
||||
methods = self.get_user_mfa_methods(user)
|
||||
kwargs.update({'methods': methods})
|
||||
return kwargs
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
|
|
|
@ -295,6 +295,7 @@ class Config(dict):
|
|||
'SECURITY_PASSWORD_LOWER_CASE': False,
|
||||
'SECURITY_PASSWORD_NUMBER': False,
|
||||
'SECURITY_PASSWORD_SPECIAL_CHAR': False,
|
||||
'SECURITY_MFA_IN_LOGIN_PAGE': False,
|
||||
'SECURITY_LOGIN_CHALLENGE_ENABLED': False,
|
||||
'SECURITY_LOGIN_CAPTCHA_ENABLED': True,
|
||||
'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_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION
|
||||
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_DATA_CRYPTO_ALGO = CONFIG.SECURITY_DATA_CRYPTO_ALGO
|
||||
SECURITY_INSECURE_COMMAND = CONFIG.SECURITY_INSECURE_COMMAND
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\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"
|
||||
"Last-Translator: ibuler <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
|
||||
#: settings/serializers/sms.py:6 terminal/models/storage.py:23
|
||||
#: 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/user_asset_permission.html:37
|
||||
#: 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
|
||||
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
|
||||
#: 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/gathered_user/models.py:26
|
||||
msgid "Comment"
|
||||
|
@ -97,8 +97,8 @@ msgstr "动作"
|
|||
#: perms/models/base.py:45 templates/index.html:78
|
||||
#: terminal/backends/command/models.py:18
|
||||
#: 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
|
||||
#: users/models/user.py:814 users/models/user.py:840
|
||||
#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:180
|
||||
#: users/models/user.py:813 users/models/user.py:839
|
||||
#: users/serializers/group.py:19
|
||||
#: users/templates/users/user_asset_permission.html:38
|
||||
#: users/templates/users/user_asset_permission.html:64
|
||||
|
@ -177,7 +177,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
|
|||
#: applications/serializers/attrs/application_type/vmware_client.py:26
|
||||
#: assets/models/base.py:176 assets/models/gathered_user.py:15
|
||||
#: 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
|
||||
#: xpack/plugins/change_auth_plan/models/asset.py:35
|
||||
#: xpack/plugins/change_auth_plan/models/asset.py:191
|
||||
|
@ -377,7 +377,7 @@ msgstr "目标URL"
|
|||
#: applications/serializers/attrs/application_type/vmware_client.py:30
|
||||
#: assets/models/base.py:177 audits/signals_handler.py:65
|
||||
#: 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
|
||||
#: users/templates/users/user_otp_check_password.html:13
|
||||
#: 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/cmd_filter.py:67 assets/models/group.py:21
|
||||
#: 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
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:45
|
||||
#: 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
|
||||
#: 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
|
||||
#: 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"
|
||||
msgstr "创建日期"
|
||||
|
||||
|
@ -599,7 +599,7 @@ msgstr "带宽"
|
|||
msgid "Contact"
|
||||
msgstr "联系人"
|
||||
|
||||
#: assets/models/cluster.py:22 users/models/user.py:624
|
||||
#: assets/models/cluster.py:22 users/models/user.py:623
|
||||
msgid "Phone"
|
||||
msgstr "手机"
|
||||
|
||||
|
@ -625,7 +625,7 @@ msgid "Default"
|
|||
msgstr "默认"
|
||||
|
||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||
#: users/models/user.py:826
|
||||
#: users/models/user.py:825
|
||||
msgid "System"
|
||||
msgstr "系统"
|
||||
|
||||
|
@ -1161,7 +1161,7 @@ msgstr "用户代理"
|
|||
#: audits/models.py:110
|
||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
||||
#: 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
|
||||
msgid "MFA"
|
||||
msgstr "多因子认证"
|
||||
|
@ -1239,13 +1239,13 @@ msgstr ""
|
|||
msgid "Auth Token"
|
||||
msgstr "认证令牌"
|
||||
|
||||
#: audits/signals_handler.py:68 authentication/views/login.py:160
|
||||
#: notifications/backends/__init__.py:11 users/models/user.py:660
|
||||
#: audits/signals_handler.py:68 authentication/views/login.py:169
|
||||
#: notifications/backends/__init__.py:11 users/models/user.py:659
|
||||
msgid "WeCom"
|
||||
msgstr "企业微信"
|
||||
|
||||
#: audits/signals_handler.py:69 authentication/views/login.py:166
|
||||
#: notifications/backends/__init__.py:12 users/models/user.py:661
|
||||
#: audits/signals_handler.py:69 authentication/views/login.py:175
|
||||
#: notifications/backends/__init__.py:12 users/models/user.py:660
|
||||
msgid "DingTalk"
|
||||
msgstr "钉钉"
|
||||
|
||||
|
@ -1432,7 +1432,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}"
|
|||
msgid "{ApplicationPermission} REMOVE {SystemUser}"
|
||||
msgstr "{ApplicationPermission} 移除 {SystemUser}"
|
||||
|
||||
#: authentication/api/connection_token.py:227
|
||||
#: authentication/api/connection_token.py:235
|
||||
msgid "Invalid token"
|
||||
msgstr "无效的令牌"
|
||||
|
||||
|
@ -1596,41 +1596,51 @@ msgid "MFA not set, please set it first"
|
|||
msgstr "多因子认证没有设置,请先完成设置"
|
||||
|
||||
#: authentication/errors.py:81
|
||||
msgid "OTP not set, please set it first"
|
||||
msgstr "OTP认证没有设置,请先完成设置"
|
||||
|
||||
#: authentication/errors.py:82
|
||||
msgid "Login confirm required"
|
||||
msgstr "需要登录复核"
|
||||
|
||||
#: authentication/errors.py:82
|
||||
#: authentication/errors.py:83
|
||||
msgid "Wait login confirm ticket for accept"
|
||||
msgstr "等待登录复核处理"
|
||||
|
||||
#: authentication/errors.py:83
|
||||
#: authentication/errors.py:84
|
||||
msgid "Login confirm ticket was {}"
|
||||
msgstr "登录复核 {}"
|
||||
|
||||
#: authentication/errors.py:260
|
||||
#: authentication/errors.py:261
|
||||
msgid "IP is not allowed"
|
||||
msgstr "来源 IP 不被允许登录"
|
||||
|
||||
#: authentication/errors.py:293
|
||||
#: authentication/errors.py:294
|
||||
msgid "SSO auth closed"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
msgstr "您的密码已过期,先修改再登录"
|
||||
|
||||
#: authentication/errors.py:350
|
||||
#: authentication/errors.py:351
|
||||
msgid "Your password is invalid"
|
||||
msgstr "您的密码无效"
|
||||
|
||||
#: authentication/errors.py:357
|
||||
#, fuzzy
|
||||
#| msgid "Not has host {} permission"
|
||||
msgid "No upload or download permission"
|
||||
msgstr "没有该主机 {} 权限"
|
||||
|
||||
#: authentication/forms.py:35
|
||||
msgid "{} days auto login"
|
||||
msgstr "{} 天内自动登录"
|
||||
|
@ -1643,12 +1653,15 @@ msgstr "MFA 验证码"
|
|||
msgid "MFA type"
|
||||
msgstr "MFA 类型"
|
||||
|
||||
#: authentication/forms.py:60 authentication/forms.py:62
|
||||
#: users/forms/profile.py:27
|
||||
#: authentication/forms.py:60 users/forms/profile.py:27
|
||||
msgid "MFA code"
|
||||
msgstr "多因子认证验证码"
|
||||
|
||||
#: authentication/mixins.py:311
|
||||
#: authentication/forms.py:62
|
||||
msgid "Dynamic code"
|
||||
msgstr "动态码"
|
||||
|
||||
#: authentication/mixins.py:328
|
||||
msgid "Please change your password"
|
||||
msgstr "请修改密码"
|
||||
|
||||
|
@ -1656,7 +1669,7 @@ msgstr "请修改密码"
|
|||
msgid "Private Token"
|
||||
msgstr "SSH密钥"
|
||||
|
||||
#: authentication/models.py:49 settings/serializers/security.py:118
|
||||
#: authentication/models.py:49 settings/serializers/security.py:144
|
||||
msgid "Login Confirm"
|
||||
msgstr "登录复核"
|
||||
|
||||
|
@ -1709,15 +1722,15 @@ msgstr ""
|
|||
"若怀疑此次登录行为异常,请及时修改账号密码。\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"
|
||||
msgstr "验证码已过期,请重新发送"
|
||||
|
||||
#: authentication/sms_verify_code.py:22
|
||||
#: authentication/sms_verify_code.py:20
|
||||
msgid "The verification code is incorrect"
|
||||
msgstr "验证码错误"
|
||||
|
||||
#: authentication/sms_verify_code.py:27
|
||||
#: authentication/sms_verify_code.py:25
|
||||
msgid "Please wait {} seconds before sending"
|
||||
msgstr "请在 {} 秒后发送"
|
||||
|
||||
|
@ -1753,14 +1766,14 @@ msgid "Show"
|
|||
msgstr "显示"
|
||||
|
||||
#: 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/templates/users/user_verify_mfa.html:32
|
||||
msgid "Disable"
|
||||
msgstr "禁用"
|
||||
|
||||
#: 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"
|
||||
msgstr "启用"
|
||||
|
||||
|
@ -1800,35 +1813,26 @@ msgstr "确认"
|
|||
msgid "Code error"
|
||||
msgstr "代码错误"
|
||||
|
||||
#: authentication/templates/authentication/login.html:155
|
||||
#: authentication/templates/authentication/login.html:143
|
||||
msgid "Welcome back, please enter username and password to login"
|
||||
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:16
|
||||
msgid "Forgot password"
|
||||
msgstr "忘记密码"
|
||||
|
||||
#: authentication/templates/authentication/login.html:194
|
||||
#: authentication/templates/authentication/login.html:186
|
||||
#: templates/_header_bar.html:83
|
||||
msgid "Login"
|
||||
msgstr "登录"
|
||||
|
||||
#: authentication/templates/authentication/login.html:201
|
||||
#: authentication/templates/authentication/login.html:193
|
||||
msgid "More login options"
|
||||
msgstr "更多登录方式"
|
||||
|
||||
#: authentication/templates/authentication/login_otp.html:24
|
||||
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
|
||||
#: authentication/templates/authentication/login_otp.html:19
|
||||
#: users/templates/users/user_otp_check_password.html:16
|
||||
#: users/templates/users/user_otp_enable_bind.html:24
|
||||
#: users/templates/users/user_otp_enable_install_app.html:29
|
||||
|
@ -1836,19 +1840,10 @@ msgstr "发送验证码"
|
|||
msgid "Next"
|
||||
msgstr "下一步"
|
||||
|
||||
#: authentication/templates/authentication/login_otp.html:31
|
||||
#: authentication/templates/authentication/login_otp.html:21
|
||||
msgid "Can't provide security? Please contact the administrator!"
|
||||
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
|
||||
msgid "Refresh"
|
||||
msgstr "刷新"
|
||||
|
@ -1957,24 +1952,28 @@ msgstr "请使用密码登录,然后绑定飞书"
|
|||
msgid "Binding FeiShu failed"
|
||||
msgstr "绑定飞书失败"
|
||||
|
||||
#: authentication/views/login.py:81
|
||||
#: authentication/views/login.py:82
|
||||
msgid "Redirecting"
|
||||
msgstr "跳转中"
|
||||
|
||||
#: authentication/views/login.py:82
|
||||
#: authentication/views/login.py:83
|
||||
msgid "Redirecting to {} authentication"
|
||||
msgstr "正在跳转到 {} 认证"
|
||||
|
||||
#: authentication/views/login.py:108
|
||||
#: authentication/views/login.py:109
|
||||
msgid "Please enable cookies and try again."
|
||||
msgstr "设置你的浏览器支持cookie"
|
||||
|
||||
#: authentication/views/login.py:172 notifications/backends/__init__.py:14
|
||||
#: users/models/user.py:662
|
||||
#: authentication/views/login.py:181 notifications/backends/__init__.py:14
|
||||
#: users/models/user.py:661
|
||||
msgid "FeiShu"
|
||||
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 ""
|
||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||
" Don't close this page"
|
||||
|
@ -1982,26 +1981,18 @@ msgstr ""
|
|||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||
" 不要关闭本页面"
|
||||
|
||||
#: authentication/views/login.py:264
|
||||
#: authentication/views/login.py:296
|
||||
msgid "No ticket found"
|
||||
msgstr "没有发现工单"
|
||||
|
||||
#: authentication/views/login.py:296
|
||||
#: authentication/views/login.py:328
|
||||
msgid "Logout success"
|
||||
msgstr "退出登录成功"
|
||||
|
||||
#: authentication/views/login.py:297
|
||||
#: authentication/views/login.py:329
|
||||
msgid "Logout success, return login page"
|
||||
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
|
||||
msgid "WeCom Error, Please contact your system administrator"
|
||||
msgstr "企业微信错误,请联系系统管理员"
|
||||
|
@ -2218,7 +2209,7 @@ msgstr ""
|
|||
"div>"
|
||||
|
||||
#: notifications/backends/__init__.py:10 users/forms/profile.py:101
|
||||
#: users/models/user.py:607
|
||||
#: users/models/user.py:606
|
||||
msgid "Email"
|
||||
msgstr "邮件"
|
||||
|
||||
|
@ -2436,7 +2427,7 @@ msgstr "组织审计员"
|
|||
msgid "GLOBAL"
|
||||
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
|
||||
msgid "Role"
|
||||
msgstr "角色"
|
||||
|
@ -2449,7 +2440,7 @@ msgstr "管理员正在修改授权,请稍等"
|
|||
msgid "The authorization cannot be revoked for the time being"
|
||||
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"
|
||||
msgstr "应用程序"
|
||||
|
||||
|
@ -2501,7 +2492,7 @@ msgid "Favorite"
|
|||
msgstr "收藏夹"
|
||||
|
||||
#: 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:67
|
||||
#: users/templates/users/user_database_app_permission.html:38
|
||||
|
@ -2512,7 +2503,7 @@ msgstr "用户组"
|
|||
#: perms/models/base.py:50
|
||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:60
|
||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50
|
||||
#: users/models/user.py:643
|
||||
#: users/models/user.py:642
|
||||
msgid "Date expired"
|
||||
msgstr "失效日期"
|
||||
|
||||
|
@ -2520,6 +2511,272 @@ msgstr "失效日期"
|
|||
msgid "From ticket"
|
||||
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:38
|
||||
#: perms/serializers/asset/permission.py:42
|
||||
|
@ -2885,7 +3142,7 @@ msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过
|
|||
msgid "SSO auth key TTL"
|
||||
msgstr "Token 有效期"
|
||||
|
||||
#: settings/serializers/auth/sso.py:16 settings/serializers/security.py:74
|
||||
#: settings/serializers/auth/sso.py:16
|
||||
msgid "Unit: second"
|
||||
msgstr "单位: 秒"
|
||||
|
||||
|
@ -3196,73 +3453,103 @@ msgstr "开启后,如果用户来源为本地,CAS、OIDC 登录将会失败"
|
|||
msgid "MFA verify TTL"
|
||||
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"
|
||||
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"
|
||||
msgstr "终端注册"
|
||||
|
||||
#: settings/serializers/security.py:85
|
||||
#: settings/serializers/security.py:111
|
||||
msgid ""
|
||||
"Allow terminal register, after all terminal setup, you should disable this "
|
||||
"for security"
|
||||
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
|
||||
|
||||
#: settings/serializers/security.py:88
|
||||
#: settings/serializers/security.py:114
|
||||
msgid "Replay watermark"
|
||||
msgstr "录像水印"
|
||||
|
||||
#: settings/serializers/security.py:89
|
||||
#: settings/serializers/security.py:115
|
||||
msgid "Enabled, the session replay contains watermark information"
|
||||
msgstr "启用后,会话录像将包含水印信息"
|
||||
|
||||
#: settings/serializers/security.py:93
|
||||
#: settings/serializers/security.py:119
|
||||
msgid "Connection max idle time"
|
||||
msgstr "连接最大空闲时间"
|
||||
|
||||
#: settings/serializers/security.py:94
|
||||
#: settings/serializers/security.py:120
|
||||
msgid "If idle time more than it, disconnect connection Unit: minute"
|
||||
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
|
||||
|
||||
#: settings/serializers/security.py:97
|
||||
#: settings/serializers/security.py:123
|
||||
msgid "Remember manual auth"
|
||||
msgstr "保存手动输入密码"
|
||||
|
||||
#: settings/serializers/security.py:100
|
||||
#: settings/serializers/security.py:126
|
||||
msgid "Enable change auth secure mode"
|
||||
msgstr "启用改密安全模式"
|
||||
|
||||
#: settings/serializers/security.py:103
|
||||
#: settings/serializers/security.py:129
|
||||
msgid "Insecure command alert"
|
||||
msgstr "危险命令告警"
|
||||
|
||||
#: settings/serializers/security.py:106
|
||||
#: settings/serializers/security.py:132
|
||||
msgid "Email recipient"
|
||||
msgstr "邮件收件人"
|
||||
|
||||
#: settings/serializers/security.py:107
|
||||
#: settings/serializers/security.py:133
|
||||
msgid "Multiple user using , split"
|
||||
msgstr "多个用户,使用 , 分割"
|
||||
|
||||
#: settings/serializers/security.py:110
|
||||
#: settings/serializers/security.py:136
|
||||
msgid "Batch command execution"
|
||||
msgstr "批量命令执行"
|
||||
|
||||
#: settings/serializers/security.py:111
|
||||
#: settings/serializers/security.py:137
|
||||
msgid "Allow user run batch command or not using ansible"
|
||||
msgstr "是否允许用户使用 ansible 执行批量命令"
|
||||
|
||||
#: settings/serializers/security.py:114
|
||||
#: settings/serializers/security.py:140
|
||||
msgid "Session share"
|
||||
msgstr "会话分享"
|
||||
|
||||
#: settings/serializers/security.py:115
|
||||
#: settings/serializers/security.py:141
|
||||
msgid "Enabled, Allows user active session to be shared with other users"
|
||||
msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作"
|
||||
|
||||
#: settings/serializers/security.py:119
|
||||
#: settings/serializers/security.py:145
|
||||
msgid "Enabled, please go to the user detail add approver"
|
||||
msgstr "启用后, 请在用户详情中添加审批人"
|
||||
|
||||
|
@ -3576,6 +3863,30 @@ msgstr ""
|
|||
"\"%(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
|
||||
msgid "Dashboard"
|
||||
msgstr "仪表盘"
|
||||
|
@ -4711,11 +5022,11 @@ msgstr "当前组织已存在该类型"
|
|||
msgid "Could not reset self otp, use profile reset instead"
|
||||
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
|
||||
|
||||
#: users/const.py:10 users/models/user.py:179
|
||||
#: users/const.py:10 users/models/user.py:178
|
||||
msgid "System administrator"
|
||||
msgstr "系统管理员"
|
||||
|
||||
#: users/const.py:11 users/models/user.py:180
|
||||
#: users/const.py:11 users/models/user.py:179
|
||||
msgid "System auditor"
|
||||
msgstr "系统审计员"
|
||||
|
||||
|
@ -4810,52 +5121,56 @@ msgstr "不能和原来的密钥相同"
|
|||
msgid "Not a valid ssh public key"
|
||||
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
|
||||
msgid "Public key"
|
||||
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"
|
||||
msgstr "短信验证码"
|
||||
|
||||
#: users/models/user.py:472
|
||||
#: users/models/user.py:471
|
||||
msgid "Force enable"
|
||||
msgstr "强制启用"
|
||||
|
||||
#: users/models/user.py:584
|
||||
#: users/models/user.py:583
|
||||
msgid "Local"
|
||||
msgstr "数据库"
|
||||
|
||||
#: users/models/user.py:618
|
||||
#: users/models/user.py:617
|
||||
msgid "Avatar"
|
||||
msgstr "头像"
|
||||
|
||||
#: users/models/user.py:621
|
||||
#: users/models/user.py:620
|
||||
msgid "Wechat"
|
||||
msgstr "微信"
|
||||
|
||||
#: users/models/user.py:632
|
||||
#: users/models/user.py:631
|
||||
msgid "Private key"
|
||||
msgstr "ssh私钥"
|
||||
|
||||
#: users/models/user.py:651
|
||||
#: users/models/user.py:650
|
||||
msgid "Source"
|
||||
msgstr "来源"
|
||||
|
||||
#: users/models/user.py:655
|
||||
#: users/models/user.py:654
|
||||
msgid "Date password last updated"
|
||||
msgstr "最后更新密码日期"
|
||||
|
||||
#: users/models/user.py:658
|
||||
#: users/models/user.py:657
|
||||
msgid "Need update password"
|
||||
msgstr "需要更新密码"
|
||||
|
||||
#: users/models/user.py:822
|
||||
#: users/models/user.py:821
|
||||
msgid "Administrator"
|
||||
msgstr "管理员"
|
||||
|
||||
#: users/models/user.py:825
|
||||
#: users/models/user.py:824
|
||||
msgid "Administrator is the super user of system"
|
||||
msgstr "Administrator是初始的超级管理员"
|
||||
|
||||
|
@ -6320,6 +6635,12 @@ msgstr "旗舰版"
|
|||
msgid "Community edition"
|
||||
msgstr "社区版"
|
||||
|
||||
#~ msgid "Enable Login MFA"
|
||||
#~ msgstr "启用登录MFA"
|
||||
|
||||
#~ msgid "Enable login password add-on"
|
||||
#~ msgstr "启用登录密码附加码"
|
||||
|
||||
#~ msgid "OpenID"
|
||||
#~ msgstr "OpenID"
|
||||
|
||||
|
|
|
@ -70,14 +70,40 @@ class SecurityAuthSerializer(serializers.Serializer):
|
|||
help_text=_("Only log in from the user source property")
|
||||
)
|
||||
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(
|
||||
min_value=5, max_value=60*60*10,
|
||||
label=_("MFA verify TTL"), help_text=_("Unit: second"),
|
||||
min_value=5, max_value=60 * 60 * 10,
|
||||
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(
|
||||
required=False, default=True,
|
||||
label=_("Enable Login captcha")
|
||||
required=False, default=False, 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):
|
||||
SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(
|
||||
|
@ -118,4 +144,3 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri
|
|||
required=False, label=_('Login Confirm'),
|
||||
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.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.db import models
|
||||
from django.db.models import TextChoices
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
|
Loading…
Reference in New Issue