Merge pull request #6898 from jumpserver/dev

merge: dev to master
pull/6903/head
老广 2021-09-16 19:35:39 +08:00 committed by GitHub
commit eee093742c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 289 additions and 182 deletions

View File

@ -144,10 +144,17 @@ class Gateway(BaseUser):
key_filename=self.private_key_file, key_filename=self.private_key_file,
sock=sock, sock=sock,
timeout=5) timeout=5)
except (paramiko.SSHException, paramiko.ssh_exception.SSHException, except (paramiko.SSHException,
paramiko.AuthenticationException, TimeoutError) as e: paramiko.ssh_exception.SSHException,
paramiko.ChannelException,
paramiko.AuthenticationException,
TimeoutError) as e:
err = getattr(e, 'text', str(e))
if err == 'Connect failed':
err = _('Connect failed')
self.is_connective = False self.is_connective = False
return False, str(e) return False, err
finally: finally:
client.close() client.close()
self.is_connective = True self.is_connective = True

View File

@ -1,7 +1,7 @@
from django.dispatch import receiver from django.dispatch import receiver
from django.apps import apps from django.apps import apps
from simple_history.signals import pre_create_historical_record from simple_history.signals import pre_create_historical_record
from django.db.models.signals import post_save, pre_save, post_delete from django.db.models.signals import post_save, pre_save, pre_delete
from common.utils import get_logger from common.utils import get_logger
from ..models import AuthBook, SystemUser from ..models import AuthBook, SystemUser
@ -28,7 +28,7 @@ def pre_create_historical_record_callback(sender, history_instance=None, **kwarg
setattr(history_instance, attr, system_user_attr_value) setattr(history_instance, attr, system_user_attr_value)
@receiver(post_delete, sender=AuthBook) @receiver(pre_delete, sender=AuthBook)
def on_authbook_post_delete(sender, instance, **kwargs): def on_authbook_post_delete(sender, instance, **kwargs):
instance.remove_asset_admin_user_if_need() instance.remove_asset_admin_user_if_need()

View File

@ -89,9 +89,6 @@ class ClientProtocolMixin:
drives_redirect = is_true(self.request.query_params.get('drives_redirect')) drives_redirect = is_true(self.request.query_params.get('drives_redirect'))
token = self.create_token(user, asset, application, system_user) token = self.create_token(user, asset, application, system_user)
if system_user.login_mode == SystemUser.LOGIN_MANUAL:
options['prompt for credentials on client:i'] = '1'
if drives_redirect: if drives_redirect:
options['drivestoredirect:s'] = '*' options['drivestoredirect:s'] = '*'
options['screen mode id:i'] = '2' if full_screen else '1' options['screen mode id:i'] = '2' if full_screen else '1'

View File

@ -67,9 +67,9 @@ class ChallengeMixin(forms.Form):
def get_user_login_form_cls(*, captcha=False): def get_user_login_form_cls(*, captcha=False):
bases = [] bases = []
if settings.SECURITY_LOGIN_CAPTCHA_ENABLED and captcha:
bases.append(CaptchaMixin)
if settings.SECURITY_LOGIN_CHALLENGE_ENABLED: if settings.SECURITY_LOGIN_CHALLENGE_ENABLED:
bases.append(ChallengeMixin) bases.append(ChallengeMixin)
elif settings.SECURITY_LOGIN_CAPTCHA_ENABLED and captcha:
bases.append(CaptchaMixin)
bases.append(UserLoginForm) bases.append(UserLoginForm)
return type('UserLoginForm', tuple(bases), {}) return type('UserLoginForm', tuple(bases), {})

View File

@ -191,7 +191,10 @@ class AuthMixin(PasswordEncryptionViewMixin):
raise self.partial_credential_error(error=error) raise self.partial_credential_error(error=error)
def _set_partial_credential_error(self, username, ip, request): def _set_partial_credential_error(self, username, ip, request):
self.partial_credential_error = partial(errors.CredentialError, username=username, ip=ip, request=request) self.partial_credential_error = partial(
errors.CredentialError, username=username,
ip=ip, request=request
)
def get_auth_data(self, decrypt_passwd=False): def get_auth_data(self, decrypt_passwd=False):
request = self.request request = self.request

View File

@ -147,7 +147,7 @@
{% csrf_token %} {% csrf_token %}
<div style="line-height: 17px;margin-bottom: 20px;color: #999999;"> <div style="line-height: 17px;margin-bottom: 20px;color: #999999;">
{% if form.errors %} {% if form.errors %}
<p class="red-fonts" style="color: red"> <p class="help-block">
{% if form.non_field_errors %} {% if form.non_field_errors %}
{{ form.non_field_errors.as_text }} {{ form.non_field_errors.as_text }}
{% endif %} {% endif %}
@ -160,9 +160,15 @@
</div> </div>
{% bootstrap_field form.username show_label=False %} {% bootstrap_field form.username show_label=False %}
<div class="form-group">
<input type="password" class="form-control" id="password" placeholder="{% trans 'Password' %}" required=""> <div class="form-group {% if form.password.errors %} has-error {% endif %}">
<input type="password" class="form-control" id="password" placeholder="{% trans 'Password' %}" required>
<input id="password-hidden" type="text" style="display:none" name="{{ form.password.html_name }}"> <input id="password-hidden" type="text" style="display:none" name="{{ form.password.html_name }}">
{% if form.password.errors %}
<p class="help-block" style="text-align: left">
{{ form.password.errors.as_text }}
</p>
{% endif %}
</div> </div>
{% if form.challenge %} {% if form.challenge %}
{% bootstrap_field form.challenge show_label=False %} {% bootstrap_field form.challenge show_label=False %}
@ -220,7 +226,6 @@
<i class="fa"><img src="{{ LOGIN_FEISHU_LOGO_URL }}" height="13" width="13"></i> {% trans 'FeiShu' %} <i class="fa"><img src="{{ LOGIN_FEISHU_LOGO_URL }}" height="13" width="13"></i> {% trans 'FeiShu' %}
</a> </a>
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}
<div class="text-center" style="display: inline-block;"> <div class="text-center" style="display: inline-block;">
@ -236,6 +241,9 @@
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script> <script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script>
<script> <script>
function encryptLoginPassword(password, rsaPublicKey) { function encryptLoginPassword(password, rsaPublicKey) {
if (!password) {
return ''
}
var jsencrypt = new JSEncrypt(); //加密对象 var jsencrypt = new JSEncrypt(); //加密对象
jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥 jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥
return jsencrypt.encrypt(password); //加密 return jsencrypt.encrypt(password); //加密

View File

@ -134,7 +134,6 @@ function cancelTicket() {
} }
function cancelCloseConfirm() { function cancelCloseConfirm() {
cancelTicket();
window.onbeforeunload = function() {}; window.onbeforeunload = function() {};
window.onunload = function(){}; window.onunload = function(){};
} }
@ -158,6 +157,7 @@ $(document).ready(function () {
cancelCloseConfirm(); cancelCloseConfirm();
window.location.reload(); window.location.reload();
}).on('click', '.btn-return', function () { }).on('click', '.btn-return', function () {
cancelTicket();
cancelCloseConfirm(); cancelCloseConfirm();
window.location = "{% url 'authentication:login' %}" window.location = "{% url 'authentication:login' %}"
}) })

