mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-07-04 18:46:39 +00:00
Merge branch 'dev' into pr@dev@connectivity_choice
This commit is contained in:
commit
dd5bcab4ff
@ -1,9 +1,12 @@
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext as _
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
from authentication.mixins import AuthMixin
|
||||
from common.api import JMSModelViewSet
|
||||
@ -44,6 +47,9 @@ class PasskeyViewSet(AuthMixin, FlashMessageMixin, JMSModelViewSet):
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='login', permission_classes=[AllowAny])
|
||||
def login(self, request):
|
||||
confirm_mfa = request.GET.get('mfa')
|
||||
if confirm_mfa:
|
||||
request.session['passkey_confirm_mfa'] = '1'
|
||||
return render(request, 'authentication/passkey.html', {})
|
||||
|
||||
def redirect_to_error(self, error):
|
||||
@ -64,8 +70,16 @@ class PasskeyViewSet(AuthMixin, FlashMessageMixin, JMSModelViewSet):
|
||||
if not user:
|
||||
return self.redirect_to_error(_('Auth failed'))
|
||||
|
||||
confirm_mfa = request.session.get('passkey_confirm_mfa')
|
||||
if confirm_mfa:
|
||||
request.session['CONFIRM_LEVEL'] = ConfirmType.values.index('mfa') + 1
|
||||
request.session['CONFIRM_TIME'] = int(time.time())
|
||||
request.session['passkey_confirm_mfa'] = ''
|
||||
return Response('ok')
|
||||
|
||||
try:
|
||||
self.check_oauth2_auth(user, settings.AUTH_BACKEND_PASSKEY)
|
||||
self.mark_mfa_ok('passkey', user)
|
||||
return self.redirect_to_guard_view()
|
||||
except Exception as e:
|
||||
msg = getattr(e, 'msg', '') or str(e)
|
||||
|
@ -34,6 +34,7 @@ class MFAType(TextChoices):
|
||||
Email = 'email', _('Email')
|
||||
Face = 'face', _('Face Recognition')
|
||||
Radius = 'otp_radius', _('Radius')
|
||||
Passkey = 'passkey', _('Passkey')
|
||||
Custom = 'mfa_custom', _('Custom')
|
||||
|
||||
|
||||
|
@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
class BaseMFA(abc.ABC):
|
||||
placeholder = _('Please input security code')
|
||||
skip_cache_check = False
|
||||
has_code = True
|
||||
|
||||
def __init__(self, user):
|
||||
"""
|
||||
|
@ -11,6 +11,7 @@ class MFAFace(BaseMFA, AuthFaceMixin):
|
||||
display_name = MFAType.Face.name
|
||||
placeholder = 'Face Recognition'
|
||||
skip_cache_check = True
|
||||
has_code = False
|
||||
|
||||
def _check_code(self, code):
|
||||
assert self.is_authenticated()
|
||||
|
@ -49,4 +49,3 @@ class MFAOtp(BaseMFA):
|
||||
|
||||
def help_text_of_disable(self):
|
||||
return ''
|
||||
|
||||
|
46
apps/authentication/mfa/passkey.py
Normal file
46
apps/authentication/mfa/passkey.py
Normal file
@ -0,0 +1,46 @@
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentication.mfa.base import BaseMFA
|
||||
from ..const import MFAType
|
||||
|
||||
|
||||
class MFAPasskey(BaseMFA):
|
||||
name = MFAType.Passkey.value
|
||||
display_name = MFAType.Passkey.name
|
||||
placeholder = 'Passkey'
|
||||
has_code = False
|
||||
|
||||
def _check_code(self, code):
|
||||
assert self.is_authenticated()
|
||||
|
||||
return False, ''
|
||||
|
||||
def is_active(self):
|
||||
if not self.is_authenticated():
|
||||
return True
|
||||
return self.user.passkey_set.count()
|
||||
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return settings.AUTH_PASSKEY
|
||||
|
||||
def get_enable_url(self) -> str:
|
||||
return '/ui/#/profile/passkeys'
|
||||
|
||||
def get_disable_url(self) -> str:
|
||||
return '/ui/#/profile/passkeys'
|
||||
|
||||
def disable(self):
|
||||
pass
|
||||
|
||||
def can_disable(self) -> bool:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def help_text_of_enable():
|
||||
return _("Using passkey as MFA")
|
||||
|
||||
@staticmethod
|
||||
def help_text_of_disable():
|
||||
return _("Using passkey as MFA")
|
@ -5,12 +5,13 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login passkey</title>
|
||||
<script src="{% static "js/jquery-3.6.1.min.js" %}?_=9"></script>
|
||||
<script src="{% static 'js/jquery-3.6.1.min.js' %}?_=9"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form action='{% url 'api-auth:passkey-auth' %}' method="post" id="loginForm">
|
||||
<input type="hidden" name="passkeys" id="passkeys"/>
|
||||
</form>
|
||||
<form action="{% url 'api-auth:passkey-auth' %}" method="post" id="loginForm">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="passkeys" id="passkeys"/>
|
||||
</form>
|
||||
</body>
|
||||
<script>
|
||||
const loginUrl = "/core/auth/login/";
|
||||
|
@ -5,11 +5,14 @@ from datetime import datetime, timedelta
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import reverse
|
||||
from django.templatetags.static import static
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from audits.models import UserLoginLog
|
||||
from common.utils import get_ip_city, get_request_ip
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.utils import static_or_direct
|
||||
from users.models import User
|
||||
from .notifications import DifferentCityLoginMessage
|
||||
|
||||
@ -75,3 +78,72 @@ def check_user_property_is_correct(username, **properties):
|
||||
user = None
|
||||
break
|
||||
return user
|
||||
|
||||
|
||||
def get_auth_methods():
|
||||
return [
|
||||
{
|
||||
'name': 'OpenID',
|
||||
'enabled': settings.AUTH_OPENID,
|
||||
'url': reverse('authentication:openid:login'),
|
||||
'logo': static('img/login_oidc_logo.png'),
|
||||
'auto_redirect': True # 是否支持自动重定向
|
||||
},
|
||||
{
|
||||
'name': 'CAS',
|
||||
'enabled': settings.AUTH_CAS,
|
||||
'url': reverse('authentication:cas:cas-login'),
|
||||
'logo': static('img/login_cas_logo.png'),
|
||||
'auto_redirect': True
|
||||
},
|
||||
{
|
||||
'name': 'SAML2',
|
||||
'enabled': settings.AUTH_SAML2,
|
||||
'url': reverse('authentication:saml2:saml2-login'),
|
||||
'logo': static('img/login_saml2_logo.png'),
|
||||
'auto_redirect': True
|
||||
},
|
||||
{
|
||||
'name': settings.AUTH_OAUTH2_PROVIDER,
|
||||
'enabled': settings.AUTH_OAUTH2,
|
||||
'url': reverse('authentication:oauth2:login'),
|
||||
'logo': static_or_direct(settings.AUTH_OAUTH2_LOGO_PATH),
|
||||
'auto_redirect': True
|
||||
},
|
||||
{
|
||||
'name': _('WeCom'),
|
||||
'enabled': settings.AUTH_WECOM,
|
||||
'url': reverse('authentication:wecom-qr-login'),
|
||||
'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')
|
||||
},
|
||||
{
|
||||
'name': _('FeiShu'),
|
||||
'enabled': settings.AUTH_FEISHU,
|
||||
'url': reverse('authentication:feishu-qr-login'),
|
||||
'logo': static('img/login_feishu_logo.png')
|
||||
},
|
||||
{
|
||||
'name': 'Lark',
|
||||
'enabled': settings.AUTH_LARK,
|
||||
'url': reverse('authentication:lark-qr-login'),
|
||||
'logo': static('img/login_lark_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _('Slack'),
|
||||
'enabled': settings.AUTH_SLACK,
|
||||
'url': reverse('authentication:slack-qr-login'),
|
||||
'logo': static('img/login_slack_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _("Passkey"),
|
||||
'enabled': settings.AUTH_PASSKEY,
|
||||
'url': reverse('api-auth:passkey-login'),
|
||||
'logo': static('img/login_passkey.png')
|
||||
}
|
||||
]
|
||||
|
@ -14,7 +14,6 @@ from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||
from django.db import IntegrityError
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import gettext as _, get_language
|
||||
@ -25,13 +24,14 @@ from django.views.generic.base import TemplateView, RedirectView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from common.const import Language
|
||||
from common.utils import FlashMessageUtil, static_or_direct, safe_next_url
|
||||
from common.utils import FlashMessageUtil, safe_next_url
|
||||
from users.utils import (
|
||||
redirect_user_first_login_or_index
|
||||
)
|
||||
from .. import mixins, errors
|
||||
from ..const import RSA_PRIVATE_KEY, RSA_PUBLIC_KEY
|
||||
from ..forms import get_user_login_form_cls
|
||||
from ..utils import get_auth_methods
|
||||
|
||||
__all__ = [
|
||||
'UserLoginView', 'UserLogoutView',
|
||||
@ -46,73 +46,17 @@ class UserLoginContextMixin:
|
||||
|
||||
def get_support_auth_methods(self):
|
||||
query_string = self.request.GET.urlencode()
|
||||
auth_methods = [
|
||||
{
|
||||
'name': 'OpenID',
|
||||
'enabled': settings.AUTH_OPENID,
|
||||
'url': f"{reverse('authentication:openid:login')}?{query_string}",
|
||||
'logo': static('img/login_oidc_logo.png'),
|
||||
'auto_redirect': True # 是否支持自动重定向
|
||||
},
|
||||
{
|
||||
'name': 'CAS',
|
||||
'enabled': settings.AUTH_CAS,
|
||||
'url': f"{reverse('authentication:cas:cas-login')}?{query_string}",
|
||||
'logo': static('img/login_cas_logo.png'),
|
||||
'auto_redirect': True
|
||||
},
|
||||
{
|
||||
'name': 'SAML2',
|
||||
'enabled': settings.AUTH_SAML2,
|
||||
'url': f"{reverse('authentication:saml2:saml2-login')}?{query_string}",
|
||||
'logo': static('img/login_saml2_logo.png'),
|
||||
'auto_redirect': True
|
||||
},
|
||||
{
|
||||
'name': settings.AUTH_OAUTH2_PROVIDER,
|
||||
'enabled': settings.AUTH_OAUTH2,
|
||||
'url': f"{reverse('authentication:oauth2:login')}?{query_string}",
|
||||
'logo': static_or_direct(settings.AUTH_OAUTH2_LOGO_PATH),
|
||||
'auto_redirect': True
|
||||
},
|
||||
{
|
||||
'name': _('WeCom'),
|
||||
'enabled': settings.AUTH_WECOM,
|
||||
'url': f"{reverse('authentication:wecom-qr-login')}?{query_string}",
|
||||
'logo': static('img/login_wecom_logo.png'),
|
||||
},
|
||||
{
|
||||
'name': _('DingTalk'),
|
||||
'enabled': settings.AUTH_DINGTALK,
|
||||
'url': f"{reverse('authentication:dingtalk-qr-login')}?{query_string}",
|
||||
'logo': static('img/login_dingtalk_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _('FeiShu'),
|
||||
'enabled': settings.AUTH_FEISHU,
|
||||
'url': f"{reverse('authentication:feishu-qr-login')}?{query_string}",
|
||||
'logo': static('img/login_feishu_logo.png')
|
||||
},
|
||||
{
|
||||
'name': 'Lark',
|
||||
'enabled': settings.AUTH_LARK,
|
||||
'url': f"{reverse('authentication:lark-qr-login')}?{query_string}",
|
||||
'logo': static('img/login_lark_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _('Slack'),
|
||||
'enabled': settings.AUTH_SLACK,
|
||||
'url': f"{reverse('authentication:slack-qr-login')}?{query_string}",
|
||||
'logo': static('img/login_slack_logo.png')
|
||||
},
|
||||
{
|
||||
'name': _("Passkey"),
|
||||
'enabled': settings.AUTH_PASSKEY,
|
||||
'url': f"{reverse('api-auth:passkey-login')}?{query_string}",
|
||||
'logo': static('img/login_passkey.png')
|
||||
}
|
||||
]
|
||||
return [method for method in auth_methods if method['enabled']]
|
||||
all_methods = get_auth_methods()
|
||||
methods = []
|
||||
for method in all_methods:
|
||||
method = method.copy()
|
||||
if not method.get('enabled', False):
|
||||
continue
|
||||
url = method.get('url', '')
|
||||
if query_string and url:
|
||||
method['url'] = '{}?{}'.format(url, query_string)
|
||||
methods.append(method)
|
||||
return methods
|
||||
|
||||
@staticmethod
|
||||
def get_support_langs():
|
||||
|
@ -40,6 +40,8 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
|
||||
|
||||
if mfa_type == MFAType.Face:
|
||||
return redirect(reverse('authentication:login-face-capture'))
|
||||
elif mfa_type == MFAType.Passkey:
|
||||
return redirect('/api/v1/authentication/passkeys/login/')
|
||||
return self.do_mfa_check(form, code, mfa_type)
|
||||
|
||||
def do_mfa_check(self, form, code, mfa_type):
|
||||
|
@ -136,6 +136,8 @@ msgstr ">>> 开始执行测试网关账号可连接性任务"
|
||||
#: users/templates/users/_msg_user_created.html:13
|
||||
#: users/templates/users/user_password_verify.html:18
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:27
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:89
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:96
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
@ -746,7 +748,7 @@ msgstr "状态"
|
||||
#: accounts/serializers/account/account.py:278
|
||||
#: accounts/templates/accounts/change_secret_failed_info.html:13
|
||||
#: assets/const/automation.py:9
|
||||
#: authentication/templates/authentication/passkey.html:173
|
||||
#: authentication/templates/authentication/passkey.html:174
|
||||
#: authentication/views/base.py:42 authentication/views/base.py:43
|
||||
#: authentication/views/base.py:44 common/const/choices.py:67
|
||||
#: settings/templates/ldap/_msg_import_ldap_user.html:26
|
||||
@ -855,6 +857,8 @@ msgstr "重复密码"
|
||||
#: users/serializers/profile.py:186
|
||||
#: users/templates/users/_msg_user_created.html:12
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:25
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:87
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:94
|
||||
msgid "Username"
|
||||
msgstr "用户名"
|
||||
|
||||
@ -1905,8 +1909,8 @@ msgid ""
|
||||
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (Domain name "
|
||||
"support)"
|
||||
msgstr ""
|
||||
"* 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:"
|
||||
"db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
|
||||
"* 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, "
|
||||
"2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
|
||||
|
||||
#: acls/serializers/base.py:41 assets/serializers/asset/host.py:19
|
||||
msgid "IP/Host"
|
||||
@ -1934,8 +1938,8 @@ msgid ""
|
||||
"With * indicating a match all. Such as: 192.168.10.1, 192.168.1.0/24, "
|
||||
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 "
|
||||
msgstr ""
|
||||
"* 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:"
|
||||
"db8:2de::e13, 2001:db8:1a:1110::/64"
|
||||
"* 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, "
|
||||
"2001:db8:2de::e13, 2001:db8:1a:1110::/64"
|
||||
|
||||
#: acls/serializers/rules/rules.py:33
|
||||
#: authentication/templates/authentication/_msg_oauth_bind.html:12
|
||||
@ -2179,7 +2183,8 @@ msgstr "云服务"
|
||||
msgid "Web"
|
||||
msgstr "Web"
|
||||
|
||||
#: assets/const/category.py:16 common/sdk/sms/endpoint.py:20
|
||||
#: assets/const/category.py:16 common/sdk/sms/custom_file.py:47
|
||||
#: common/sdk/sms/endpoint.py:20
|
||||
msgid "Custom type"
|
||||
msgstr "自定义类型"
|
||||
|
||||
@ -2286,7 +2291,7 @@ msgid "Any"
|
||||
msgstr "任意"
|
||||
|
||||
#: assets/const/protocol.py:88 rbac/tree.py:62
|
||||
#: settings/serializers/security.py:245
|
||||
#: settings/serializers/security.py:266
|
||||
msgid "Security"
|
||||
msgstr "安全"
|
||||
|
||||
@ -2884,7 +2889,7 @@ msgstr "端口超出范围 (0-65535)"
|
||||
msgid "Protocol is required: {}"
|
||||
msgstr "协议是必填的: {}"
|
||||
|
||||
#: assets/serializers/asset/common.py:353
|
||||
#: assets/serializers/asset/common.py:353 labels/api.py:107
|
||||
msgid "Invalid data"
|
||||
msgstr "无效的数据"
|
||||
|
||||
@ -3553,29 +3558,29 @@ msgid "Auth Token"
|
||||
msgstr "认证令牌"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:37 authentication/notifications.py:73
|
||||
#: authentication/views/login.py:79 notifications/backends/__init__.py:11
|
||||
#: authentication/utils.py:114 notifications/backends/__init__.py:11
|
||||
#: settings/serializers/auth/wecom.py:11 settings/serializers/auth/wecom.py:16
|
||||
#: users/models/user/__init__.py:130 users/models/user/_source.py:19
|
||||
msgid "WeCom"
|
||||
msgstr "企业微信"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:38 authentication/views/feishu.py:97
|
||||
#: authentication/views/login.py:91 notifications/backends/__init__.py:14
|
||||
#: audits/signal_handlers/login_log.py:38 authentication/utils.py:126
|
||||
#: authentication/views/feishu.py:97 notifications/backends/__init__.py:14
|
||||
#: settings/serializers/auth/feishu.py:12
|
||||
#: settings/serializers/auth/feishu.py:14 users/models/user/__init__.py:136
|
||||
#: users/models/user/_source.py:21
|
||||
msgid "FeiShu"
|
||||
msgstr "飞书"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:40 authentication/views/login.py:103
|
||||
#: audits/signal_handlers/login_log.py:40 authentication/utils.py:138
|
||||
#: authentication/views/slack.py:79 notifications/backends/__init__.py:16
|
||||
#: settings/serializers/auth/slack.py:11 settings/serializers/auth/slack.py:13
|
||||
#: users/models/user/__init__.py:142 users/models/user/_source.py:23
|
||||
msgid "Slack"
|
||||
msgstr "Slack"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:41 authentication/views/dingtalk.py:151
|
||||
#: authentication/views/login.py:85 notifications/backends/__init__.py:12
|
||||
#: audits/signal_handlers/login_log.py:41 authentication/utils.py:120
|
||||
#: authentication/views/dingtalk.py:151 notifications/backends/__init__.py:12
|
||||
#: settings/serializers/auth/dingtalk.py:11 users/models/user/__init__.py:133
|
||||
#: users/models/user/_source.py:20
|
||||
msgid "DingTalk"
|
||||
@ -3586,8 +3591,8 @@ msgstr "钉钉"
|
||||
msgid "Temporary token"
|
||||
msgstr "临时密码"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:43 authentication/views/login.py:109
|
||||
#: settings/serializers/auth/passkey.py:8
|
||||
#: audits/signal_handlers/login_log.py:43 authentication/const.py:37
|
||||
#: authentication/utils.py:144 settings/serializers/auth/passkey.py:8
|
||||
#: settings/serializers/auth/passkey.py:11
|
||||
msgid "Passkey"
|
||||
msgstr "Passkey"
|
||||
@ -3671,8 +3676,8 @@ msgstr "ACL 动作是人脸在线"
|
||||
msgid "No available face feature"
|
||||
msgstr "没有可用的人脸特征"
|
||||
|
||||
#: authentication/api/face.py:100 authentication/mfa/face.py:21
|
||||
#: authentication/mfa/face.py:23 users/views/profile/face.py:72
|
||||
#: authentication/api/face.py:100 authentication/mfa/face.py:22
|
||||
#: authentication/mfa/face.py:24 users/views/profile/face.py:72
|
||||
msgid "Facial comparison failed"
|
||||
msgstr "人脸比对失败"
|
||||
|
||||
@ -3739,11 +3744,11 @@ msgstr "OpenID 错误"
|
||||
msgid "Please check if a user with the same username or email already exists"
|
||||
msgstr "请检查是否已经存在相同用户名或邮箱的用户"
|
||||
|
||||
#: authentication/backends/passkey/api.py:37
|
||||
#: authentication/backends/passkey/api.py:40
|
||||
msgid "Only register passkey for local user"
|
||||
msgstr "仅为本地用户注册密钥"
|
||||
|
||||
#: authentication/backends/passkey/api.py:65
|
||||
#: authentication/backends/passkey/api.py:71
|
||||
msgid "Auth failed"
|
||||
msgstr "认证失败"
|
||||
|
||||
@ -3793,7 +3798,7 @@ msgstr "人脸识别"
|
||||
msgid "Radius"
|
||||
msgstr "Radius"
|
||||
|
||||
#: authentication/const.py:37
|
||||
#: authentication/const.py:38
|
||||
msgid "Custom"
|
||||
msgstr "自定义"
|
||||
|
||||
@ -3993,7 +3998,7 @@ msgstr "动态码"
|
||||
msgid "Please input security code"
|
||||
msgstr "请输入动态安全码"
|
||||
|
||||
#: authentication/mfa/base.py:27
|
||||
#: authentication/mfa/base.py:28
|
||||
msgid ""
|
||||
"The two-factor code you entered has either already been used or has expired. "
|
||||
"Please request a new one."
|
||||
@ -4019,11 +4024,11 @@ msgstr "邮件验证码校验失败"
|
||||
msgid "Email verification code"
|
||||
msgstr "邮件验证码"
|
||||
|
||||
#: authentication/mfa/face.py:55
|
||||
#: authentication/mfa/face.py:56
|
||||
msgid "Bind face to enable"
|
||||
msgstr "绑定人脸特征以启用"
|
||||
|
||||
#: authentication/mfa/face.py:59
|
||||
#: authentication/mfa/face.py:60
|
||||
msgid "Unbind face to disable"
|
||||
msgstr "解绑人脸特征以禁用"
|
||||
|
||||
@ -4039,6 +4044,10 @@ msgstr "虚拟 MFA 验证码"
|
||||
msgid "Virtual OTP based MFA"
|
||||
msgstr "虚拟 MFA(OTP)"
|
||||
|
||||
#: authentication/mfa/passkey.py:42 authentication/mfa/passkey.py:46
|
||||
msgid "Using passkey as MFA"
|
||||
msgstr ""
|
||||
|
||||
#: authentication/mfa/radius.py:8
|
||||
msgid "Radius verify code invalid"
|
||||
msgstr "Radius 校验失败"
|
||||
@ -4537,17 +4546,17 @@ msgstr "返回"
|
||||
msgid "Copy success"
|
||||
msgstr "复制成功"
|
||||
|
||||
#: authentication/templates/authentication/passkey.html:162
|
||||
#: authentication/templates/authentication/passkey.html:163
|
||||
msgid ""
|
||||
"This page is not served over HTTPS. Please use HTTPS to ensure security of "
|
||||
"your credentials."
|
||||
msgstr "本页面未使用 HTTPS 协议,请使用 HTTPS 协议以确保您的凭据安全。"
|
||||
|
||||
#: authentication/templates/authentication/passkey.html:173
|
||||
#: authentication/templates/authentication/passkey.html:174
|
||||
msgid "Do you want to retry ?"
|
||||
msgstr "是否重试 ?"
|
||||
|
||||
#: authentication/utils.py:24 common/utils/ip/geoip/utils.py:24
|
||||
#: authentication/utils.py:27 common/utils/ip/geoip/utils.py:24
|
||||
#: xpack/plugins/cloud/const.py:33
|
||||
msgid "LAN"
|
||||
msgstr "局域网"
|
||||
@ -4632,23 +4641,23 @@ msgstr "Lark 已经绑定"
|
||||
msgid "Failed to get user from Lark"
|
||||
msgstr "从 Lark 获取用户失败"
|
||||
|
||||
#: authentication/views/login.py:219
|
||||
#: authentication/views/login.py:163
|
||||
msgid "Redirecting"
|
||||
msgstr "跳转中"
|
||||
|
||||
#: authentication/views/login.py:220
|
||||
#: authentication/views/login.py:164
|
||||
msgid "Redirecting to {} authentication"
|
||||
msgstr "正在跳转到 {} 认证"
|
||||
|
||||
#: authentication/views/login.py:247
|
||||
#: authentication/views/login.py:191
|
||||
msgid "Login timeout, please try again."
|
||||
msgstr "登录超时,请重新登录"
|
||||
|
||||
#: authentication/views/login.py:292
|
||||
#: authentication/views/login.py:236
|
||||
msgid "User email already exists ({})"
|
||||
msgstr "用户邮箱已存在 ({})"
|
||||
|
||||
#: authentication/views/login.py:370
|
||||
#: authentication/views/login.py:314
|
||||
msgid ""
|
||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||
" Don't close this page"
|
||||
@ -4656,15 +4665,15 @@ msgstr ""
|
||||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||
" 不要关闭本页面"
|
||||
|
||||
#: authentication/views/login.py:375
|
||||
#: authentication/views/login.py:319
|
||||
msgid "No ticket found"
|
||||
msgstr "没有发现工单"
|
||||
|
||||
#: authentication/views/login.py:411
|
||||
#: authentication/views/login.py:355
|
||||
msgid "Logout success"
|
||||
msgstr "退出登录成功"
|
||||
|
||||
#: authentication/views/login.py:412
|
||||
#: authentication/views/login.py:356
|
||||
msgid "Logout success, return login page"
|
||||
msgstr "退出登录成功,返回到登录页面"
|
||||
|
||||
@ -4759,7 +4768,7 @@ msgstr "企业专业版"
|
||||
msgid "Ultimate edition"
|
||||
msgstr "企业旗舰版"
|
||||
|
||||
#: common/const/common.py:5 xpack/plugins/cloud/manager.py:454
|
||||
#: common/const/common.py:5 xpack/plugins/cloud/manager.py:450
|
||||
#, python-format
|
||||
msgid "%(name)s was created successfully"
|
||||
msgstr "%(name)s 创建成功"
|
||||
@ -5033,6 +5042,10 @@ msgstr "自定义短信文件无效"
|
||||
msgid "SMS sending failed[%s]: %s"
|
||||
msgstr "短信发送失败[%s]: %s"
|
||||
|
||||
#: common/sdk/sms/custom_file.py:47 common/serializers/common.py:98
|
||||
msgid "File"
|
||||
msgstr "文件"
|
||||
|
||||
#: common/sdk/sms/endpoint.py:16
|
||||
msgid "Alibaba cloud"
|
||||
msgstr "阿里云"
|
||||
@ -5077,10 +5090,6 @@ msgstr "请在 {} 秒后发送"
|
||||
msgid "Children"
|
||||
msgstr "节点"
|
||||
|
||||
#: common/serializers/common.py:98
|
||||
msgid "File"
|
||||
msgstr "文件"
|
||||
|
||||
#: common/serializers/fields.py:139
|
||||
msgid "Invalid data type"
|
||||
msgstr "无效的数据"
|
||||
@ -5202,6 +5211,10 @@ msgstr "你的账号已创建成功"
|
||||
msgid "JumpServer - An open-source PAM"
|
||||
msgstr "JumpServer 开源堡垒机"
|
||||
|
||||
#: jumpserver/context_processor.py:28
|
||||
msgid "FIT2CLOUD"
|
||||
msgstr ""
|
||||
|
||||
#: jumpserver/views/celery_flower.py:22
|
||||
msgid "<h1>Flower service unavailable, check it</h1>"
|
||||
msgstr "Flower 服务不可用,请检查"
|
||||
@ -5233,7 +5246,7 @@ msgstr ""
|
||||
msgid "App Labels"
|
||||
msgstr "标签管理"
|
||||
|
||||
#: labels/models.py:15
|
||||
#: labels/models.py:15 settings/serializers/security.py:205
|
||||
msgid "Color"
|
||||
msgstr "颜色"
|
||||
|
||||
@ -7207,7 +7220,7 @@ msgstr "租户 ID"
|
||||
|
||||
#: settings/serializers/feature.py:110 terminal/serializers/storage.py:68
|
||||
#: xpack/plugins/cloud/manager.py:119 xpack/plugins/cloud/manager.py:124
|
||||
#: xpack/plugins/cloud/models.py:292
|
||||
#: xpack/plugins/cloud/manager.py:161 xpack/plugins/cloud/models.py:292
|
||||
msgid "Region"
|
||||
msgstr "地域"
|
||||
|
||||
@ -7618,63 +7631,88 @@ msgstr "每天检测一次,超过预设时间的用户自动禁用"
|
||||
msgid "Watermark"
|
||||
msgstr "开启水印"
|
||||
|
||||
#: settings/serializers/security.py:200
|
||||
#: settings/serializers/security.py:199
|
||||
|
||||
msgid "Watermark session content"
|
||||
msgstr "会话水印自定义内容"
|
||||
|
||||
#: settings/serializers/security.py:202
|
||||
msgid "Watermark console content"
|
||||
msgstr "管理页面水印自定义内容"
|
||||
|
||||
#: settings/serializers/security.py:208
|
||||
msgid "Watermark font size"
|
||||
msgstr "字体字号"
|
||||
|
||||
#: settings/serializers/security.py:211
|
||||
msgid "Watermark height"
|
||||
msgstr "单个水印高度"
|
||||
|
||||
#: settings/serializers/security.py:214
|
||||
msgid "Watermark width"
|
||||
msgstr "单个水印宽度"
|
||||
|
||||
#: settings/serializers/security.py:217
|
||||
msgid "Watermark rotate"
|
||||
msgstr "水印旋转角度"
|
||||
|
||||
#: settings/serializers/security.py:221
|
||||
msgid "Max idle time (minute)"
|
||||
msgstr "连接最大空闲时间 (分)"
|
||||
|
||||
#: settings/serializers/security.py:201
|
||||
#: settings/serializers/security.py:222
|
||||
msgid "If idle time more than it, disconnect connection."
|
||||
msgstr "提示:如果超过该配置没有操作,连接会被断开"
|
||||
|
||||
#: settings/serializers/security.py:204
|
||||
#: settings/serializers/security.py:225
|
||||
msgid "Session expire at browser closed"
|
||||
msgstr "会话在浏览器关闭时过期"
|
||||
|
||||
#: settings/serializers/security.py:205
|
||||
#: settings/serializers/security.py:226
|
||||
msgid "Whether to expire the session when the user closes their browser."
|
||||
msgstr "当用户关闭浏览器时是否使会话过期。"
|
||||
|
||||
#: settings/serializers/security.py:210
|
||||
#: settings/serializers/security.py:231
|
||||
msgid "Allow users to view asset session information"
|
||||
msgstr "允许用户查看资产在线会话信息"
|
||||
|
||||
#: settings/serializers/security.py:212
|
||||
#: settings/serializers/security.py:233
|
||||
msgid ""
|
||||
"When a user connects to an asset, the account selection popup displays the "
|
||||
"number of active sessions for the current asset (RDP protocol only)."
|
||||
msgstr ""
|
||||
"当用户连接资产时,账号选择弹窗中显示当前资产的在线会话数量(仅 rdp 协议)"
|
||||
|
||||
#: settings/serializers/security.py:218
|
||||
#: settings/serializers/security.py:239
|
||||
msgid "Max online time (hour)"
|
||||
msgstr "会话连接最大时间 (时)"
|
||||
|
||||
#: settings/serializers/security.py:219
|
||||
#: settings/serializers/security.py:240
|
||||
msgid "If session connection time more than it, disconnect connection."
|
||||
msgstr "提示:如果会话连接超过该配置,连接会被断开"
|
||||
|
||||
#: settings/serializers/security.py:222
|
||||
#: settings/serializers/security.py:243
|
||||
msgid "Remember manual auth"
|
||||
msgstr "保存手动输入密码"
|
||||
|
||||
#: settings/serializers/security.py:225
|
||||
#: settings/serializers/security.py:246
|
||||
#: terminal/templates/terminal/_msg_session_sharing.html:10
|
||||
msgid "Session share"
|
||||
msgstr "会话分享"
|
||||
|
||||
#: settings/serializers/security.py:226
|
||||
#: settings/serializers/security.py:247
|
||||
msgid "Enabled, Allows user active session to be shared with other users"
|
||||
msgstr "开启后允许用户分享已连接的资产会话给他人,协同工作"
|
||||
|
||||
#: settings/serializers/security.py:232
|
||||
#: settings/serializers/security.py:253
|
||||
msgid "Insecure command alert"
|
||||
msgstr "危险命令告警"
|
||||
|
||||
#: settings/serializers/security.py:235
|
||||
#: settings/serializers/security.py:256
|
||||
msgid "Email recipient"
|
||||
msgstr "邮件收件人"
|
||||
|
||||
#: settings/serializers/security.py:236
|
||||
#: settings/serializers/security.py:257
|
||||
msgid "Multiple user using , split"
|
||||
msgstr "多个用户,使用 , 分割"
|
||||
|
||||
@ -8039,17 +8077,17 @@ msgstr ""
|
||||
"您的SSH密钥没有设置或已失效,请点击 <a href=\"%(user_pubkey_update)s\"> 链接 "
|
||||
"</a> 更新"
|
||||
|
||||
#: templates/_mfa_login_field.html:31
|
||||
#: templates/_mfa_login_field.html:29
|
||||
#: users/templates/users/forgot_password.html:101
|
||||
msgid "Send"
|
||||
msgstr "发送"
|
||||
|
||||
#: templates/_mfa_login_field.html:110
|
||||
#: templates/_mfa_login_field.html:108
|
||||
#: users/templates/users/forgot_password.html:176
|
||||
msgid "Wait: "
|
||||
msgstr "等待:"
|
||||
|
||||
#: templates/_mfa_login_field.html:120
|
||||
#: templates/_mfa_login_field.html:118
|
||||
#: users/templates/users/forgot_password.html:192
|
||||
msgid "The verification code has been sent"
|
||||
msgstr "验证码已发送"
|
||||
@ -8162,7 +8200,7 @@ msgstr "会话不存在: {}"
|
||||
msgid "Session is finished or the protocol not supported"
|
||||
msgstr "会话已经完成或协议不支持"
|
||||
|
||||
#: terminal/api/session/session.py:345
|
||||
#: terminal/api/session/session.py:345 tickets/api/ticket.py:140
|
||||
msgid "User does not have permission"
|
||||
msgstr "用户没有权限"
|
||||
|
||||
@ -8710,9 +8748,9 @@ msgid ""
|
||||
"days. <a href=\"https://learn.microsoft.com/en-us/windows-server/remote/"
|
||||
"remote-desktop-services/rds-client-access-license\">Detail</a>"
|
||||
msgstr ""
|
||||
"如果不存在,RDS 将处于试用模式,试用期为 120 天。<a href='https://learn."
|
||||
"microsoft.com/en-us/windows-server/remote/remote-desktop-services/rds-client-"
|
||||
"access-license'>详情</a>"
|
||||
"如果不存在,RDS 将处于试用模式,试用期为 120 天。<a href='https://"
|
||||
"learn.microsoft.com/en-us/windows-server/remote/remote-desktop-services/rds-"
|
||||
"client-access-license'>详情</a>"
|
||||
|
||||
#: terminal/serializers/applet_host.py:55
|
||||
msgid "RDS License Server"
|
||||
@ -8930,8 +8968,8 @@ msgid ""
|
||||
"If there are multiple hosts, use a comma (,) to separate them. <br>(For "
|
||||
"example: http://www.jumpserver.a.com:9100, http://www.jumpserver.b.com:9100)"
|
||||
msgstr ""
|
||||
"如果有多个主机,请用逗号 (,) 分隔它们。<br>(例如:http://www.jumpserver.a."
|
||||
"com:9100,http://www.jumpserver.b.com:9100)"
|
||||
"如果有多个主机,请用逗号 (,) 分隔它们。<br>(例如:http://"
|
||||
"www.jumpserver.a.com:9100,http://www.jumpserver.b.com:9100)"
|
||||
|
||||
#: terminal/serializers/storage.py:199
|
||||
msgid "Index by date"
|
||||
@ -10529,59 +10567,63 @@ msgstr "同步地区"
|
||||
msgid "Get instances of region \"%s\" error, error: %s"
|
||||
msgstr "获取区域 \"%s\" 的实例错误,错误:%s"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:161 xpack/plugins/cloud/models.py:289
|
||||
msgid "Instance"
|
||||
msgstr "实例"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:187
|
||||
#, python-format
|
||||
msgid "Failed to synchronize the instance \"%s\""
|
||||
msgstr "无法同步实例 %s"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:379
|
||||
#: xpack/plugins/cloud/manager.py:378
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The updated platform of asset \"%s\" is inconsistent with the original "
|
||||
"platform type. Skip platform and protocol updates"
|
||||
msgstr "资产 \"%s\" 的更新平台与原平台类型不一致。跳过平台和协议更新"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:436
|
||||
#: xpack/plugins/cloud/manager.py:432
|
||||
#, python-format
|
||||
msgid "The asset \"%s\" already exists"
|
||||
msgstr "资产 \"%s\" 已存在"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:438
|
||||
#: xpack/plugins/cloud/manager.py:434
|
||||
#, python-format
|
||||
msgid "Update asset \"%s\""
|
||||
msgstr "更新资产 \"%s\""
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:441
|
||||
#: xpack/plugins/cloud/manager.py:437
|
||||
#, python-format
|
||||
msgid "Asset \"%s\" has been updated"
|
||||
msgstr "资产 \"%s\" 已更新"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:450
|
||||
#: xpack/plugins/cloud/manager.py:446
|
||||
#, python-format
|
||||
msgid "Prepare to create asset \"%s\""
|
||||
msgstr "准备创建资产 %s"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:471
|
||||
#: xpack/plugins/cloud/manager.py:467
|
||||
#, python-format
|
||||
msgid "Set nodes \"%s\""
|
||||
msgstr "设置节点: \"%s\""
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:497
|
||||
#: xpack/plugins/cloud/manager.py:493
|
||||
#, python-format
|
||||
msgid "Set accounts \"%s\""
|
||||
msgstr "设置账号: %s"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:513
|
||||
#: xpack/plugins/cloud/manager.py:509
|
||||
#, python-format
|
||||
msgid "Set protocols \"%s\""
|
||||
msgstr "设置协议 \"%s\""
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:521
|
||||
#: xpack/plugins/cloud/manager.py:517
|
||||
#, python-format
|
||||
msgid "Set labels \"%s\""
|
||||
msgstr "设置标签: \"%s\""
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:535 xpack/plugins/cloud/tasks.py:31
|
||||
#: xpack/plugins/cloud/manager.py:531 xpack/plugins/cloud/tasks.py:31
|
||||
msgid "Run sync instance task"
|
||||
msgstr "执行同步实例任务"
|
||||
|
||||
@ -10671,10 +10713,6 @@ msgstr "同步任务"
|
||||
msgid "Sync instance task history"
|
||||
msgstr "同步实例任务历史"
|
||||
|
||||
#: xpack/plugins/cloud/models.py:289
|
||||
msgid "Instance"
|
||||
msgstr "实例"
|
||||
|
||||
#: xpack/plugins/cloud/models.py:306
|
||||
msgid "Sync instance detail"
|
||||
msgstr "同步实例详情"
|
||||
@ -10979,6 +11017,12 @@ msgstr "订阅 ID"
|
||||
msgid "Auto node classification"
|
||||
msgstr "自动节点分类"
|
||||
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:92
|
||||
#, fuzzy
|
||||
#| msgid "Domain name"
|
||||
msgid "domain_name"
|
||||
msgstr "域名称"
|
||||
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:98
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:102
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:126
|
||||
|
@ -1536,5 +1536,13 @@
|
||||
"disallowSelfUpdateFields": "Not allowed to modify the current fields yourself",
|
||||
"forceEnableMFAHelpText": "If force enable, user can not disable by themselves",
|
||||
"removeWarningMsg": "Are you sure you want to remove",
|
||||
"setVariable": "Set variable"
|
||||
"setVariable": "Set variable",
|
||||
"WatermarkVariableHelpText": "You can use ${key} to read built-in variables in watermark content",
|
||||
"isConsoleCanUse": "is Console page can use",
|
||||
"userId": "User ID",
|
||||
"userName": "User name",
|
||||
"currentTime": "Current time",
|
||||
"assetId": "Asset ID",
|
||||
"assetName": "Asset name",
|
||||
"assetAddress": "Asset address"
|
||||
}
|
||||
|
@ -1538,5 +1538,15 @@
|
||||
"disallowSelfUpdateFields": "不允许自己修改当前字段",
|
||||
"forceEnableMFAHelpText": "如果强制启用,用户无法自行禁用",
|
||||
"removeWarningMsg": "你确定要移除",
|
||||
"setVariable": "设置参数"
|
||||
"setVariable": "设置参数",
|
||||
"Watermark": "水印",
|
||||
"WatermarkVariableHelpText": "您可以在自定义水印内容中使用 ${key} 读取内置变量",
|
||||
"isConsoleCanUse": "管理页面是否可用",
|
||||
"userId": "用户ID",
|
||||
"name": "用户名称",
|
||||
"userName": "用户名",
|
||||
"currentTime": "当前时间",
|
||||
"assetId": "资产 ID",
|
||||
"assetName": "资产名称",
|
||||
"assetAddress": "资产地址"
|
||||
}
|
||||
|
@ -589,6 +589,13 @@ class Config(dict):
|
||||
'SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER': '',
|
||||
'SECURITY_LUNA_REMEMBER_AUTH': True,
|
||||
'SECURITY_WATERMARK_ENABLED': True,
|
||||
'SECURITY_WATERMARK_SESSION_CONTENT': '${name}(${userName})\n${assetName}',
|
||||
'SECURITY_WATERMARK_CONSOLE_CONTENT': '${userName}(${name})',
|
||||
'SECURITY_WATERMARK_COLOR': 'rgba(184, 184, 184, 0.8)',
|
||||
'SECURITY_WATERMARK_FONT_SIZE': 13,
|
||||
'SECURITY_WATERMARK_HEIGHT': 200,
|
||||
'SECURITY_WATERMARK_WIDTH': 200,
|
||||
'SECURITY_WATERMARK_ROTATE': 45,
|
||||
'SECURITY_MFA_VERIFY_TTL': 3600,
|
||||
'SECURITY_UNCOMMON_USERS_TTL': 999,
|
||||
'VERIFY_CODE_TTL': 60,
|
||||
|
@ -328,9 +328,13 @@ MFA_BACKEND_FACE = 'authentication.mfa.face.MFAFace'
|
||||
MFA_BACKEND_RADIUS = 'authentication.mfa.radius.MFARadius'
|
||||
MFA_BACKEND_SMS = 'authentication.mfa.sms.MFASms'
|
||||
MFA_BACKEND_EMAIL = 'authentication.mfa.email.MFAEmail'
|
||||
MFA_BACKEND_PASSKEY = 'authentication.mfa.passkey.MFAPasskey'
|
||||
MFA_BACKEND_CUSTOM = 'authentication.mfa.custom.MFACustom'
|
||||
|
||||
MFA_BACKENDS = [MFA_BACKEND_OTP, MFA_BACKEND_RADIUS, MFA_BACKEND_SMS, MFA_BACKEND_FACE, MFA_BACKEND_EMAIL]
|
||||
MFA_BACKENDS = [
|
||||
MFA_BACKEND_OTP, MFA_BACKEND_RADIUS, MFA_BACKEND_SMS,
|
||||
MFA_BACKEND_PASSKEY, MFA_BACKEND_FACE, MFA_BACKEND_EMAIL
|
||||
]
|
||||
|
||||
MFA_CUSTOM = CONFIG.MFA_CUSTOM
|
||||
MFA_CUSTOM_FILE_MD5 = CONFIG.MFA_CUSTOM_FILE_MD5
|
||||
|
@ -160,9 +160,17 @@ HEALTH_CHECK_TOKEN = CONFIG.HEALTH_CHECK_TOKEN
|
||||
|
||||
TERMINAL_RDP_ADDR = CONFIG.TERMINAL_RDP_ADDR
|
||||
SECURITY_LUNA_REMEMBER_AUTH = CONFIG.SECURITY_LUNA_REMEMBER_AUTH
|
||||
# 水印
|
||||
SECURITY_WATERMARK_ENABLED = CONFIG.SECURITY_WATERMARK_ENABLED
|
||||
SECURITY_SESSION_SHARE = CONFIG.SECURITY_SESSION_SHARE
|
||||
SECURITY_WATERMARK_SESSION_CONTENT = CONFIG.SECURITY_WATERMARK_SESSION_CONTENT
|
||||
SECURITY_WATERMARK_COLOR = CONFIG.SECURITY_WATERMARK_COLOR
|
||||
SECURITY_WATERMARK_FONT_SIZE = CONFIG.SECURITY_WATERMARK_FONT_SIZE
|
||||
SECURITY_WATERMARK_HEIGHT = CONFIG.SECURITY_WATERMARK_HEIGHT
|
||||
SECURITY_WATERMARK_WIDTH = CONFIG.SECURITY_WATERMARK_WIDTH
|
||||
SECURITY_WATERMARK_ROTATE = CONFIG.SECURITY_WATERMARK_ROTATE
|
||||
SECURITY_WATERMARK_CONSOLE_CONTENT = CONFIG.SECURITY_WATERMARK_CONSOLE_CONTENT
|
||||
|
||||
SECURITY_SESSION_SHARE = CONFIG.SECURITY_SESSION_SHARE
|
||||
LOGIN_REDIRECT_TO_BACKEND = CONFIG.LOGIN_REDIRECT_TO_BACKEND
|
||||
LOGIN_REDIRECT_MSG_ENABLED = CONFIG.LOGIN_REDIRECT_MSG_ENABLED
|
||||
|
||||
|
@ -37,12 +37,16 @@ class ComponentI18nApi(RetrieveAPIView):
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
name = kwargs.get('name')
|
||||
lang = request.query_params.get('lang')
|
||||
flat = request.query_params.get('flat', '1')
|
||||
data = self.get_component_translations(name)
|
||||
if lang:
|
||||
code = Language.to_internal_code(lang, with_filename=True)
|
||||
data = data.get(code) or {}
|
||||
flat = request.query_params.get('flat', '1')
|
||||
if flat == '0':
|
||||
# 这里要使用原始的 lang, lina 会 merge
|
||||
data = {lang: data}
|
||||
|
||||
if not lang:
|
||||
return Response(data)
|
||||
if lang not in Language.choices:
|
||||
lang = 'en'
|
||||
code = Language.to_internal_code(lang, with_filename=True)
|
||||
data = data.get(code) or {}
|
||||
if flat == '0':
|
||||
# 这里要使用原始的 lang, lina 会 merge
|
||||
data = {lang: data}
|
||||
return Response(data)
|
||||
|
@ -29,6 +29,13 @@ class PrivateSettingSerializer(PublicSettingSerializer):
|
||||
SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField()
|
||||
SECURITY_LUNA_REMEMBER_AUTH = serializers.BooleanField()
|
||||
SECURITY_WATERMARK_ENABLED = serializers.BooleanField()
|
||||
SECURITY_WATERMARK_SESSION_CONTENT = serializers.CharField()
|
||||
SECURITY_WATERMARK_CONSOLE_CONTENT = serializers.CharField()
|
||||
SECURITY_WATERMARK_COLOR = serializers.CharField()
|
||||
SECURITY_WATERMARK_FONT_SIZE = serializers.IntegerField()
|
||||
SECURITY_WATERMARK_HEIGHT = serializers.IntegerField()
|
||||
SECURITY_WATERMARK_WIDTH = serializers.IntegerField()
|
||||
SECURITY_WATERMARK_ROTATE = serializers.IntegerField()
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = serializers.BooleanField()
|
||||
VIEW_ASSET_ONLINE_SESSION_INFO = serializers.BooleanField()
|
||||
PASSWORD_RULE = serializers.DictField()
|
||||
|
@ -195,6 +195,27 @@ class SecuritySessionSerializer(serializers.Serializer):
|
||||
SECURITY_WATERMARK_ENABLED = serializers.BooleanField(
|
||||
required=True, label=_('Watermark'),
|
||||
)
|
||||
SECURITY_WATERMARK_SESSION_CONTENT = serializers.CharField(
|
||||
required=False, label=_('Watermark session content'),
|
||||
)
|
||||
SECURITY_WATERMARK_CONSOLE_CONTENT = serializers.CharField(
|
||||
required=False, label=_("Watermark console content")
|
||||
)
|
||||
SECURITY_WATERMARK_COLOR = serializers.CharField(
|
||||
max_length=32, default="", label=_("Color")
|
||||
)
|
||||
SECURITY_WATERMARK_FONT_SIZE = serializers.IntegerField(
|
||||
required=False, label=_('Watermark font size'), min_value=1, max_value=100,
|
||||
)
|
||||
SECURITY_WATERMARK_HEIGHT = serializers.IntegerField(
|
||||
required=False, label=_('Watermark height'), default=200
|
||||
)
|
||||
SECURITY_WATERMARK_WIDTH = serializers.IntegerField(
|
||||
required=False, label=_('Watermark width'), default=200
|
||||
)
|
||||
SECURITY_WATERMARK_ROTATE = serializers.IntegerField(
|
||||
required=False, label=_('Watermark rotate'), default=45
|
||||
)
|
||||
SECURITY_MAX_IDLE_TIME = serializers.IntegerField(
|
||||
min_value=1, max_value=99999, required=False,
|
||||
label=_('Max idle time (minute)'),
|
||||
|
@ -5,34 +5,32 @@
|
||||
onchange="selectChange(this.value)"
|
||||
>
|
||||
{% for backend in mfa_backends %}
|
||||
<option value="{{ backend.name }}"
|
||||
{% if not backend.is_active %} disabled {% endif %}
|
||||
>
|
||||
{{ backend.display_name }}
|
||||
</option>
|
||||
<option value="{{ backend.name }}"
|
||||
{% if not backend.is_active %} disabled {% endif %}
|
||||
>
|
||||
{{ backend.display_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="mfa-div">
|
||||
{% for backend in mfa_backends %}
|
||||
<div id="mfa-{{ backend.name }}" class="mfa-field
|
||||
<div id="mfa-{{ backend.name }}" class="mfa-field
|
||||
{% if backend.challenge_required %}challenge-required{% endif %}"
|
||||
style="display: none"
|
||||
style="display: none"
|
||||
>
|
||||
{% if backend.has_code %}
|
||||
<input type="text" class="form-control input-style"
|
||||
placeholder="{{ backend.placeholder }}"
|
||||
>
|
||||
|
||||
{% if backend.name == 'face' %}
|
||||
{% else %}
|
||||
<input type="text" class="form-control input-style"
|
||||
placeholder="{{ backend.placeholder }}"
|
||||
>
|
||||
{% if backend.challenge_required %}
|
||||
<button class="btn btn-primary full-width btn-challenge"
|
||||
type='button' onclick="sendChallengeCode(this)"
|
||||
>
|
||||
{% trans 'Send' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if backend.challenge_required %}
|
||||
<button class="btn btn-primary full-width btn-challenge"
|
||||
type='button' onclick="sendChallengeCode(this)"
|
||||
>
|
||||
{% trans 'Send' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@ -121,7 +119,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
function onError (responseText, responseJson, status) {
|
||||
function onError(responseText, responseJson, status) {
|
||||
setTimeout(function () {
|
||||
toastr.error(responseJson.detail || responseJson.error);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user