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,
sock=sock,
timeout=5)
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
paramiko.AuthenticationException, TimeoutError) as e:
except (paramiko.SSHException,
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
return False, str(e)
return False, err
finally:
client.close()
self.is_connective = True

View File

@ -1,7 +1,7 @@
from django.dispatch import receiver
from django.apps import apps
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 ..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)
@receiver(post_delete, sender=AuthBook)
@receiver(pre_delete, sender=AuthBook)
def on_authbook_post_delete(sender, instance, **kwargs):
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'))
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:
options['drivestoredirect:s'] = '*'
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):
bases = []
if settings.SECURITY_LOGIN_CAPTCHA_ENABLED and captcha:
bases.append(CaptchaMixin)
if settings.SECURITY_LOGIN_CHALLENGE_ENABLED:
bases.append(ChallengeMixin)
elif settings.SECURITY_LOGIN_CAPTCHA_ENABLED and captcha:
bases.append(CaptchaMixin)
bases.append(UserLoginForm)
return type('UserLoginForm', tuple(bases), {})

View File

@ -191,7 +191,10 @@ class AuthMixin(PasswordEncryptionViewMixin):
raise self.partial_credential_error(error=error)
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):
request = self.request

View File

@ -147,7 +147,7 @@
{% csrf_token %}
<div style="line-height: 17px;margin-bottom: 20px;color: #999999;">
{% if form.errors %}
<p class="red-fonts" style="color: red">
<p class="help-block">
{% if form.non_field_errors %}
{{ form.non_field_errors.as_text }}
{% endif %}
@ -160,9 +160,15 @@
</div>
{% 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 }}">
{% if form.password.errors %}
<p class="help-block" style="text-align: left">
{{ form.password.errors.as_text }}
</p>
{% endif %}
</div>
{% if form.challenge %}
{% 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' %}
</a>
{% endif %}
</div>
{% else %}
<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>
function encryptLoginPassword(password, rsaPublicKey) {
if (!password) {
return ''
}
var jsencrypt = new JSEncrypt(); //加密对象
jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥
return jsencrypt.encrypt(password); //加密

View File

@ -134,7 +134,6 @@ function cancelTicket() {
}
function cancelCloseConfirm() {
cancelTicket();
window.onbeforeunload = function() {};
window.onunload = function(){};
}
@ -158,6 +157,7 @@ $(document).ready(function () {
cancelCloseConfirm();
window.location.reload();
}).on('click', '.btn-return', function () {
cancelTicket();
cancelCloseConfirm();
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.shortcuts import reverse, redirect
from django.utils.decorators import method_decorator
from django.db import transaction
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
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.urls import reverse_lazy
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 (
redirect_user_first_login_or_index
)
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
@ -109,7 +109,7 @@ class UserLoginView(mixins.AuthMixin, FormView):
self.request.session.delete_test_cookie()
try:
with atomic():
with transaction.atomic():
self.check_user_auth(decrypt_passwd=True)
except errors.AuthFailedError as e:
form.add_error(None, e.msg)

View File

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

Binary file not shown.

View File

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

View File

@ -17,13 +17,20 @@ class ServerPerformanceMessage(SystemMessage):
def __init__(self, msg):
self._msg = msg
def get_common_msg(self):
def get_html_msg(self) -> dict:
subject = self._msg[:80]
return {
'subject': subject.replace('<br>', '; '),
'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
def post_insert_to_db(cls, subscription: SystemMsgSubscription):
admins = User.objects.filter(role=User.ROLE.ADMIN)
@ -39,14 +46,14 @@ class ServerPerformanceCheckUtil(object):
'max_threshold': False,
'alarm_msg_format': _('The terminal is offline: {name}')
},
'disk_usage': {
'disk_used': {
'default': 0,
'max_threshold': 80,
'alarm_msg_format': _(
'[Disk] Disk used more than {max_threshold}%: => {value} ({name})'
)
},
'memory_usage': {
'memory_used': {
'default': 0,
'max_threshold': 85,
'alarm_msg_format': _(
@ -82,7 +89,6 @@ class ServerPerformanceCheckUtil(object):
default = data['default']
max_threshold = data['max_threshold']
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:
return
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}')()
else:
setattr(settings, self.name, self.cleaned_value)
self.refresh_keycloak_to_openid_if_need()
@classmethod
def refresh_authentications(cls, name):
@ -129,6 +130,41 @@ class Setting(models.Model):
def refresh_AUTH_OPENID(cls):
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
def refresh_AUTH_RADIUS(cls):
cls.refresh_authentications('AUTH_RADIUS')

View File

@ -67,10 +67,10 @@ class SecurityAuthSerializer(serializers.Serializer):
)
ONLY_ALLOW_AUTH_FROM_SOURCE = serializers.BooleanField(
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(
min_value=5, max_value=60*30,
min_value=5, max_value=60*60*10,
label=_("MFA verify TTL"), help_text=_("Unit: second"),
)
SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField(

View File

@ -1,9 +1,13 @@
from datetime import datetime
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from perms.models import ApplicationPermission
from applications.const import AppCategory, AppType
from orgs.utils import tmp_to_org
from tickets.models import Ticket
from applications.models import Application
from assets.models import SystemUser
from .common import DefaultPermissionName
__all__ = [
@ -69,9 +73,19 @@ class ApplySerializer(serializers.Serializer):
'Permission named `{}` already exists'.format(permission_name)
))
def validate_apply_date_expired(self, apply_date_expired):
apply_date_start = self.root.initial_data['meta'].get('apply_date_start')
if str(apply_date_expired) <= apply_date_start:
def validate_apply_applications(self, apply_applications):
type = self.root.initial_data['meta'].get('apply_type')
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')
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 rest_framework import serializers
from perms.serializers import ActionsField
@ -61,9 +63,12 @@ class ApplySerializer(serializers.Serializer):
'Permission named `{}` already exists'.format(permission_name)
))
def validate_apply_date_expired(self, apply_date_expired):
apply_date_start = self.root.initial_data['meta'].get('apply_date_start')
if str(apply_date_expired) <= apply_date_start:
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')
raise serializers.ValidationError(error)
return apply_date_expired
return value

View File

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

View File

@ -1,6 +1,8 @@
from datetime import datetime
from urllib.parse import urljoin
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 notifications.notifications import UserMessage
@ -160,6 +162,8 @@ Browser: %(browser)s
class PasswordExpirationReminderMsg(UserMessage):
update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate')
def get_text_msg(self) -> dict:
user = self.user
@ -186,7 +190,7 @@ Login direct 👇
'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
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),
'email': user.email,
'login_url': reverse('authentication:login', external=True),
@ -224,7 +228,7 @@ Login direct 👇
'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
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),
'email': user.email,
'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', # 布尔字段
'date_expired', 'date_joined', 'last_login', # 日期字段
'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'
]
# 包含不太常用的字段,可以没有
@ -88,6 +88,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
'is_wecom_bound': {'label': _('Is wecom bound')},
'is_dingtalk_bound': {'label': _('Is dingtalk bound')},
'is_feishu_bound': {'label': _('Is feishu bound')},
'is_otp_secret_key_bound': {'label': _('Is OTP bound')},
'phone': {'validators': [PhoneValidator()]},
}