View File

@ -9,6 +9,7 @@ from django.contrib.auth import login as auth_login, logout as auth_logout
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import reverse, redirect from django.shortcuts import reverse, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.db import transaction
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
@ -18,14 +19,13 @@ from django.views.generic.edit import FormView
from django.conf import settings from django.conf import settings
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.contrib.auth import BACKEND_SESSION_KEY from django.contrib.auth import BACKEND_SESSION_KEY
from django.db.transaction import atomic
from common.utils import get_request_ip, FlashMessageUtil from common.utils import FlashMessageUtil
from users.utils import ( from users.utils import (
redirect_user_first_login_or_index redirect_user_first_login_or_index
) )
from ..const import RSA_PRIVATE_KEY, RSA_PUBLIC_KEY from ..const import RSA_PRIVATE_KEY, RSA_PUBLIC_KEY
from .. import mixins, errors, utils from .. import mixins, errors
from ..forms import get_user_login_form_cls from ..forms import get_user_login_form_cls
@ -109,7 +109,7 @@ class UserLoginView(mixins.AuthMixin, FormView):
self.request.session.delete_test_cookie() self.request.session.delete_test_cookie()
try: try:
with atomic(): with transaction.atomic():
self.check_user_auth(decrypt_passwd=True) self.check_user_auth(decrypt_passwd=True)
except errors.AuthFailedError as e: except errors.AuthFailedError as e:
form.add_error(None, e.msg) form.add_error(None, e.msg)

View File

