Merge branch 'dev' of github.com:jumpserver/jumpserver into dev

pull/6181/head
ibuler 2021-05-20 15:53:16 +08:00
commit 2d9ce16601
24 changed files with 256 additions and 176 deletions

View File

@ -263,7 +263,7 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向
## 致谢 ## 致谢
- [Apache Guacamole](https://guacamole.apache.org/) Web页面连接 RDP, SSH, VNC协议设备JumpServer 图形化连接依赖 - [Apache Guacamole](https://guacamole.apache.org/) Web页面连接 RDP, SSH, VNC协议设备JumpServer 图形化组件 Lion 依赖
- [OmniDB](https://omnidb.org/) Web页面连接使用数据库JumpServer Web数据库依赖 - [OmniDB](https://omnidb.org/) Web页面连接使用数据库JumpServer Web数据库依赖

View File

@ -98,8 +98,8 @@ class SystemUserTaskApi(generics.CreateAPIView):
return task return task
@staticmethod @staticmethod
def do_test(system_user): def do_test(system_user, asset_ids):
task = test_system_user_connectivity_manual.delay(system_user) task = test_system_user_connectivity_manual.delay(system_user, asset_ids)
return task return task
def get_object(self): def get_object(self):
@ -109,16 +109,20 @@ class SystemUserTaskApi(generics.CreateAPIView):
def perform_create(self, serializer): def perform_create(self, serializer):
action = serializer.validated_data["action"] action = serializer.validated_data["action"]
asset = serializer.validated_data.get('asset') asset = serializer.validated_data.get('asset')
assets = serializer.validated_data.get('assets') or []
if asset:
assets = [asset]
else:
assets = serializer.validated_data.get('assets') or []
asset_ids = [asset.id for asset in assets]
asset_ids = asset_ids if asset_ids else None
system_user = self.get_object() system_user = self.get_object()
if action == 'push': if action == 'push':
assets = [asset] if asset else assets
asset_ids = [asset.id for asset in assets]
asset_ids = asset_ids if asset_ids else None
task = self.do_push(system_user, asset_ids) task = self.do_push(system_user, asset_ids)
else: else:
task = self.do_test(system_user) task = self.do_test(system_user, asset_ids)
data = getattr(serializer, '_data', {}) data = getattr(serializer, '_data', {})
data["task"] = task.id data["task"] = task.id
setattr(serializer, '_data', data) setattr(serializer, '_data', data)

View File

@ -4,7 +4,7 @@ import re
from rest_framework import serializers from rest_framework import serializers
from common.drf.serializers import AdaptedBulkListSerializer from common.drf.serializers import AdaptedBulkListSerializer
from ..models import CommandFilter, CommandFilterRule, SystemUser from ..models import CommandFilter, CommandFilterRule
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from common.utils import get_object_or_none, lazyproperty from common.utils import get_object_or_none, lazyproperty
@ -50,6 +50,20 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
fields = '__all__' fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_action_choices()
def set_action_choices(self):
from django.conf import settings
action = self.fields.get('action')
if not action:
return
choices = action._choices
if not settings.XPACK_ENABLED:
choices.pop(CommandFilterRule.ActionChoices.confirm, None)
action._choices = choices
# def validate_content(self, content): # def validate_content(self, content):
# tp = self.initial_data.get("type") # tp = self.initial_data.get("type")
# if tp == CommandFilterRule.TYPE_REGEX: # if tp == CommandFilterRule.TYPE_REGEX:

View File

@ -61,7 +61,9 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
fields_fk = ['domain'] fields_fk = ['domain']
fields = fields_small + fields_fk fields = fields_small + fields_fk
extra_kwargs = { extra_kwargs = {
'password': {'validators': [NoSpecialChars()]} 'password': {'write_only': True, 'validators': [NoSpecialChars()]},
'private_key': {"write_only": True},
'public_key': {"write_only": True},
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -5,6 +5,7 @@ from collections import defaultdict
from celery import shared_task from celery import shared_task
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from assets.models import Asset
from common.utils import get_logger from common.utils import get_logger
from orgs.utils import tmp_to_org, org_aware_func from orgs.utils import tmp_to_org, org_aware_func
from ..models import SystemUser from ..models import SystemUser
@ -96,9 +97,12 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
@shared_task(queue="ansible") @shared_task(queue="ansible")
@org_aware_func("system_user") @org_aware_func("system_user")
def test_system_user_connectivity_manual(system_user): def test_system_user_connectivity_manual(system_user, asset_ids=None):
task_name = _("Test system user connectivity: {}").format(system_user) task_name = _("Test system user connectivity: {}").format(system_user)
assets = system_user.get_related_assets() if asset_ids:
assets = Asset.objects.filter(id__in=asset_ids)
else:
assets = system_user.get_related_assets()
test_system_user_connectivity_util(system_user, assets, task_name) test_system_user_connectivity_util(system_user, assets, task_name)

View File

@ -21,7 +21,7 @@ class DingTalkQRUnBindBase(APIView):
if not user.dingtalk_id: if not user.dingtalk_id:
raise errors.DingTalkNotBound raise errors.DingTalkNotBound
user.dingtalk_id = '' user.dingtalk_id = None
user.save() user.save()
return Response() return Response()

View File

@ -21,7 +21,7 @@ class WeComQRUnBindBase(APIView):
if not user.wecom_id: if not user.wecom_id:
raise errors.WeComNotBound raise errors.WeComNotBound
user.wecom_id = '' user.wecom_id = None
user.save() user.save()
return Response() return Response()

View File

@ -8,7 +8,7 @@ from django.core.cache import cache
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from six import text_type from six import text_type
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend as DJModelBackend
from rest_framework import HTTP_HEADER_ENCODING from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import authentication, exceptions from rest_framework import authentication, exceptions
from common.auth import signature from common.auth import signature
@ -25,6 +25,11 @@ def get_request_date_header(request):
return date return date
class ModelBackend(DJModelBackend):
def user_can_authenticate(self, user):
return user.is_valid
class AccessKeyAuthentication(authentication.BaseAuthentication): class AccessKeyAuthentication(authentication.BaseAuthentication):
"""App使用Access key进行签名认证, 目前签名算法比较简单, """App使用Access key进行签名认证, 目前签名算法比较简单,
app注册或者手动建立后,会生成 access_key_id access_key_secret, app注册或者手动建立后,会生成 access_key_id access_key_secret,

View File

@ -2,7 +2,7 @@ import urllib
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views import View from django.views import View

View File

@ -2,7 +2,7 @@ import urllib
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views import View from django.views import View

View File

@ -189,6 +189,7 @@ class DatesLoginMetricMixin:
def get_dates_login_times_top10_users(self): def get_dates_login_times_top10_users(self):
users = self.sessions_queryset.values("user_id") \ users = self.sessions_queryset.values("user_id") \
.annotate(total=Count("user_id")) \ .annotate(total=Count("user_id")) \
.annotate(user=Max('user')) \
.annotate(last=Max("date_start")).order_by("-total")[:10] .annotate(last=Max("date_start")).order_by("-total")[:10]
for user in users: for user in users:
user['last'] = str(user['last']) user['last'] = str(user['last'])

View File

@ -120,7 +120,7 @@ LOGIN_CONFIRM_ENABLE = CONFIG.LOGIN_CONFIRM_ENABLE
OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS
AUTH_BACKEND_MODEL = 'django.contrib.auth.backends.ModelBackend' AUTH_BACKEND_MODEL = 'authentication.backends.api.ModelBackend'
AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend' AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend'
AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend' AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend'
AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend' AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend'

Binary file not shown.

View File

@ -3,19 +3,19 @@
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy
msgid "" 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-05-17 16:17+0800\n" "POT-Creation-Date: 2021-05-20 14:56+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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"
"Language: zh_CN\n" "Language: zh_CN\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.3\n"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:11 assets/models/asset.py:142 #: applications/models/application.py:11 assets/models/asset.py:142
@ -25,7 +25,7 @@ msgstr ""
#: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29 #: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29
#: terminal/models/storage.py:23 terminal/models/storage.py:90 #: terminal/models/storage.py:23 terminal/models/storage.py:90
#: 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:558 #: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:550
#: 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
@ -61,7 +61,7 @@ msgstr "激活中"
#: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34 #: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34
#: terminal/models/storage.py:29 terminal/models/storage.py:96 #: terminal/models/storage.py:29 terminal/models/storage.py:96
#: terminal/models/terminal.py:114 tickets/models/ticket.py:73 #: terminal/models/terminal.py:114 tickets/models/ticket.py:73
#: users/models/group.py:16 users/models/user.py:591 #: users/models/group.py:16 users/models/user.py:583
#: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:35 #: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:35
#: xpack/plugins/cloud/models.py:98 xpack/plugins/gathered_user/models.py:26 #: xpack/plugins/cloud/models.py:98 xpack/plugins/gathered_user/models.py:26
msgid "Comment" msgid "Comment"
@ -98,8 +98,8 @@ msgstr "动作"
#: perms/models/base.py:50 templates/index.html:78 #: perms/models/base.py:50 templates/index.html:78
#: terminal/backends/command/models.py:18 #: terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38
#: tickets/models/comment.py:17 users/models/user.py:184 #: tickets/models/comment.py:17 users/models/user.py:176
#: users/models/user.py:746 users/models/user.py:772 #: users/models/user.py:738 users/models/user.py:764
#: users/serializers/group.py:20 #: users/serializers/group.py:20
#: 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
@ -180,11 +180,11 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: applications/serializers/attrs/application_type/vmware_client.py:26 #: applications/serializers/attrs/application_type/vmware_client.py:26
#: assets/models/base.py:251 assets/models/gathered_user.py:15 #: assets/models/base.py:251 assets/models/gathered_user.py:15
#: audits/models.py:100 authentication/forms.py:15 authentication/forms.py:17 #: audits/models.py:100 authentication/forms.py:15 authentication/forms.py:17
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:556 #: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:548
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: xpack/plugins/change_auth_plan/models.py:47 #: xpack/plugins/change_auth_plan/models.py:47
#: xpack/plugins/change_auth_plan/models.py:278 #: xpack/plugins/change_auth_plan/models.py:278
#: xpack/plugins/cloud/serializers.py:51 #: xpack/plugins/cloud/serializers.py:71
msgid "Username" msgid "Username"
msgstr "用户名" msgstr "用户名"
@ -285,7 +285,7 @@ msgid "Cluster"
msgstr "集群" msgstr "集群"
#: applications/serializers/attrs/application_category/db.py:11 #: applications/serializers/attrs/application_category/db.py:11
#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:49 #: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:69
msgid "Host" msgid "Host"
msgstr "主机" msgstr "主机"
@ -295,7 +295,7 @@ msgstr "主机"
#: applications/serializers/attrs/application_type/oracle.py:11 #: applications/serializers/attrs/application_type/oracle.py:11
#: applications/serializers/attrs/application_type/pgsql.py:11 #: applications/serializers/attrs/application_type/pgsql.py:11
#: assets/models/asset.py:188 assets/models/domain.py:53 #: assets/models/asset.py:188 assets/models/domain.py:53
#: xpack/plugins/cloud/serializers.py:50 #: xpack/plugins/cloud/serializers.py:70
msgid "Port" msgid "Port"
msgstr "端口" msgstr "端口"
@ -325,7 +325,7 @@ msgstr "目标URL"
#: xpack/plugins/change_auth_plan/models.py:68 #: xpack/plugins/change_auth_plan/models.py:68
#: xpack/plugins/change_auth_plan/models.py:190 #: xpack/plugins/change_auth_plan/models.py:190
#: xpack/plugins/change_auth_plan/models.py:285 #: xpack/plugins/change_auth_plan/models.py:285
#: xpack/plugins/cloud/serializers.py:53 #: xpack/plugins/cloud/serializers.py:73
msgid "Password" msgid "Password"
msgstr "密码" msgstr "密码"
@ -407,7 +407,7 @@ msgstr "激活"
#: assets/models/asset.py:196 assets/models/cluster.py:19 #: assets/models/asset.py:196 assets/models/cluster.py:19
#: assets/models/user.py:66 templates/_nav.html:44 #: assets/models/user.py:66 templates/_nav.html:44
#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:146 #: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:166
msgid "Admin user" msgid "Admin user"
msgstr "管理用户" msgstr "管理用户"
@ -483,7 +483,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:24 #: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24
#: orgs/models.py:422 perms/models/base.py:55 users/models/user.py:599 #: orgs/models.py:422 perms/models/base.py:55 users/models/user.py:591
#: users/serializers/group.py:35 xpack/plugins/change_auth_plan/models.py:81 #: users/serializers/group.py:35 xpack/plugins/change_auth_plan/models.py:81
#: xpack/plugins/cloud/models.py:104 xpack/plugins/gathered_user/models.py:30 #: xpack/plugins/cloud/models.py:104 xpack/plugins/gathered_user/models.py:30
msgid "Created by" msgid "Created by"
@ -497,7 +497,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:25 #: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25
#: orgs/models.py:420 perms/models/base.py:56 users/models/group.py:18 #: orgs/models.py:420 perms/models/base.py:56 users/models/group.py:18
#: users/models/user.py:773 xpack/plugins/cloud/models.py:107 #: users/models/user.py:765 xpack/plugins/cloud/models.py:107
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
@ -543,7 +543,7 @@ msgstr "带宽"
msgid "Contact" msgid "Contact"
msgstr "联系人" msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:577 #: assets/models/cluster.py:22 users/models/user.py:569
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
@ -569,7 +569,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:758 #: users/models/user.py:750
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
@ -678,7 +678,7 @@ msgstr "ssh私钥"
#: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158 #: users/templates/users/user_asset_permission.html:158
#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:147 #: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:167
msgid "Node" msgid "Node"
msgstr "节点" msgstr "节点"
@ -814,11 +814,11 @@ msgid "Backend"
msgstr "后端" msgstr "后端"
#: assets/serializers/asset_user.py:80 users/forms/profile.py:160 #: assets/serializers/asset_user.py:80 users/forms/profile.py:160
#: users/models/user.py:588 users/templates/users/user_password_update.html:48 #: users/models/user.py:580 users/templates/users/user_password_update.html:48
msgid "Public key" msgid "Public key"
msgstr "SSH公钥" msgstr "SSH公钥"
#: assets/serializers/asset_user.py:84 users/models/user.py:585 #: assets/serializers/asset_user.py:84 users/models/user.py:577
msgid "Private key" msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
@ -949,7 +949,7 @@ msgid ""
msgstr "自检程序已经在运行,不能重复启动" msgstr "自检程序已经在运行,不能重复启动"
#: assets/tasks/push_system_user.py:193 #: assets/tasks/push_system_user.py:193
#: assets/tasks/system_user_connectivity.py:89 #: assets/tasks/system_user_connectivity.py:90
msgid "System user is dynamic: {}" msgid "System user is dynamic: {}"
msgstr "系统用户是动态的: {}" msgstr "系统用户是动态的: {}"
@ -958,7 +958,7 @@ msgid "Start push system user for platform: [{}]"
msgstr "推送系统用户到平台: [{}]" msgstr "推送系统用户到平台: [{}]"
#: assets/tasks/push_system_user.py:234 #: assets/tasks/push_system_user.py:234
#: assets/tasks/system_user_connectivity.py:81 #: assets/tasks/system_user_connectivity.py:82
msgid "Hosts count: {}" msgid "Hosts count: {}"
msgstr "主机数量: {}" msgstr "主机数量: {}"
@ -970,19 +970,19 @@ msgstr "推送系统用户到入资产: {}"
msgid "Push system users to asset: {}({}) => {}" msgid "Push system users to asset: {}({}) => {}"
msgstr "推送系统用户到入资产: {}({}) => {}" msgstr "推送系统用户到入资产: {}({}) => {}"
#: assets/tasks/system_user_connectivity.py:80 #: assets/tasks/system_user_connectivity.py:81
msgid "Start test system user connectivity for platform: [{}]" msgid "Start test system user connectivity for platform: [{}]"
msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" msgstr "开始测试系统用户在该系统平台的可连接性: [{}]"
#: assets/tasks/system_user_connectivity.py:100 #: assets/tasks/system_user_connectivity.py:101
msgid "Test system user connectivity: {}" msgid "Test system user connectivity: {}"
msgstr "测试系统用户可连接性: {}" msgstr "测试系统用户可连接性: {}"
#: assets/tasks/system_user_connectivity.py:108 #: assets/tasks/system_user_connectivity.py:112
msgid "Test system user connectivity: {} => {}" msgid "Test system user connectivity: {} => {}"
msgstr "测试系统用户可连接性: {} => {}" msgstr "测试系统用户可连接性: {} => {}"
#: assets/tasks/system_user_connectivity.py:121 #: assets/tasks/system_user_connectivity.py:125
msgid "Test system user connectivity period: {}" msgid "Test system user connectivity period: {}"
msgstr "定期测试系统用户可连接性: {}" msgstr "定期测试系统用户可连接性: {}"
@ -1127,8 +1127,8 @@ msgstr "用户代理"
#: audits/models.py:105 #: audits/models.py:105
#: 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:580 #: users/forms/profile.py:64 users/models/user.py:572
#: users/serializers/profile.py:104 #: users/serializers/profile.py:102
msgid "MFA" msgid "MFA"
msgstr "多因子认证" msgstr "多因子认证"
@ -1218,54 +1218,54 @@ msgstr "钉钉"
msgid "Code is invalid" msgid "Code is invalid"
msgstr "Code无效" msgstr "Code无效"
#: authentication/backends/api.py:52 #: authentication/backends/api.py:57
msgid "Invalid signature header. No credentials provided." msgid "Invalid signature header. No credentials provided."
msgstr "" msgstr ""
#: authentication/backends/api.py:55 #: authentication/backends/api.py:60
msgid "Invalid signature header. Signature string should not contain spaces." msgid "Invalid signature header. Signature string should not contain spaces."
msgstr "" msgstr ""
#: authentication/backends/api.py:62 #: authentication/backends/api.py:67
msgid "Invalid signature header. Format like AccessKeyId:Signature" msgid "Invalid signature header. Format like AccessKeyId:Signature"
msgstr "" msgstr ""
#: authentication/backends/api.py:66 #: authentication/backends/api.py:71
msgid "" msgid ""
"Invalid signature header. Signature string should not contain invalid " "Invalid signature header. Signature string should not contain invalid "
"characters." "characters."
msgstr "" msgstr ""
#: authentication/backends/api.py:86 authentication/backends/api.py:102 #: authentication/backends/api.py:91 authentication/backends/api.py:107
msgid "Invalid signature." msgid "Invalid signature."
msgstr "" msgstr ""
#: authentication/backends/api.py:93 #: authentication/backends/api.py:98
msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
msgstr "" msgstr ""
#: authentication/backends/api.py:98 #: authentication/backends/api.py:103
msgid "Expired, more than 15 minutes" msgid "Expired, more than 15 minutes"
msgstr "" msgstr ""
#: authentication/backends/api.py:105 #: authentication/backends/api.py:110
msgid "User disabled." msgid "User disabled."
msgstr "用户已禁用" msgstr "用户已禁用"
#: authentication/backends/api.py:123 #: authentication/backends/api.py:128
msgid "Invalid token header. No credentials provided." msgid "Invalid token header. No credentials provided."
msgstr "" msgstr ""
#: authentication/backends/api.py:126 #: authentication/backends/api.py:131
msgid "Invalid token header. Sign string should not contain spaces." msgid "Invalid token header. Sign string should not contain spaces."
msgstr "" msgstr ""
#: authentication/backends/api.py:133 #: authentication/backends/api.py:138
msgid "" msgid ""
"Invalid token header. Sign string should not contain invalid characters." "Invalid token header. Sign string should not contain invalid characters."
msgstr "" msgstr ""
#: authentication/backends/api.py:144 #: authentication/backends/api.py:149
msgid "Invalid token or cache refreshed." msgid "Invalid token or cache refreshed."
msgstr "" msgstr ""
@ -1437,13 +1437,13 @@ msgid "Show"
msgstr "显示" msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66 #: authentication/templates/authentication/_access_key_modal.html:66
#: users/models/user.py:470 users/serializers/profile.py:101 #: users/models/user.py:462 users/serializers/profile.py:99
#: users/templates/users/user_verify_mfa.html:32 #: users/templates/users/user_verify_mfa.html:32
msgid "Disable" msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: authentication/templates/authentication/_access_key_modal.html:67 #: authentication/templates/authentication/_access_key_modal.html:67
#: users/models/user.py:471 users/serializers/profile.py:102 #: users/models/user.py:463 users/serializers/profile.py:100
msgid "Enable" msgid "Enable"
msgstr "启用" msgstr "启用"
@ -1547,48 +1547,56 @@ msgstr "返回"
msgid "Copy success" msgid "Copy success"
msgstr "复制成功" msgstr "复制成功"
#: authentication/views/dingtalk.py:41 authentication/views/wecom.py:41 #: authentication/views/dingtalk.py:41
msgid "DingTalk Error, Please contact your system administrator"
msgstr "钉钉错误,请联系系统管理员"
#: authentication/views/dingtalk.py:44
msgid "DingTalk Error"
msgstr "钉钉错误"
#: authentication/views/dingtalk.py:56 authentication/views/wecom.py:56
msgid "You've been hacked" msgid "You've been hacked"
msgstr "你被攻击了" msgstr "你被攻击了"
#: authentication/views/dingtalk.py:77 #: authentication/views/dingtalk.py:92
msgid "DingTalk is already bound" msgid "DingTalk is already bound"
msgstr "钉钉已经绑定" msgstr "钉钉已经绑定"
#: authentication/views/dingtalk.py:90 authentication/views/wecom.py:89 #: authentication/views/dingtalk.py:105 authentication/views/wecom.py:104
msgid "Please verify your password first" msgid "Please verify your password first"
msgstr "请检查密码" msgstr "请检查密码"
#: authentication/views/dingtalk.py:114 authentication/views/wecom.py:113 #: authentication/views/dingtalk.py:129 authentication/views/wecom.py:128
msgid "Invalid user_id" msgid "Invalid user_id"
msgstr "无效的 user_id" msgstr "无效的 user_id"
#: authentication/views/dingtalk.py:130 #: authentication/views/dingtalk.py:145
msgid "DingTalk query user failed" msgid "DingTalk query user failed"
msgstr "钉钉查询用户失败" msgstr "钉钉查询用户失败"
#: authentication/views/dingtalk.py:139 #: authentication/views/dingtalk.py:154
msgid "The DingTalk is already bound to another user" msgid "The DingTalk is already bound to another user"
msgstr "该钉钉已经绑定其他用户" msgstr "该钉钉已经绑定其他用户"
#: authentication/views/dingtalk.py:144 authentication/views/dingtalk.py:227 #: authentication/views/dingtalk.py:159 authentication/views/dingtalk.py:242
#: authentication/views/dingtalk.py:228 #: authentication/views/dingtalk.py:243
msgid "Binding DingTalk successfully" msgid "Binding DingTalk successfully"
msgstr "绑定 钉钉 成功" msgstr "绑定 钉钉 成功"
#: authentication/views/dingtalk.py:196 #: authentication/views/dingtalk.py:211
msgid "Failed to get user from DingTalk" msgid "Failed to get user from DingTalk"
msgstr "从钉钉获取用户失败" msgstr "从钉钉获取用户失败"
#: authentication/views/dingtalk.py:202 #: authentication/views/dingtalk.py:217
msgid "DingTalk is not bound" msgid "DingTalk is not bound"
msgstr "钉钉没有绑定" msgstr "钉钉没有绑定"
#: authentication/views/dingtalk.py:203 authentication/views/wecom.py:201 #: authentication/views/dingtalk.py:218 authentication/views/wecom.py:216
msgid "Please login with a password and then bind the WeCom" msgid "Please login with a password and then bind the WeCom"
msgstr "请使用密码登录,然后绑定企业微信" msgstr "请使用密码登录,然后绑定企业微信"
#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:246 #: authentication/views/dingtalk.py:260 authentication/views/dingtalk.py:261
msgid "Binding DingTalk failed" msgid "Binding DingTalk failed"
msgstr "绑定钉钉失败" msgstr "绑定钉钉失败"
@ -1624,32 +1632,40 @@ msgstr "退出登录成功"
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: authentication/views/wecom.py:76 #: authentication/views/wecom.py:41
msgid "WeCom Error, Please contact your system administrator"
msgstr "企业微信错误,请联系系统管理员"
#: authentication/views/wecom.py:44
msgid "WeCom Error"
msgstr "企业微信错误"
#: authentication/views/wecom.py:91
msgid "WeCom is already bound" msgid "WeCom is already bound"
msgstr "企业微信已经绑定" msgstr "企业微信已经绑定"
#: authentication/views/wecom.py:128 #: authentication/views/wecom.py:143
msgid "WeCom query user failed" msgid "WeCom query user failed"
msgstr "企业微信查询用户失败" msgstr "企业微信查询用户失败"
#: authentication/views/wecom.py:137 #: authentication/views/wecom.py:152
msgid "The WeCom is already bound to another user" msgid "The WeCom is already bound to another user"
msgstr "该企业微信已经绑定其他用户" msgstr "该企业微信已经绑定其他用户"
#: authentication/views/wecom.py:142 authentication/views/wecom.py:225 #: authentication/views/wecom.py:157 authentication/views/wecom.py:240
#: authentication/views/wecom.py:226 #: authentication/views/wecom.py:241
msgid "Binding WeCom successfully" msgid "Binding WeCom successfully"
msgstr "绑定 企业微信 成功" msgstr "绑定 企业微信 成功"
#: authentication/views/wecom.py:194 #: authentication/views/wecom.py:209
msgid "Failed to get user from WeCom" msgid "Failed to get user from WeCom"
msgstr "从企业微信获取用户失败" msgstr "从企业微信获取用户失败"
#: authentication/views/wecom.py:200 #: authentication/views/wecom.py:215
msgid "WeCom is not bound" msgid "WeCom is not bound"
msgstr "没有绑定企业微信" msgstr "没有绑定企业微信"
#: authentication/views/wecom.py:243 authentication/views/wecom.py:244 #: authentication/views/wecom.py:258 authentication/views/wecom.py:259
msgid "Binding WeCom failed" msgid "Binding WeCom failed"
msgstr "绑定企业微信失败" msgstr "绑定企业微信失败"
@ -1675,7 +1691,7 @@ msgstr "对象"
msgid "The file content overflowed (The maximum length `{}` bytes)" msgid "The file content overflowed (The maximum length `{}` bytes)"
msgstr "文件内容太大 (最大长度 `{}` 字节)" msgstr "文件内容太大 (最大长度 `{}` 字节)"
#: common/drf/parsers/base.py:146 #: common/drf/parsers/base.py:148
msgid "Parse file error: {}" msgid "Parse file error: {}"
msgstr "解析文件错误: {}" msgstr "解析文件错误: {}"
@ -1811,36 +1827,36 @@ msgstr "没有该主机 {} 权限"
msgid "Operations" msgid "Operations"
msgstr "运维" msgstr "运维"
#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:160 #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162
msgid "Cycle perform" msgid "Cycle perform"
msgstr "周期执行" msgstr "周期执行"
#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:148 #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150
msgid "Regularly perform" msgid "Regularly perform"
msgstr "定期执行" msgstr "定期执行"
#: ops/mixin.py:106 ops/mixin.py:145 #: ops/mixin.py:106 ops/mixin.py:147
#: xpack/plugins/change_auth_plan/serializers.py:53 #: xpack/plugins/change_auth_plan/serializers.py:53
msgid "Periodic perform" msgid "Periodic perform"
msgstr "定时执行" msgstr "定时执行"
#: ops/mixin.py:111 #: ops/mixin.py:112
msgid "Interval" msgid "Interval"
msgstr "间隔" msgstr "间隔"
#: ops/mixin.py:120 #: ops/mixin.py:122
msgid "* Please enter a valid crontab expression" msgid "* Please enter a valid crontab expression"
msgstr "* 请输入有效的 crontab 表达式" msgstr "* 请输入有效的 crontab 表达式"
#: ops/mixin.py:127 #: ops/mixin.py:129
msgid "Range {} to {}" msgid "Range {} to {}"
msgstr "输入在 {} - {} 范围之间" msgstr "输入在 {} - {} 范围之间"
#: ops/mixin.py:138 #: ops/mixin.py:140
msgid "Require periodic or regularly perform setting" msgid "Require periodic or regularly perform setting"
msgstr "需要周期或定期设置" msgstr "需要周期或定期设置"
#: ops/mixin.py:149 #: ops/mixin.py:151
msgid "" msgid ""
"eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux " "eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux "
"crontab expressions <min hour day month week> (<a href='https://tool.lu/" "crontab expressions <min hour day month week> (<a href='https://tool.lu/"
@ -1851,7 +1867,7 @@ msgstr ""
"分 时 日 月 星期> <a href='https://tool.lu/crontab/' target='_blank'>在线工" "分 时 日 月 星期> <a href='https://tool.lu/crontab/' target='_blank'>在线工"
"具</a> <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行" "具</a> <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
#: ops/mixin.py:160 #: ops/mixin.py:162
msgid "Tips: (Units: hour)" msgid "Tips: (Units: hour)"
msgstr "提示:(单位: 时)" msgstr "提示:(单位: 时)"
@ -1962,12 +1978,11 @@ msgstr "更新任务内容: {}"
msgid "Disk used more than 80%: {} => {}" msgid "Disk used more than 80%: {} => {}"
msgstr "磁盘使用率超过 80%: {} => {}" msgstr "磁盘使用率超过 80%: {} => {}"
#: orgs/api.py:76 #: orgs/api.py:79
#, python-brace-format msgid "Have {} exists, Please delete"
msgid "Have `{model._meta.verbose_name}` exists, Please delete" msgstr "{} 存在数据, 请先删除"
msgstr "`{model._meta.verbose_name}` 存在数据, 请先删除"
#: orgs/api.py:80 #: orgs/api.py:83
msgid "The current organization cannot be deleted" msgid "The current organization cannot be deleted"
msgstr "当前组织不能被删除" msgstr "当前组织不能被删除"
@ -1989,7 +2004,7 @@ msgstr "组织审计员"
msgid "GLOBAL" msgid "GLOBAL"
msgstr "全局组织" msgstr "全局组织"
#: orgs/models.py:419 users/models/user.py:568 #: orgs/models.py:419 users/models/user.py:560
#: users/templates/users/_select_user_modal.html:15 #: users/templates/users/_select_user_modal.html:15
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
@ -2002,7 +2017,7 @@ msgstr "管理员正在修改授权,请稍等"
msgid "The authorization cannot be revoked for the time being" msgid "The authorization cannot be revoked for the time being"
msgstr "该授权暂时不能撤销" msgstr "该授权暂时不能撤销"
#: perms/models/application_permission.py:27 users/models/user.py:185 #: perms/models/application_permission.py:27 users/models/user.py:177
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
@ -2061,7 +2076,7 @@ msgid "Favorite"
msgstr "收藏夹" msgstr "收藏夹"
#: perms/models/base.py:51 templates/_nav.html:21 users/models/group.py:31 #: perms/models/base.py:51 templates/_nav.html:21 users/models/group.py:31
#: users/models/user.py:564 users/templates/users/_select_user_modal.html:16 #: users/models/user.py:556 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
@ -2074,7 +2089,7 @@ msgstr "用户组"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:77 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:77
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:43 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:43
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:81 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:81
#: users/models/user.py:596 #: users/models/user.py:588
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
@ -2096,15 +2111,15 @@ msgstr "用户名"
#: perms/serializers/asset/permission.py:47 #: perms/serializers/asset/permission.py:47
msgid "User groups name" msgid "User groups name"
msgstr "用户组数量" msgstr "用户组名称"
#: perms/serializers/asset/permission.py:48 #: perms/serializers/asset/permission.py:48
msgid "Assets name" msgid "Assets name"
msgstr "资产名" msgstr "资产名"
#: perms/serializers/asset/permission.py:50 #: perms/serializers/asset/permission.py:50
msgid "System users name" msgid "System users name"
msgstr "系统用户名" msgstr "系统用户名"
#: perms/serializers/asset/permission.py:70 users/serializers/user.py:81 #: perms/serializers/asset/permission.py:70 users/serializers/user.py:81
msgid "Is valid" msgid "Is valid"
@ -2131,13 +2146,9 @@ msgstr "邮件已经发送{}, 请检查"
msgid "Welcome to the JumpServer open source Bastion Host" msgid "Welcome to the JumpServer open source Bastion Host"
msgstr "欢迎使用JumpServer开源堡垒机" msgstr "欢迎使用JumpServer开源堡垒机"
#: settings/api/dingtalk.py:29 #: settings/api/dingtalk.py:36 settings/api/wecom.py:36
msgid "AppSecret is required" msgid "Test success"
msgstr "AppSecret 是必须的" msgstr "测试成功"
#: settings/api/dingtalk.py:35 settings/api/wecom.py:35
msgid "OK"
msgstr ""
#: settings/api/ldap.py:189 #: settings/api/ldap.py:189
msgid "Get ldap users is None" msgid "Get ldap users is None"
@ -2147,10 +2158,6 @@ msgstr "获取 LDAP 用户为 None"
msgid "Imported {} users successfully" msgid "Imported {} users successfully"
msgstr "导入 {} 个用户成功" msgstr "导入 {} 个用户成功"
#: settings/api/wecom.py:29
msgid "Secret is required"
msgstr "Secret 是必须的"
#: settings/models.py:123 users/templates/users/reset_password.html:29 #: settings/models.py:123 users/templates/users/reset_password.html:29
msgid "Setting" msgid "Setting"
msgstr "设置" msgstr "设置"
@ -2435,16 +2442,13 @@ msgstr ""
#: settings/serializers/settings.py:172 #: settings/serializers/settings.py:172
msgid "Number of repeated historical passwords" msgid "Number of repeated historical passwords"
msgstr "历史密码可重复次数" msgstr "不能设置近几次密码"
#: settings/serializers/settings.py:173 #: settings/serializers/settings.py:173
msgid "" msgid ""
"Tip: When the user resets the password, it cannot be the previous n " "Tip: When the user resets the password, it cannot be the previous n "
"historical passwords of the user (the value of n here is the value filled in " "historical passwords of the user"
"the input box)" msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
msgstr ""
"提示用户重置密码时不能为该用户前n次历史密码 (此处的n值即为输入框中填写的"
"值)"
#: settings/serializers/settings.py:177 #: settings/serializers/settings.py:177
msgid "Password minimum length" msgid "Password minimum length"
@ -3040,19 +3044,19 @@ msgstr "登录了"
msgid "Filters" msgid "Filters"
msgstr "过滤" msgstr "过滤"
#: terminal/api/session.py:189 #: terminal/api/session.py:185
msgid "Session does not exist: {}" msgid "Session does not exist: {}"
msgstr "会话不存在: {}" msgstr "会话不存在: {}"
#: terminal/api/session.py:192 #: terminal/api/session.py:188
msgid "Session is finished or the protocol not supported" msgid "Session is finished or the protocol not supported"
msgstr "会话已经完成或协议不支持" msgstr "会话已经完成或协议不支持"
#: terminal/api/session.py:197 #: terminal/api/session.py:193
msgid "User does not exist: {}" msgid "User does not exist: {}"
msgstr "用户不存在: {}" msgstr "用户不存在: {}"
#: terminal/api/session.py:201 #: terminal/api/session.py:197
msgid "User does not have permission" msgid "User does not have permission"
msgstr "用户没有权限" msgstr "用户没有权限"
@ -3084,6 +3088,10 @@ msgstr "测试成功"
msgid "Test failure: Account invalid" msgid "Test failure: Account invalid"
msgstr "测试失败: 账户无效" msgstr "测试失败: 账户无效"
#: terminal/api/terminal.py:39
msgid "Have online sessions"
msgstr "有在线会话"
#: terminal/backends/command/es.py:27 #: terminal/backends/command/es.py:27
msgid "Invalid elasticsearch config" msgid "Invalid elasticsearch config"
msgstr "无效的 Elasticsearch 配置" msgstr "无效的 Elasticsearch 配置"
@ -3820,7 +3828,7 @@ msgstr "确认密码"
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms/profile.py:101 users/models/user.py:560 #: users/forms/profile.py:101 users/models/user.py:552
msgid "Email" msgid "Email"
msgstr "邮件" msgstr "邮件"
@ -3852,48 +3860,48 @@ msgstr "复制你的公钥到这里"
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms/profile.py:149 users/serializers/profile.py:76 #: users/forms/profile.py:149 users/serializers/profile.py:74
#: users/serializers/profile.py:150 users/serializers/profile.py:163 #: users/serializers/profile.py:148 users/serializers/profile.py:161
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法" msgstr "SSH密钥不合法"
#: users/models/user.py:182 #: users/models/user.py:174
msgid "System administrator" msgid "System administrator"
msgstr "系统管理员" msgstr "系统管理员"
#: users/models/user.py:183 #: users/models/user.py:175
msgid "System auditor" msgid "System auditor"
msgstr "系统审计员" msgstr "系统审计员"
#: users/models/user.py:472 #: users/models/user.py:464
msgid "Force enable" msgid "Force enable"
msgstr "强制启用" msgstr "强制启用"
#: users/models/user.py:537 #: users/models/user.py:529
msgid "Local" msgid "Local"
msgstr "数据库" msgstr "数据库"
#: users/models/user.py:571 #: users/models/user.py:563
msgid "Avatar" msgid "Avatar"
msgstr "头像" msgstr "头像"
#: users/models/user.py:574 #: users/models/user.py:566
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:604 #: users/models/user.py:596
msgid "Source" msgid "Source"
msgstr "用户来源" msgstr "用户来源"
#: users/models/user.py:608 #: users/models/user.py:600
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最后更新密码日期" msgstr "最后更新密码日期"
#: users/models/user.py:754 #: users/models/user.py:746
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:757 #: users/models/user.py:749
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
@ -3909,11 +3917,11 @@ msgstr "密码不满足安全规则"
msgid "The new password cannot be the last {} passwords" msgid "The new password cannot be the last {} passwords"
msgstr "新密码不能是最近 {} 次的密码" msgstr "新密码不能是最近 {} 次的密码"
#: users/serializers/profile.py:48 #: users/serializers/profile.py:46
msgid "The newly set password is inconsistent" msgid "The newly set password is inconsistent"
msgstr "两次密码不一致" msgstr "两次密码不一致"
#: users/serializers/profile.py:121 users/serializers/user.py:80 #: users/serializers/profile.py:119 users/serializers/user.py:80
msgid "Is first login" msgid "Is first login"
msgstr "首次登录" msgstr "首次登录"
@ -3991,7 +3999,7 @@ msgid "Security token validation"
msgstr "安全令牌验证" msgstr "安全令牌验证"
#: users/templates/users/_base_otp.html:14 xpack/plugins/cloud/models.py:78 #: users/templates/users/_base_otp.html:14 xpack/plugins/cloud/models.py:78
#: xpack/plugins/cloud/serializers.py:145 #: xpack/plugins/cloud/serializers.py:165
msgid "Account" msgid "Account"
msgstr "账户" msgstr "账户"
@ -4281,7 +4289,7 @@ msgstr ""
" <br>\n" " <br>\n"
" " " "
#: users/utils.py:116 users/views/profile/reset.py:126 #: users/utils.py:116 users/views/profile/reset.py:124
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
@ -4553,7 +4561,7 @@ msgstr "用户认证源来自 {}, 请去相应系统修改密码"
msgid "* The new password cannot be the last {} passwords" msgid "* The new password cannot be the last {} passwords"
msgstr "* 新密码不能是最近 {} 次的密码" msgstr "* 新密码不能是最近 {} 次的密码"
#: users/views/profile/reset.py:127 #: users/views/profile/reset.py:125
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
@ -4732,7 +4740,7 @@ msgstr "云服务商"
msgid "Cloud account" msgid "Cloud account"
msgstr "云账号" msgstr "云账号"
#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:126 #: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:146
msgid "Regions" msgid "Regions"
msgstr "地域" msgstr "地域"
@ -4740,7 +4748,7 @@ msgstr "地域"
msgid "Hostname strategy" msgid "Hostname strategy"
msgstr "主机名策略" msgstr "主机名策略"
#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:149 #: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:169
msgid "Always update" msgid "Always update"
msgstr "总是更新" msgstr "总是更新"
@ -4932,20 +4940,24 @@ msgstr ""
msgid "Subscription ID" msgid "Subscription ID"
msgstr "" msgstr ""
#: xpack/plugins/cloud/serializers.py:124 #: xpack/plugins/cloud/serializers.py:55
msgid "{} is required"
msgstr "{} 字段是必填项"
#: xpack/plugins/cloud/serializers.py:144
msgid "History count" msgid "History count"
msgstr "执行次数" msgstr "执行次数"
#: xpack/plugins/cloud/serializers.py:125 #: xpack/plugins/cloud/serializers.py:145
msgid "Instance count" msgid "Instance count"
msgstr "实例个数" msgstr "实例个数"
#: xpack/plugins/cloud/serializers.py:148 #: xpack/plugins/cloud/serializers.py:168
#: xpack/plugins/gathered_user/serializers.py:20 #: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display" msgid "Periodic display"
msgstr "定时执行" msgstr "定时执行"
#: xpack/plugins/cloud/utils.py:64 #: xpack/plugins/cloud/utils.py:65
msgid "Account unavailable" msgid "Account unavailable"
msgstr "账户无效" msgstr "账户无效"
@ -5033,6 +5045,9 @@ msgstr "旗舰版"
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid "AppSecret is required"
#~ msgstr "AppSecret 是必须的"
#~ msgid "Corporation ID(corpid)" #~ msgid "Corporation ID(corpid)"
#~ msgstr "企业 ID(CorpId)" #~ msgstr "企业 ID(CorpId)"

View File

@ -60,7 +60,10 @@ class OrgViewSet(BulkModelViewSet):
@tmp_to_root_org() @tmp_to_root_org()
def get_data_from_model(self, model): def get_data_from_model(self, model):
if model == User: if model == User:
data = model.objects.filter(orgs__id=self.org.id, m2m_org_members__role=ROLE.USER) data = model.objects.filter(
orgs__id=self.org.id,
m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR]
)
elif model == Node: elif model == Node:
# 跟节点不能手动删除,所以排除检查 # 跟节点不能手动删除,所以排除检查
data = model.objects.filter(org_id=self.org.id).exclude(parent_key='', key__regex=r'^[0-9]+$') data = model.objects.filter(org_id=self.org.id).exclude(parent_key='', key__regex=r'^[0-9]+$')
@ -73,7 +76,7 @@ class OrgViewSet(BulkModelViewSet):
for model in org_related_models: for model in org_related_models:
data = self.get_data_from_model(model) data = self.get_data_from_model(model)
if data: if data:
msg = _(f'Have `{model._meta.verbose_name}` exists, Please delete') msg = _('Have {} exists, Please delete').format(model._meta.verbose_name)
return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN) return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN)
else: else:
if str(current_org) == str(self.org): if str(current_org) == str(self.org):

View File

@ -79,7 +79,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
def compute_users_amount(self): def compute_users_amount(self):
if self.org.is_root(): if self.org.is_root():
users_amount = User.objects.all().count() users_amount = User.objects.exclude(role='App').count()
else: else:
users_amount = OrganizationMember.objects.values( users_amount = OrganizationMember.objects.values(
'user_id' 'user_id'

View File

@ -25,13 +25,18 @@ class DingTalkTestingAPI(GenericAPIView):
if not dingtalk_appsecret: if not dingtalk_appsecret:
secret = Setting.objects.filter(name='DINGTALK_APPSECRET').first() secret = Setting.objects.filter(name='DINGTALK_APPSECRET').first()
if not secret: if secret:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': _('AppSecret is required')}) dingtalk_appsecret = secret.cleaned_value
dingtalk_appsecret = secret.cleaned_value
dingtalk_appsecret = dingtalk_appsecret or ''
try: try:
dingtalk = DingTalk(appid=dingtalk_appkey, appsecret=dingtalk_appsecret, agentid=dingtalk_agentid) dingtalk = DingTalk(appid=dingtalk_appkey, appsecret=dingtalk_appsecret, agentid=dingtalk_agentid)
dingtalk.send_text(['test'], 'test') dingtalk.send_text(['test'], 'test')
return Response(status=status.HTTP_200_OK, data={'msg': _('OK')}) return Response(status=status.HTTP_200_OK, data={'msg': _('Test success')})
except APIException as e: except APIException as e:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': e.detail}) try:
error = e.detail['errmsg']
except:
error = e.detail
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error})

View File

@ -25,13 +25,18 @@ class WeComTestingAPI(GenericAPIView):
if not wecom_corpsecret: if not wecom_corpsecret:
secret = Setting.objects.filter(name='WECOM_SECRET').first() secret = Setting.objects.filter(name='WECOM_SECRET').first()
if not secret: if secret:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': _('Secret is required')}) wecom_corpsecret = secret.cleaned_value
wecom_corpsecret = secret.cleaned_value
wecom_corpsecret = wecom_corpsecret or ''
try: try:
wecom = WeCom(corpid=wecom_corpid, corpsecret=wecom_corpsecret, agentid=wecom_agentid) wecom = WeCom(corpid=wecom_corpid, corpsecret=wecom_corpsecret, agentid=wecom_agentid)
wecom.send_text(['test'], 'test') wecom.send_text(['test'], 'test')
return Response(status=status.HTTP_200_OK, data={'msg': _('OK')}) return Response(status=status.HTTP_200_OK, data={'msg': _('Test success')})
except APIException as e: except APIException as e:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': e.detail}) try:
error = e.detail['errmsg']
except:
error = e.detail
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error})