@ -14,6 +14,7 @@ import types
import errno import errno
import json import json
import yaml import yaml
import copy
from importlib import import_module from importlib import import_module
from django.urls import reverse_lazy from django.urls import reverse_lazy
from urllib.parse import urljoin, urlparse from urllib.parse import urljoin, urlparse
@ -348,7 +349,8 @@ class Config(dict):
'HEALTH_CHECK_TOKEN': '', 'HEALTH_CHECK_TOKEN': '',
} }
def compatible_auth_openid_of_key(self): @staticmethod
def convert_keycloak_to_openid(keycloak_config):
""" """
兼容OpenID旧配置 ( version <= 1.5.8) 兼容OpenID旧配置 ( version <= 1.5.8)
因为旧配置只支持OpenID协议的Keycloak实现, 因为旧配置只支持OpenID协议的Keycloak实现,
@ -356,65 +358,79 @@ class Config(dict):
构造出新配置中标准OpenID协议中所需的Endpoint即可 构造出新配置中标准OpenID协议中所需的Endpoint即可
(Keycloak说明文档参考: https://www.keycloak.org/docs/latest/securing_apps/) (Keycloak说明文档参考: https://www.keycloak.org/docs/latest/securing_apps/)
""" """
if self.AUTH_OPENID and not self.AUTH_OPENID_REALM_NAME:
self['AUTH_OPENID_KEYCLOAK'] = False
if not self.AUTH_OPENID: openid_config = copy.deepcopy(keycloak_config)
auth_openid = openid_config.get('AUTH_OPENID')
auth_openid_realm_name = openid_config.get('AUTH_OPENID_REALM_NAME')
auth_openid_server_url = openid_config.get('AUTH_OPENID_SERVER_URL')
if not auth_openid:
return return
realm_name = self.AUTH_OPENID_REALM_NAME if auth_openid and not auth_openid_realm_name:
if realm_name is None: # 开启的是标准 OpenID 配置,关掉 Keycloak 配置
openid_config.update({
'AUTH_OPENID_KEYCLOAK': False
})
if auth_openid_realm_name is None:
return return
compatible_keycloak_config = [ # # convert key # #
( compatible_config = {
'AUTH_OPENID_PROVIDER_ENDPOINT', 'AUTH_OPENID_PROVIDER_ENDPOINT': auth_openid_server_url,
self.AUTH_OPENID_SERVER_URL
),
(
'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT',
'/realms/{}/protocol/openid-connect/auth'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT',
'/realms/{}/protocol/openid-connect/token'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT',
'/realms/{}/protocol/openid-connect/certs'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT',
'/realms/{}/protocol/openid-connect/userinfo'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT',
'/realms/{}/protocol/openid-connect/logout'.format(realm_name)
)
]
for key, value in compatible_keycloak_config:
self[key] = value
def compatible_auth_openid_of_value(self): 'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT': '/realms/{}/protocol/openid-connect/auth'
""" ''.format(auth_openid_realm_name),
兼容值的绝对路径相对路径 'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT': '/realms/{}/protocol/openid-connect/token'
(key AUTH_OPENID_PROVIDER_*_ENDPOINT 的配置) ''.format(auth_openid_realm_name),
""" 'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT': '/realms/{}/protocol/openid-connect/certs'
if not self.AUTH_OPENID: ''.format(auth_openid_realm_name),
return 'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT': '/realms/{}/protocol/openid-connect/userinfo'
''.format(auth_openid_realm_name),
'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT': '/realms/{}/protocol/openid-connect/logout'
''.format(auth_openid_realm_name)
}
for key, value in compatible_config.items():
openid_config[key] = value
base = self.AUTH_OPENID_PROVIDER_ENDPOINT # # convert value # #
config = list(self.items()) """ 兼容值的绝对路径、相对路径 (key 为 AUTH_OPENID_PROVIDER_*_ENDPOINT 的配置) """
for key, value in config: base = openid_config.get('AUTH_OPENID_PROVIDER_ENDPOINT')
for key, value in openid_config.items():
result = re.match(r'^AUTH_OPENID_PROVIDER_.*_ENDPOINT$', key) result = re.match(r'^AUTH_OPENID_PROVIDER_.*_ENDPOINT$', key)
if result is None: if result is None:
continue continue
if value is None: if value is None:
# None 在 url 中有特殊含义 (比如对于: end_session_endpoint) # None 在 url 中有特殊含义 (比如对于: end_session_endpoint)
continue continue
value = build_absolute_uri(base, value) value = build_absolute_uri(base, value)
openid_config[key] = value
return openid_config
def get_keycloak_config(self):
keycloak_config = {
'AUTH_OPENID': self.AUTH_OPENID,
'AUTH_OPENID_REALM_NAME': self.AUTH_OPENID_REALM_NAME,
'AUTH_OPENID_SERVER_URL': self.AUTH_OPENID_SERVER_URL,
'AUTH_OPENID_PROVIDER_ENDPOINT': self.AUTH_OPENID_PROVIDER_ENDPOINT
}
return keycloak_config
def set_openid_config(self, openid_config):
for key, value in openid_config.items():
self[key] = value self[key] = value
def compatible_auth_openid(self, keycloak_config=None):
if keycloak_config is None:
keycloak_config = self.get_keycloak_config()
openid_config = self.convert_keycloak_to_openid(keycloak_config)
if openid_config:
self.set_openid_config(openid_config)
def compatible(self): def compatible(self):
""" """
对配置做兼容处理 对配置做兼容处理
@ -424,14 +440,8 @@ class Config(dict):
处理顺序要保持先对key做处理, 再对value做处理, 处理顺序要保持先对key做处理, 再对value做处理,
因为处理value的时候只根据最新版本支持的key进行 因为处理value的时候只根据最新版本支持的key进行
""" """
parts = ['key', 'value'] # 兼容 OpenID 配置
targets = ['auth_openid'] self.compatible_auth_openid()
for part in parts:
for target in targets:
method_name = 'compatible_{}_of_{}'.format(target, part)
method = getattr(self, method_name, None)
if method is not None:
method()
def convert_type(self, k, v): def convert_type(self, k, v):
default_value = self.defaults.get(k) default_value = self.defaults.get(k)

Binary file not shown.

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-15 20:51+0800\n" "POT-Creation-Date: 2021-09-16 19:25+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -25,7 +25,7 @@ msgstr ""
#: orgs/models.py:24 perms/models/base.py:44 settings/models.py:29 #: orgs/models.py:24 perms/models/base.py:44 settings/models.py:29
#: settings/serializers/sms.py:6 terminal/models/storage.py:23 #: settings/serializers/sms.py:6 terminal/models/storage.py:23
#: terminal/models/task.py:16 terminal/models/terminal.py:100 #: terminal/models/task.py:16 terminal/models/terminal.py:100
#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:604 #: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:605
#: users/templates/users/_select_user_modal.html:13 #: users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:37
#: users/templates/users/user_asset_permission.html:154 #: users/templates/users/user_asset_permission.html:154
@ -60,7 +60,7 @@ msgstr "激活中"
#: orgs/models.py:27 perms/models/base.py:53 settings/models.py:34 #: orgs/models.py:27 perms/models/base.py:53 settings/models.py:34
#: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: terminal/models/storage.py:26 terminal/models/terminal.py:114
#: tickets/models/ticket.py:71 users/models/group.py:16 #: tickets/models/ticket.py:71 users/models/group.py:16
#: users/models/user.py:637 xpack/plugins/change_auth_plan/models/base.py:41 #: users/models/user.py:638 xpack/plugins/change_auth_plan/models/base.py:41
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113
#: xpack/plugins/gathered_user/models.py:26 #: xpack/plugins/gathered_user/models.py:26
msgid "Comment" msgid "Comment"
@ -98,7 +98,7 @@ msgstr "动作"
#: terminal/backends/command/models.py:18 #: terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38
#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:181 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:181
#: users/models/user.py:809 users/models/user.py:835 #: users/models/user.py:814 users/models/user.py:840
#: users/serializers/group.py:19 #: users/serializers/group.py:19
#: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:38
#: users/templates/users/user_asset_permission.html:64 #: users/templates/users/user_asset_permission.html:64
@ -177,7 +177,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: applications/serializers/attrs/application_type/vmware_client.py:26 #: applications/serializers/attrs/application_type/vmware_client.py:26
#: assets/models/base.py:176 assets/models/gathered_user.py:15 #: assets/models/base.py:176 assets/models/gathered_user.py:15
#: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17 #: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:602 #: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:603
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: xpack/plugins/change_auth_plan/models/asset.py:35 #: xpack/plugins/change_auth_plan/models/asset.py:35
#: xpack/plugins/change_auth_plan/models/asset.py:191 #: xpack/plugins/change_auth_plan/models/asset.py:191
@ -281,7 +281,7 @@ msgstr "应用管理"
#: applications/serializers/application.py:88 assets/models/label.py:21 #: applications/serializers/application.py:88 assets/models/label.py:21
#: perms/models/application_permission.py:20 #: perms/models/application_permission.py:20
#: perms/serializers/application/user_permission.py:33 #: perms/serializers/application/user_permission.py:33
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22
#: xpack/plugins/change_auth_plan/models/app.py:25 #: xpack/plugins/change_auth_plan/models/app.py:25
msgid "Category" msgid "Category"
msgstr "类别" msgstr "类别"
@ -292,7 +292,7 @@ msgstr "类别"
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:55 terminal/models/storage.py:116 #: terminal/models/storage.py:55 terminal/models/storage.py:116
#: tickets/models/flow.py:51 tickets/models/ticket.py:48 #: tickets/models/flow.py:51 tickets/models/ticket.py:48
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:27 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
#: xpack/plugins/change_auth_plan/models/app.py:28 #: xpack/plugins/change_auth_plan/models/app.py:28
#: xpack/plugins/change_auth_plan/models/app.py:148 #: xpack/plugins/change_auth_plan/models/app.py:148
msgid "Type" msgid "Type"
@ -310,7 +310,7 @@ msgstr ""
#: applications/serializers/application.py:59 #: applications/serializers/application.py:59
#: applications/serializers/application.py:89 assets/serializers/label.py:13 #: applications/serializers/application.py:89 assets/serializers/label.py:13
#: perms/serializers/application/permission.py:16 #: perms/serializers/application/permission.py:16
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:24 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26
msgid "Category display" msgid "Category display"
msgstr "类别名称" msgstr "类别名称"
@ -318,7 +318,7 @@ msgstr "类别名称"
#: applications/serializers/application.py:91 #: applications/serializers/application.py:91
#: assets/serializers/system_user.py:26 audits/serializers.py:29 #: assets/serializers/system_user.py:26 audits/serializers.py:29
#: perms/serializers/application/permission.py:17 #: perms/serializers/application/permission.py:17
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:31 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33
#: tickets/serializers/ticket/ticket.py:22 #: tickets/serializers/ticket/ticket.py:22
#: tickets/serializers/ticket/ticket.py:168 #: tickets/serializers/ticket/ticket.py:168
msgid "Type display" msgid "Type display"
@ -377,7 +377,7 @@ msgstr "目标URL"
#: applications/serializers/attrs/application_type/vmware_client.py:30 #: applications/serializers/attrs/application_type/vmware_client.py:30
#: assets/models/base.py:177 audits/signals_handler.py:65 #: assets/models/base.py:177 audits/signals_handler.py:65
#: authentication/forms.py:22 #: authentication/forms.py:22
#: authentication/templates/authentication/login.html:164 #: authentication/templates/authentication/login.html:165
#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 #: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21
#: users/templates/users/user_otp_check_password.html:13 #: users/templates/users/user_otp_check_password.html:13
#: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_update.html:43
@ -531,7 +531,7 @@ msgstr "标签管理"
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
#: assets/models/cmd_filter.py:67 assets/models/group.py:21 #: assets/models/cmd_filter.py:67 assets/models/group.py:21
#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25 #: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25
#: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:645 #: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:646
#: users/serializers/group.py:33 #: users/serializers/group.py:33
#: xpack/plugins/change_auth_plan/models/base.py:45 #: xpack/plugins/change_auth_plan/models/base.py:45
#: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30 #: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30
@ -544,7 +544,7 @@ msgstr "创建者"
#: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50 #: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50
#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26 #: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26
#: orgs/models.py:435 perms/models/base.py:52 users/models/group.py:18 #: orgs/models.py:435 perms/models/base.py:52 users/models/group.py:18
#: users/models/user.py:836 xpack/plugins/cloud/models.py:122 #: users/models/user.py:841 xpack/plugins/cloud/models.py:122
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
@ -599,7 +599,7 @@ msgstr "带宽"
msgid "Contact" msgid "Contact"
msgstr "联系人" msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:623 #: assets/models/cluster.py:22 users/models/user.py:624
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
@ -625,7 +625,7 @@ msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14 #: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:821 #: users/models/user.py:826
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
@ -687,7 +687,7 @@ msgstr "无法连接到 {ip} 上的端口 {port}"
msgid "Authentication failed" msgid "Authentication failed"
msgstr "认证失败" msgstr "认证失败"
#: assets/models/domain.py:133 #: assets/models/domain.py:133 assets/models/domain.py:155
msgid "Connect failed" msgid "Connect failed"
msgstr "连接失败" msgstr "连接失败"
@ -1091,8 +1091,8 @@ msgstr "成功"
#: audits/models.py:43 ops/models/command.py:30 perms/models/base.py:49 #: audits/models.py:43 ops/models/command.py:30 perms/models/base.py:49
#: terminal/models/session.py:52 #: terminal/models/session.py:52
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:53 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:45 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:47
#: xpack/plugins/change_auth_plan/models/base.py:105 #: xpack/plugins/change_auth_plan/models/base.py:105
#: xpack/plugins/change_auth_plan/models/base.py:189 #: xpack/plugins/change_auth_plan/models/base.py:189
#: xpack/plugins/gathered_user/models.py:76 #: xpack/plugins/gathered_user/models.py:76
@ -1160,7 +1160,7 @@ msgstr "用户代理"
#: audits/models.py:110 #: audits/models.py:110
#: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: authentication/templates/authentication/login_otp.html:6 #: authentication/templates/authentication/login_otp.html:6
#: users/forms/profile.py:64 users/models/user.py:626 #: users/forms/profile.py:64 users/models/user.py:627
#: users/serializers/profile.py:102 #: users/serializers/profile.py:102
msgid "MFA" msgid "MFA"
msgstr "多因子认证" msgstr "多因子认证"
@ -1239,14 +1239,14 @@ msgid "Auth Token"
msgstr "认证令牌" msgstr "认证令牌"
#: audits/signals_handler.py:68 #: audits/signals_handler.py:68
#: authentication/templates/authentication/login.html:210 #: authentication/templates/authentication/login.html:216
#: notifications/backends/__init__.py:11 users/models/user.py:659 #: notifications/backends/__init__.py:11 users/models/user.py:660
msgid "WeCom" msgid "WeCom"
msgstr "企业微信" msgstr "企业微信"
#: audits/signals_handler.py:69 #: audits/signals_handler.py:69
#: authentication/templates/authentication/login.html:215 #: authentication/templates/authentication/login.html:221
#: notifications/backends/__init__.py:12 users/models/user.py:660 #: notifications/backends/__init__.py:12 users/models/user.py:661
msgid "DingTalk" msgid "DingTalk"
msgstr "钉钉" msgstr "钉钉"
@ -1433,7 +1433,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}"
msgid "{ApplicationPermission} REMOVE {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}"
msgstr "{ApplicationPermission} 移除 {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}"
#: authentication/api/connection_token.py:226 #: authentication/api/connection_token.py:227
msgid "Invalid token" msgid "Invalid token"
msgstr "无效的令牌" msgstr "无效的令牌"
@ -1616,15 +1616,15 @@ msgstr "来源 IP 不被允许登录"
msgid "SSO auth closed" msgid "SSO auth closed"
msgstr "SSO 认证关闭了" msgstr "SSO 认证关闭了"
#: authentication/errors.py:298 authentication/mixins.py:318 #: authentication/errors.py:298 authentication/mixins.py:321
msgid "Your password is too simple, please change it for security" msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改" msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors.py:307 authentication/mixins.py:325 #: authentication/errors.py:307 authentication/mixins.py:328
msgid "You should to change your password before login" msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码" msgstr "登录完成前,请先修改密码"
#: authentication/errors.py:316 authentication/mixins.py:332 #: authentication/errors.py:316 authentication/mixins.py:335
msgid "Your password has expired, please reset before logging in" msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录" msgstr "您的密码已过期,先修改再登录"
@ -1649,7 +1649,7 @@ msgstr "MFA 类型"
msgid "MFA code" msgid "MFA code"
msgstr "多因子认证验证码" msgstr "多因子认证验证码"
#: authentication/mixins.py:308 #: authentication/mixins.py:311
msgid "Please change your password" msgid "Please change your password"
msgstr "请修改密码" msgstr "请修改密码"
@ -1764,31 +1764,31 @@ msgstr "代码错误"
msgid "Welcome back, please enter username and password to login" msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录" msgstr "欢迎回来,请输入用户名和密码登录"
#: authentication/templates/authentication/login.html:183 #: authentication/templates/authentication/login.html:189
#: users/templates/users/forgot_password.html:15 #: users/templates/users/forgot_password.html:15
#: users/templates/users/forgot_password.html:16 #: users/templates/users/forgot_password.html:16
msgid "Forgot password" msgid "Forgot password"
msgstr "忘记密码" msgstr "忘记密码"
#: authentication/templates/authentication/login.html:190 #: authentication/templates/authentication/login.html:196
#: templates/_header_bar.html:83 #: templates/_header_bar.html:83
msgid "Login" msgid "Login"
msgstr "登录" msgstr "登录"
#: authentication/templates/authentication/login.html:197 #: authentication/templates/authentication/login.html:203
msgid "More login options" msgid "More login options"
msgstr "更多登录方式" msgstr "更多登录方式"
#: authentication/templates/authentication/login.html:200 #: authentication/templates/authentication/login.html:206
msgid "OpenID" msgid "OpenID"
msgstr "OpenID" msgstr "OpenID"
#: authentication/templates/authentication/login.html:205 #: authentication/templates/authentication/login.html:211
msgid "CAS" msgid "CAS"
msgstr "CAS" msgstr "CAS"
#: authentication/templates/authentication/login.html:220 #: authentication/templates/authentication/login.html:226
#: notifications/backends/__init__.py:14 users/models/user.py:661 #: notifications/backends/__init__.py:14 users/models/user.py:662
msgid "FeiShu" msgid "FeiShu"
msgstr "飞书" msgstr "飞书"
@ -2186,7 +2186,7 @@ msgstr ""
"div>" "div>"
#: notifications/backends/__init__.py:10 users/forms/profile.py:101 #: notifications/backends/__init__.py:10 users/forms/profile.py:101
#: users/models/user.py:606 #: users/models/user.py:607
msgid "Email" msgid "Email"
msgstr "邮件" msgstr "邮件"
@ -2342,22 +2342,22 @@ msgstr "任务结束"
msgid "Server performance" msgid "Server performance"
msgstr "监控告警" msgstr "监控告警"
#: ops/notifications.py:40 #: ops/notifications.py:47
#, python-brace-format #, python-brace-format
msgid "The terminal is offline: {name}" msgid "The terminal is offline: {name}"
msgstr "终端已离线: {name}" msgstr "终端已离线: {name}"
#: ops/notifications.py:46 #: ops/notifications.py:53
#, python-brace-format #, python-brace-format
msgid "[Disk] Disk used more than {max_threshold}%: => {value} ({name})" msgid "[Disk] Disk used more than {max_threshold}%: => {value} ({name})"
msgstr "[Disk] 硬盘使用率超过 {max_threshold}%: => {value} ({name})" msgstr "[Disk] 硬盘使用率超过 {max_threshold}%: => {value} ({name})"
#: ops/notifications.py:53 #: ops/notifications.py:60
#, python-brace-format #, python-brace-format
msgid "[Memory] Memory used more than {max_threshold}%: => {value} ({name})" msgid "[Memory] Memory used more than {max_threshold}%: => {value} ({name})"
msgstr "[Memory] 内存使用率超过 {max_threshold}%: => {value} ({name})" msgstr "[Memory] 内存使用率超过 {max_threshold}%: => {value} ({name})"
#: ops/notifications.py:60 #: ops/notifications.py:67
#, python-brace-format #, python-brace-format
msgid "[CPU] CPU load more than {max_threshold}: => {value} ({name})" msgid "[CPU] CPU load more than {max_threshold}: => {value} ({name})"
msgstr "[CPU] CPU 使用率超过 {max_threshold}: => {value} ({name})" msgstr "[CPU] CPU 使用率超过 {max_threshold}: => {value} ({name})"
@ -2404,7 +2404,7 @@ msgstr "组织审计员"
msgid "GLOBAL" msgid "GLOBAL"
msgstr "全局组织" msgstr "全局组织"
#: orgs/models.py:434 users/models/user.py:614 users/serializers/user.py:37 #: orgs/models.py:434 users/models/user.py:615 users/serializers/user.py:37
#: users/templates/users/_select_user_modal.html:15 #: users/templates/users/_select_user_modal.html:15
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
@ -2469,7 +2469,7 @@ msgid "Favorite"
msgstr "收藏夹" msgstr "收藏夹"
#: perms/models/base.py:47 templates/_nav.html:21 users/models/group.py:31 #: perms/models/base.py:47 templates/_nav.html:21 users/models/group.py:31
#: users/models/user.py:610 users/templates/users/_select_user_modal.html:16 #: users/models/user.py:611 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:39
#: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_asset_permission.html:67
#: users/templates/users/user_database_app_permission.html:38 #: users/templates/users/user_database_app_permission.html:38
@ -2478,9 +2478,9 @@ msgid "User group"
msgstr "用户组" msgstr "用户组"
#: perms/models/base.py:50 #: perms/models/base.py:50
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:56 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:48 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50
#: users/models/user.py:642 #: users/models/user.py:643
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
@ -2583,7 +2583,7 @@ msgstr "成功导入 {} 个用户 ( 组织: {} )"
msgid "Welcome to the JumpServer open source Bastion Host" msgid "Welcome to the JumpServer open source Bastion Host"
msgstr "欢迎使用JumpServer开源堡垒机" msgstr "欢迎使用JumpServer开源堡垒机"
#: settings/models.py:155 users/templates/users/reset_password.html:29 #: settings/models.py:191 users/templates/users/reset_password.html:29
msgid "Setting" msgid "Setting"
msgstr "设置" msgstr "设置"
@ -3132,7 +3132,7 @@ msgstr "下个设备登录,上次登录会被顶掉"
msgid "Only exist user login" msgid "Only exist user login"
msgstr "仅已存在用户登录" msgstr "仅已存在用户登录"
#: settings/serializers/security.py:66 settings/serializers/security.py:70 #: settings/serializers/security.py:66
msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet"
msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会失败" msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会失败"
@ -3140,6 +3140,10 @@ msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会
msgid "Only from source login" msgid "Only from source login"
msgstr "仅从用户来源登录" msgstr "仅从用户来源登录"
#: settings/serializers/security.py:70
msgid "Only log in from the user source property"
msgstr "开启后如果用户来源为本地CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:74 #: settings/serializers/security.py:74
msgid "MFA verify TTL" msgid "MFA verify TTL"
msgstr "MFA 校验有效期" msgstr "MFA 校验有效期"
@ -3980,7 +3984,7 @@ msgstr "链接失效"
#: terminal/models/sharing.py:50 #: terminal/models/sharing.py:50
msgid "Link expired" msgid "Link expired"
msgstr "是否过期" msgstr "链接过期"
#: terminal/models/sharing.py:63 #: terminal/models/sharing.py:63
msgid "Session sharing" msgid "Session sharing"
@ -4539,52 +4543,52 @@ msgstr "你有一个新的工单, 申请人 - {}"
msgid "Your ticket has been processed, processor - {}" msgid "Your ticket has been processed, processor - {}"
msgstr "你的工单已被处理, 处理人 - {}" msgstr "你的工单已被处理, 处理人 - {}"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:16 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:18
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:16 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:18
msgid "Apply name" msgid "Apply name"
msgstr "应用名称" msgstr "应用名称"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:35 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:37
msgid "Apply applications" msgid "Apply applications"
msgstr "申请应用" msgstr "申请应用"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:40 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:42
msgid "Apply applications display" msgid "Apply applications display"
msgstr "应用名称名称" msgstr "应用名称名称"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:44 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:46
msgid "Apply system users" msgid "Apply system users"
msgstr "系统用户" msgstr "系统用户"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:49 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:51
msgid "Apply system user display" msgid "Apply system user display"
msgstr "批准的系统用户名称" msgstr "批准的系统用户名称"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:69 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:71
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:61 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:63
#: tickets/serializers/ticket/ticket.py:127 #: tickets/serializers/ticket/ticket.py:127
msgid "Permission named `{}` already exists" msgid "Permission named `{}` already exists"
msgstr "授权名称 `{}` 已存在" msgstr "授权名称 `{}` 已存在"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:75 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:80
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:67 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:72
msgid "The expiration date should be greater than the start date" msgid "The expiration date should be greater than the start date"
msgstr "过期时间要大于开始时间" msgstr "过期时间要大于开始时间"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:20 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:22
msgid "Apply assets" msgid "Apply assets"
msgstr "申请资产" msgstr "申请资产"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:24 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:26
msgid "Approve assets display" msgid "Approve assets display"
msgstr "批准的资产名称" msgstr "批准的资产名称"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:29 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:31
msgid "Approve system users" msgid "Approve system users"
msgstr "批准的系统用户" msgstr "批准的系统用户"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:35
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:41 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:43
msgid "Apply assets display" msgid "Apply assets display"
msgstr "批准的资产名称" msgstr "批准的资产名称"
@ -4758,7 +4762,7 @@ msgstr "不能和原来的密钥相同"
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法" msgstr "SSH密钥不合法"
#: users/forms/profile.py:160 users/models/user.py:634 #: users/forms/profile.py:160 users/models/user.py:635
#: users/templates/users/user_password_update.html:48 #: users/templates/users/user_password_update.html:48
msgid "Public key" msgid "Public key"
msgstr "SSH公钥" msgstr "SSH公钥"
@ -4771,49 +4775,49 @@ msgstr "短信验证码"
msgid "Force enable" msgid "Force enable"
msgstr "强制启用" msgstr "强制启用"
#: users/models/user.py:583 #: users/models/user.py:584
msgid "Local" msgid "Local"
msgstr "数据库" msgstr "数据库"
#: users/models/user.py:617 #: users/models/user.py:618
msgid "Avatar" msgid "Avatar"
msgstr "头像" msgstr "头像"
#: users/models/user.py:620 #: users/models/user.py:621
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:631 #: users/models/user.py:632
msgid "Private key" msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
#: users/models/user.py:650 #: users/models/user.py:651
msgid "Source" msgid "Source"
msgstr "来源" msgstr "来源"
#: users/models/user.py:654 #: users/models/user.py:655
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最后更新密码日期" msgstr "最后更新密码日期"
#: users/models/user.py:657 #: users/models/user.py:658
msgid "Need update password" msgid "Need update password"
msgstr "需要更新密码" msgstr "需要更新密码"
#: users/models/user.py:817 #: users/models/user.py:822
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:820 #: users/models/user.py:825
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
#: users/notifications.py:17 users/notifications.py:50 #: users/notifications.py:19 users/notifications.py:52
#: users/templates/users/reset_password.html:5 #: users/templates/users/reset_password.html:5
#: users/templates/users/reset_password.html:6 #: users/templates/users/reset_password.html:6
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
#: users/notifications.py:18 #: users/notifications.py:20
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -4853,7 +4857,7 @@ msgstr ""
"%(login_url)s\n" "%(login_url)s\n"
"\n" "\n"
#: users/notifications.py:51 #: users/notifications.py:53
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -4897,12 +4901,12 @@ msgstr ""
" <br>\n" " <br>\n"
" " " "
#: users/notifications.py:90 users/notifications.py:122 #: users/notifications.py:92 users/notifications.py:124
#: users/views/profile/reset.py:127 #: users/views/profile/reset.py:127
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/notifications.py:91 #: users/notifications.py:93
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -4948,7 +4952,7 @@ msgstr ""
" \n" " \n"
" " " "
#: users/notifications.py:123 #: users/notifications.py:125
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5011,11 +5015,11 @@ msgstr ""
" \n" " \n"
" " " "
#: users/notifications.py:166 users/notifications.py:202 #: users/notifications.py:170 users/notifications.py:206
msgid "Security notice" msgid "Security notice"
msgstr "安全通知" msgstr "安全通知"
#: users/notifications.py:167 #: users/notifications.py:171
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5059,7 +5063,7 @@ msgstr ""
"\n" "\n"
" " " "
#: users/notifications.py:203 #: users/notifications.py:207
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5108,11 +5112,11 @@ msgstr ""
" <br>\n" " <br>\n"
" " " "
#: users/notifications.py:240 users/notifications.py:259 #: users/notifications.py:244 users/notifications.py:263
msgid "Expiration notice" msgid "Expiration notice"
msgstr "过期通知" msgstr "过期通知"
#: users/notifications.py:241 #: users/notifications.py:245
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5133,7 +5137,7 @@ msgstr ""
"为了不影响您正常工作,请联系管理员确认。\n" "为了不影响您正常工作,请联系管理员确认。\n"
" " " "
#: users/notifications.py:260 #: users/notifications.py:264
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5155,11 +5159,11 @@ msgstr ""
" <br>\n" " <br>\n"
" " " "
#: users/notifications.py:280 users/notifications.py:301 #: users/notifications.py:284 users/notifications.py:305
msgid "SSH Key Reset" msgid "SSH Key Reset"
msgstr "重置SSH密钥" msgstr "重置SSH密钥"
#: users/notifications.py:281 #: users/notifications.py:285
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5184,7 +5188,7 @@ msgstr ""
"\n" "\n"
" " " "
#: users/notifications.py:302 #: users/notifications.py:306
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5209,11 +5213,11 @@ msgstr ""
" <br>\n" " <br>\n"
" " " "
#: users/notifications.py:324 users/notifications.py:344 #: users/notifications.py:328 users/notifications.py:348
msgid "MFA Reset" msgid "MFA Reset"
msgstr "重置 MFA" msgstr "重置 MFA"
#: users/notifications.py:325 #: users/notifications.py:329
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5238,7 +5242,7 @@ msgstr ""
"\n" "\n"
" " " "
#: users/notifications.py:345 #: users/notifications.py:349
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -5267,7 +5271,7 @@ msgstr ""
msgid "The old password is incorrect" msgid "The old password is incorrect"
msgstr "旧密码错误" msgstr "旧密码错误"
#: users/serializers/profile.py:36 users/serializers/user.py:140 #: users/serializers/profile.py:36 users/serializers/user.py:141
msgid "Password does not match security rules" msgid "Password does not match security rules"
msgstr "密码不满足安全规则" msgstr "密码不满足安全规则"
@ -5353,11 +5357,15 @@ msgstr "是否绑定了钉钉"
msgid "Is feishu bound" msgid "Is feishu bound"
msgstr "是否绑定了飞书" msgstr "是否绑定了飞书"
#: users/serializers/user.py:114 #: users/serializers/user.py:91
msgid "Is OTP bound"
msgstr "是否绑定了虚拟MFA"
#: users/serializers/user.py:115
msgid "Role limit to {}" msgid "Role limit to {}"
msgstr "角色只能为 {}" msgstr "角色只能为 {}"
#: users/serializers/user.py:234 #: users/serializers/user.py:235
msgid "name not unique" msgid "name not unique"
msgstr "名称重复" msgstr "名称重复"
@ -6243,3 +6251,6 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid "Only "
#~ msgstr "仅能从用户配置来源登录"

View File

@ -17,13 +17,20 @@ class ServerPerformanceMessage(SystemMessage):
def __init__(self, msg): def __init__(self, msg):
self._msg = msg self._msg = msg
def get_common_msg(self): def get_html_msg(self) -> dict:
subject = self._msg[:80] subject = self._msg[:80]
return { return {
'subject': subject.replace('<br>', '; '), 'subject': subject.replace('<br>', '; '),
'message': self._msg 'message': self._msg
} }
def get_text_msg(self) -> dict:
subject = self._msg[:80]
return {
'subject': subject.replace('<br>', '; '),
'message': self._msg.replace('<br>', '\n')
}
@classmethod @classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription): def post_insert_to_db(cls, subscription: SystemMsgSubscription):
admins = User.objects.filter(role=User.ROLE.ADMIN) admins = User.objects.filter(role=User.ROLE.ADMIN)
@ -39,14 +46,14 @@ class ServerPerformanceCheckUtil(object):
'max_threshold': False, 'max_threshold': False,
'alarm_msg_format': _('The terminal is offline: {name}') 'alarm_msg_format': _('The terminal is offline: {name}')
}, },
'disk_usage': { 'disk_used': {
'default': 0, 'default': 0,
'max_threshold': 80, 'max_threshold': 80,
'alarm_msg_format': _( 'alarm_msg_format': _(
'[Disk] Disk used more than {max_threshold}%: => {value} ({name})' '[Disk] Disk used more than {max_threshold}%: => {value} ({name})'
) )
}, },
'memory_usage': { 'memory_used': {
'default': 0, 'default': 0,
'max_threshold': 85, 'max_threshold': 85,
'alarm_msg_format': _( 'alarm_msg_format': _(
@ -82,7 +89,6 @@ class ServerPerformanceCheckUtil(object):
default = data['default'] default = data['default']
max_threshold = data['max_threshold'] max_threshold = data['max_threshold']
value = getattr(self._terminal.stat, item, default) value = getattr(self._terminal.stat, item, default)
print(value, max_threshold, self._terminal.name, self._terminal.id)
if isinstance(value, bool) and value != max_threshold: if isinstance(value, bool) and value != max_threshold:
return return
elif isinstance(value, (int, float)) and value < max_threshold: elif isinstance(value, (int, float)) and value < max_threshold:

View File

@ -84,6 +84,7 @@ class Setting(models.Model):
getattr(self.__class__, f'refresh_{self.name}')() getattr(self.__class__, f'refresh_{self.name}')()
else: else:
setattr(settings, self.name, self.cleaned_value) setattr(settings, self.name, self.cleaned_value)
self.refresh_keycloak_to_openid_if_need()
@classmethod @classmethod
def refresh_authentications(cls, name): def refresh_authentications(cls, name):
@ -129,6 +130,41 @@ class Setting(models.Model):
def refresh_AUTH_OPENID(cls): def refresh_AUTH_OPENID(cls):
cls.refresh_authentications('AUTH_OPENID') cls.refresh_authentications('AUTH_OPENID')
def refresh_keycloak_to_openid_if_need(self):
watch_config_names = [
'AUTH_OPENID', 'AUTH_OPENID_REALM_NAME', 'AUTH_OPENID_SERVER_URL',
'AUTH_OPENID_PROVIDER_ENDPOINT', 'AUTH_OPENID_KEYCLOAK'
]
if self.name not in watch_config_names:
# 不在监听的配置中, 不需要刷新
return
auth_keycloak = self.__class__.objects.filter(name='AUTH_OPENID_KEYCLOAK').first()
if not auth_keycloak or not auth_keycloak.cleaned_value:
# 关闭 Keycloak 方式的配置, 不需要刷新
return
from jumpserver.conf import Config
config_names = [
'AUTH_OPENID', 'AUTH_OPENID_REALM_NAME',
'AUTH_OPENID_SERVER_URL', 'AUTH_OPENID_PROVIDER_ENDPOINT'
]
# 获取当前 keycloak 配置
keycloak_config = {}
for name in config_names:
setting = self.__class__.objects.filter(name=name).first()
if not setting:
continue
value = setting.cleaned_value
keycloak_config[name] = value
# 转化 keycloak 配置为 openid 配置
openid_config = Config.convert_keycloak_to_openid(keycloak_config)
if not openid_config:
return
# 刷新 settings
for key, value in openid_config.items():
setattr(settings, key, value)
@classmethod @classmethod
def refresh_AUTH_RADIUS(cls): def refresh_AUTH_RADIUS(cls):
cls.refresh_authentications('AUTH_RADIUS') cls.refresh_authentications('AUTH_RADIUS')

View File

@ -67,10 +67,10 @@ class SecurityAuthSerializer(serializers.Serializer):
) )
ONLY_ALLOW_AUTH_FROM_SOURCE = serializers.BooleanField( ONLY_ALLOW_AUTH_FROM_SOURCE = serializers.BooleanField(
required=False, default=False, label=_("Only from source login"), required=False, default=False, label=_("Only from source login"),
help_text=_("If enable, CAS、OIDC auth will be failed, if user not exist yet") help_text=_("Only log in from the user source property")
) )
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField( SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(
min_value=5, max_value=60*30, min_value=5, max_value=60*60*10,
label=_("MFA verify TTL"), help_text=_("Unit: second"), label=_("MFA verify TTL"), help_text=_("Unit: second"),
) )
SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField( SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField(

View File

@ -1,9 +1,13 @@
from datetime import datetime
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from perms.models import ApplicationPermission from perms.models import ApplicationPermission
from applications.const import AppCategory, AppType from applications.const import AppCategory, AppType
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from tickets.models import Ticket from tickets.models import Ticket
from applications.models import Application
from assets.models import SystemUser
from .common import DefaultPermissionName from .common import DefaultPermissionName
__all__ = [ __all__ = [
@ -69,9 +73,19 @@ class ApplySerializer(serializers.Serializer):
'Permission named `{}` already exists'.format(permission_name) 'Permission named `{}` already exists'.format(permission_name)
)) ))
def validate_apply_date_expired(self, apply_date_expired): def validate_apply_applications(self, apply_applications):
apply_date_start = self.root.initial_data['meta'].get('apply_date_start') type = self.root.initial_data['meta'].get('apply_type')
if str(apply_date_expired) <= apply_date_start: org_id = self.root.initial_data.get('org_id')
with tmp_to_org(org_id):
applications = Application.objects.filter(id__in=apply_applications, type=type).values_list('id', flat=True)
return list(applications)
def validate_apply_date_expired(self, value):
date_start = self.root.initial_data['meta'].get('apply_date_start')
date_start = datetime.strptime(date_start, '%Y-%m-%dT%H:%M:%S.%fZ')
date_expired = self.root.initial_data['meta'].get('apply_date_expired')
date_expired = datetime.strptime(date_expired, '%Y-%m-%dT%H:%M:%S.%fZ')
if date_expired <= date_start:
error = _('The expiration date should be greater than the start date') error = _('The expiration date should be greater than the start date')
raise serializers.ValidationError(error) raise serializers.ValidationError(error)
return apply_date_expired return value

View File

@ -1,3 +1,5 @@
from datetime import datetime
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from perms.serializers import ActionsField from perms.serializers import ActionsField
@ -61,9 +63,12 @@ class ApplySerializer(serializers.Serializer):
'Permission named `{}` already exists'.format(permission_name) 'Permission named `{}` already exists'.format(permission_name)
)) ))
def validate_apply_date_expired(self, apply_date_expired): def validate_apply_date_expired(self, value):
apply_date_start = self.root.initial_data['meta'].get('apply_date_start') date_start = self.root.initial_data['meta'].get('apply_date_start')
if str(apply_date_expired) <= apply_date_start: date_start = datetime.strptime(date_start, '%Y-%m-%dT%H:%M:%S.%fZ')
date_expired = self.root.initial_data['meta'].get('apply_date_expired')
date_expired = datetime.strptime(date_expired, '%Y-%m-%dT%H:%M:%S.%fZ')
if date_expired <= date_start:
error = _('The expiration date should be greater than the start date') error = _('The expiration date should be greater than the start date')
raise serializers.ValidationError(error) raise serializers.ValidationError(error)
return apply_date_expired return value