View File

@ -170,7 +170,7 @@ class SecuritySettingSerializer(serializers.Serializer):
OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField( OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField(
min_value=0, max_value=99999, required=True, min_value=0, max_value=99999, required=True,
label=_('Number of repeated historical passwords'), label=_('Number of repeated historical passwords'),
help_text=_('Tip: When the user resets the password, it cannot be the previous n historical passwords of the user (the value of n here is the value filled in the input box)') help_text=_('Tip: When the user resets the password, it cannot be the previous n historical passwords of the user')
) )
SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField( SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(
min_value=6, max_value=30, required=True, min_value=6, max_value=30, required=True,

View File

@ -8,8 +8,9 @@ from rest_framework import generics
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework import status from rest_framework import status
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext_lazy as _
from common.exceptions import JMSException
from common.drf.api import JMSBulkModelViewSet from common.drf.api import JMSBulkModelViewSet
from common.utils import get_object_or_none from common.utils import get_object_or_none
from common.permissions import IsAppUser, IsSuperUser, WithBootstrapToken from common.permissions import IsAppUser, IsSuperUser, WithBootstrapToken
@ -30,6 +31,17 @@ class TerminalViewSet(JMSBulkModelViewSet):
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
filterset_fields = ['name', 'remote_addr', 'type'] filterset_fields = ['name', 'remote_addr', 'type']
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.get_online_session_count() > 0:
raise JMSException(
code='have_online_session',
detail=_('Have online sessions')
)
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
if isinstance(request.data, list): if isinstance(request.data, list):
raise exceptions.BulkCreateNotSupport() raise exceptions.BulkCreateNotSupport()

View File

@ -7,8 +7,6 @@ import string
import random import random
import datetime import datetime
from functools import partial
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.auth.hashers import check_password, make_password from django.contrib.auth.hashers import check_password, make_password
@ -30,7 +28,7 @@ from users.exceptions import MFANotEnabled
from ..signals import post_user_change_password from ..signals import post_user_change_password
__all__ = ['User'] __all__ = ['User', 'UserPasswordHistory']
logger = get_logger(__file__) logger = get_logger(__file__)
@ -83,12 +81,6 @@ class AuthMixin:
else: else:
return False return False
def save_history_password(self, password):
UserPasswordHistory.objects.create(
user=self, password=make_password(password),
date_created=self.date_password_last_updated
)
def is_public_key_valid(self): def is_public_key_valid(self):
""" """
Check if the user's ssh public key is valid. Check if the user's ssh public key is valid.
@ -771,3 +763,9 @@ class UserPasswordHistory(models.Model):
user = models.ForeignKey("users.User", related_name='history_passwords', user = models.ForeignKey("users.User", related_name='history_passwords',
on_delete=models.CASCADE, verbose_name=_('User')) on_delete=models.CASCADE, verbose_name=_('User'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
def __str__(self):
return f'{self.user} set at {self.date_created}'
def __repr__(self):
return self.__str__()

View File

@ -39,8 +39,6 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer):
limit_count = settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT limit_count = settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT
msg = _('The new password cannot be the last {} passwords').format(limit_count) msg = _('The new password cannot be the last {} passwords').format(limit_count)
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
else:
self.instance.save_history_password(value)
return value return value
def validate_new_password_again(self, value): def validate_new_password_again(self, value):

View File

@ -6,17 +6,33 @@ from django_auth_ldap.backend import populate_user
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django_cas_ng.signals import cas_user_authenticated from django_cas_ng.signals import cas_user_authenticated
from django.db.models.signals import post_save
from jms_oidc_rp.signals import openid_create_or_update_user from jms_oidc_rp.signals import openid_create_or_update_user
from common.utils import get_logger from common.utils import get_logger
from .signals import post_user_create from .signals import post_user_create
from .models import User from .models import User, UserPasswordHistory
logger = get_logger(__file__) logger = get_logger(__file__)
@receiver(post_save, sender=User)
def save_passwd_change(sender, instance: User, **kwargs):
passwds = UserPasswordHistory.objects.filter(user=instance).order_by('-date_created')\
.values_list('password', flat=True)[:int(settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT)]
for p in passwds:
if instance.password == p:
break
else:
UserPasswordHistory.objects.create(
user=instance, password=instance.password,
date_created=instance.date_password_last_updated
)
@receiver(post_user_create) @receiver(post_user_create)
def on_user_create(sender, user=None, **kwargs): def on_user_create(sender, user=None, **kwargs):
logger.debug("Receive user `{}` create signal".format(user.name)) logger.debug("Receive user `{}` create signal".format(user.name))

View File

@ -111,8 +111,6 @@ class UserResetPasswordView(FormView):
error = _('* The new password cannot be the last {} passwords').format(limit_count) error = _('* The new password cannot be the last {} passwords').format(limit_count)
form.add_error('new_password', error) form.add_error('new_password', error)
return self.form_invalid(form) return self.form_invalid(form)
else:
user.save_history_password(password)
user.reset_password(password) user.reset_password(password)
User.expired_reset_password_token(token) User.expired_reset_password_token(token)