View File

@ -472,6 +472,7 @@ class MFAMixin:
(2, _("Force enable")), (2, _("Force enable")),
) )
is_org_admin: bool is_org_admin: bool
username: str
@property @property
def mfa_enabled(self): def mfa_enabled(self):
@ -685,6 +686,10 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
def is_feishu_bound(self): def is_feishu_bound(self):
return bool(self.feishu_id) return bool(self.feishu_id)
@property
def is_otp_secret_key_bound(self):
return bool(self.otp_secret_key)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('users:user-detail', args=(self.id,)) return reverse('users:user-detail', args=(self.id,))

View File

@ -1,6 +1,8 @@
from datetime import datetime from datetime import datetime
from urllib.parse import urljoin
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings
from common.utils import reverse, get_request_ip_or_data, get_request_user_agent, lazyproperty from common.utils import reverse, get_request_ip_or_data, get_request_user_agent, lazyproperty
from notifications.notifications import UserMessage from notifications.notifications import UserMessage
@ -160,6 +162,8 @@ Browser: %(browser)s
class PasswordExpirationReminderMsg(UserMessage): class PasswordExpirationReminderMsg(UserMessage):
update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate')
def get_text_msg(self) -> dict: def get_text_msg(self) -> dict:
user = self.user user = self.user
@ -186,7 +190,7 @@ Login direct 👇
'name': user.name, 'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp( 'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
user.date_password_expired)).strftime('%Y-%m-%d %H:%M'), user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
'update_password_url': reverse('users:user-password-update', external=True), 'update_password_url': self.update_password_url,
'forget_password_url': reverse('authentication:forgot-password', external=True), 'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email, 'email': user.email,
'login_url': reverse('authentication:login', external=True), 'login_url': reverse('authentication:login', external=True),
@ -224,7 +228,7 @@ Login direct 👇
'name': user.name, 'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp( 'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
user.date_password_expired)).strftime('%Y-%m-%d %H:%M'), user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
'update_password_url': reverse('users:user-password-update', external=True), 'update_password_url': self.update_password_url,
'forget_password_url': reverse('authentication:forgot-password', external=True), 'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email, 'email': user.email,
'login_url': reverse('authentication:login', external=True), 'login_url': reverse('authentication:login', external=True),

View File

@ -51,7 +51,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
'mfa_enabled', 'is_valid', 'is_expired', 'is_active', # 布尔字段 'mfa_enabled', 'is_valid', 'is_expired', 'is_active', # 布尔字段
'date_expired', 'date_joined', 'last_login', # 日期字段 'date_expired', 'date_joined', 'last_login', # 日期字段
'created_by', 'comment', # 通用字段 'created_by', 'comment', # 通用字段
'is_wecom_bound', 'is_dingtalk_bound', 'is_feishu_bound', 'is_wecom_bound', 'is_dingtalk_bound', 'is_feishu_bound', 'is_otp_secret_key_bound',
'wecom_id', 'dingtalk_id', 'feishu_id' 'wecom_id', 'dingtalk_id', 'feishu_id'
] ]
# 包含不太常用的字段,可以没有 # 包含不太常用的字段,可以没有
@ -88,6 +88,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
'is_wecom_bound': {'label': _('Is wecom bound')}, 'is_wecom_bound': {'label': _('Is wecom bound')},
'is_dingtalk_bound': {'label': _('Is dingtalk bound')}, 'is_dingtalk_bound': {'label': _('Is dingtalk bound')},
'is_feishu_bound': {'label': _('Is feishu bound')}, 'is_feishu_bound': {'label': _('Is feishu bound')},
'is_otp_secret_key_bound': {'label': _('Is OTP bound')},
'phone': {'validators': [PhoneValidator()]}, 'phone': {'validators': [PhoneValidator()]},
} }